78 Commits
newdiff ... svn

Author SHA1 Message Date
Loic d'Anterroches
2d271f6b69 Restructured one more time to be as SCM independent as possible.
The work is delegated as much as possible to the IDF_Scm_* classes.
2008-09-02 15:51:57 +02:00
Loic d'Anterroches
57a5b4738a Restructured the file hierarchy.
Git and Svn are now in a Scm subfolder.
2008-09-01 21:42:18 +02:00
Loic d'Anterroches
fad12e17c7 Cosmetic changes. 2008-08-29 20:38:13 +02:00
Loic d'Anterroches
2c7b3e1e1a Cosmetic improvements. 2008-08-29 19:59:53 +02:00
Nicolas LASSALLE
ccc41c86b0 Added support of subversion. 2008-08-29 19:50:10 +02:00
Loic d'Anterroches
763d7ca7f6 Fixed to have the latest ticket first by default. 2008-08-14 09:33:33 +02:00
Loic d'Anterroches
e4902ef76e Fixed another glitch in the French translations. 2008-08-13 22:30:56 +02:00
Loic d'Anterroches
02e00054f6 Added cosmetic improvements.
Updated the French translations to use the French title conventions (No
Uppercase For The First Letter of Each Word but only for the first
word).
Activated some of the menu links in the issue pages depending on the
context.
2008-08-13 22:11:08 +02:00
Loic d'Anterroches
0ee7dc5a89 Updated the French translations. 2008-08-13 21:57:30 +02:00
Loic d'Anterroches
5b411228c2 Added a smarter way to mark a user as interested by an issue.
A user posting a comment is marked as interested only if not the owner
and not the submitter. If in that case, the user will be anyway notified
of the changes to those issues.
2008-08-13 21:28:58 +02:00
Loic d'Anterroches
0e3a74558c Removed stupid debug information. 2008-08-13 21:28:42 +02:00
Loic d'Anterroches
109431cd40 Remove the uncessary sign in propagando from the download pages. 2008-08-13 21:23:26 +02:00
Loic d'Anterroches
a8db15d1e5 Added a warning when adding a comment to a closed issue.
The warning is only displayed for the simple users.
2008-08-13 21:20:47 +02:00
Loic d'Anterroches
0d4a31c086 Added accessibility keys. 2008-08-13 21:09:04 +02:00
Loic d'Anterroches
2a1628f8de Fixed bug when doing a search with an empty query. 2008-08-13 20:47:06 +02:00
Loic d'Anterroches
2e36487be4 Updated the French translations and the translation template. 2008-08-13 18:36:17 +02:00
Loic d'Anterroches
643650a6a7 Added the indexing of the issues in the migration script. 2008-08-13 18:30:49 +02:00
Loic d'Anterroches
a1e10bd169 Added a search engine in the issues. 2008-08-13 18:26:36 +02:00
Loic d'Anterroches
5275a1a9d6 Fixed issue 5, add a way for user to manage their account.
Also added for each user a small public profile.
2008-08-12 22:17:49 +02:00
Loic d'Anterroches
0918d1d542 Added the release script.
This includes the command to generate the .pot file.
2008-08-12 20:36:41 +02:00
Loic d'Anterroches
7955aa6077 Updated the French translations. 2008-08-12 14:51:40 +02:00
Loic d'Anterroches
0ea5171f3e Fixed the HTML to have an alt attribute for the star. 2008-08-12 12:49:59 +02:00
Loic d'Anterroches
4175fe030b Little optimisation to not lookup the starred info if anonymous user. 2008-08-12 12:48:24 +02:00
Loic d'Anterroches
1a030ea4ed Added issue 14, ability to change the interest status.
The interest status or watch list is now shown in the list of the issues
and can be changed by clicking on the star in front of the title of an
issue when looking at an issue.
2008-08-12 12:46:09 +02:00
Loic d'Anterroches
b2356bd157 Fixed 17, check the constraints on the login. 2008-08-11 21:29:17 +02:00
Loic d'Anterroches
32fb0cb121 Fixed issue 15, order asc/desc not matching the age. 2008-08-11 21:10:02 +02:00
Loic d'Anterroches
740979a4cb Fixed issue 19, help disconnected from the project.
Use the tip from zero heure. https://linuxfr.org/~erlen/27028.html
2008-08-11 20:10:33 +02:00
Loic d'Anterroches
1cf3a73fe1 Fixed issue 18, link to download/git clone in the tree view visually annoying. 2008-08-11 15:27:40 +02:00
Loic d'Anterroches
88d39faed7 Fixed bug showing all the downloads of all the projects in the download area. 2008-08-09 23:07:22 +02:00
Loic d'Anterroches
685d3974e8 Removed deleted translations. 2008-08-08 21:42:42 +02:00
Loic d'Anterroches
59a81279ff Fixed issue 6, ability to remove an uploaded file. 2008-08-08 21:33:10 +02:00
Loic d'Anterroches
bd0209a28e Fixed some inconsistencies in the interface (thanks Luigi). 2008-08-08 20:34:40 +02:00
Loic d'Anterroches
6360ea93be Fixed issue 13, email notifications.
Email notifications are now sent at creation/modification of an issue.
2008-08-07 23:12:24 +02:00
Loic d'Anterroches
4b2139fe99 Updated to ignore the indefero-*.zip files. 2008-08-07 21:39:39 +02:00
Loic d'Anterroches
fcc1283001 Fixed not to display the update time of an issue if not needed. 2008-08-07 19:44:59 +02:00
Loic d'Anterroches
7383e18dff Fixed issue 4, with fine control over the tabs access.
For each tab, at the exception of the project home and the
administration area, it possible to control the access rights if the
user is anonymous, signed in, member or owner.
2008-08-07 15:35:03 +02:00
Loic d'Anterroches
1831716b07 Changed to display the update of a download only if updated. 2008-08-07 09:24:15 +02:00
Loic d'Anterroches
553f5179a2 Changed the default downloads sort order.
The sort order is now the reverse upload time and the upload time is
correctly used instead of the update time.
2008-08-06 22:38:22 +02:00
Loic d'Anterroches
19ca5ef02f Added a nice warning if the user tries to download a deprecated file. 2008-08-06 22:30:44 +02:00
Loic d'Anterroches
a8699db268 Improved the style for the labels in the context column. 2008-08-06 22:19:46 +02:00
Loic d'Anterroches
3b5251c1b4 Fixed issue 7, filter the downloads by label. 2008-08-06 21:54:45 +02:00
Loic d'Anterroches
7070e8a13a Added the language example configuration. 2008-08-06 20:26:30 +02:00
Loic d'Anterroches
bac1586334 Added the French translation of the project. 2008-08-06 14:54:01 +02:00
Loic d'Anterroches
1cb8e44ff9 Fixed English typo. 2008-08-05 21:35:26 +02:00
Loic d'Anterroches
78cf0c1c85 Fixed to push the overwrite check at the framework level. 2008-08-05 21:01:06 +02:00
Loic d'Anterroches
9f67343e36 Added the logo files for people to play with. 2008-08-05 20:42:30 +02:00
Loic d'Anterroches
64f41c0cd2 Fixed issue 8, show the featured downloads on the homepage. 2008-08-05 20:33:43 +02:00
Loic d'Anterroches
884a41fbba Fixed to prevent overwritting an existing file. 2008-08-05 20:09:20 +02:00
Loic d'Anterroches
330fa62554 Added more labels per download. 2008-08-05 20:04:42 +02:00
Loic d'Anterroches
3990098e4b Added better control that the issue/download are in the project.
When you view/edit a download or issue, if the download/issue is not in
the current project a 404 page is returned.
2008-08-05 19:58:21 +02:00
Loic d'Anterroches
5e3b2bac28 Added a download counter.
Because we like stats, even if not really reliable :)
2008-08-05 15:44:27 +02:00
Loic d'Anterroches
366b73d27e Fixed issue 10, add mime type when downloading a file. 2008-08-05 12:44:56 +02:00
Loic d'Anterroches
b4cac893a5 Added better keyboard shortcuts.
The list is available in the FAQ.
2008-08-05 12:07:04 +02:00
Loic d'Anterroches
9dedea0f9d Fixed issue 12, better regular expressions to link issues.
The bug was due to a bad initialization of the git repository path in
the issue comment parser. Time to make more init tests!
2008-08-05 11:44:04 +02:00
Loic d'Anterroches
0a868a76ba Changed the order as the result is visualy more balanced. 2008-08-04 21:45:17 +02:00
Loic d'Anterroches
31c79419b5 Limit to the latest 25 commits in the changelog. 2008-08-04 21:25:19 +02:00
Loic d'Anterroches
6ad7ee8c71 Added integration with git-daemon.
In the configuration it is possible to give a git daemon url to have it
displayed in the source view.
2008-08-04 21:24:07 +02:00
Loic d'Anterroches
da1ddc4179 Ignore the default upload folder. 2008-08-04 20:56:41 +02:00
Loic d'Anterroches
e1f961c14b Fixed error in the batch deletion. 2008-08-04 08:43:12 +02:00
Loic d'Anterroches
f40d197478 Updated the template configuration to add the upload path/url. 2008-08-04 08:39:00 +02:00
Loic d'Anterroches
7a5bb7345d Added a download area to the forge. 2008-08-04 00:42:05 +02:00
Loic d'Anterroches
fb9d52fa87 Added a simple logo. 2008-08-03 16:12:25 +02:00
Loic d'Anterroches
e6df530624 Fixed to avoid lines being too long. 2008-08-03 10:09:03 +02:00
Loic d'Anterroches
54d3783173 Added a note on how to mark a bug as duplicate. 2008-08-03 10:07:20 +02:00
Loic d'Anterroches
9e2d9e691b Added the markdown filter for the description of the project. 2008-08-02 22:52:11 +02:00
Loic d'Anterroches
bf8a33708b Added a + icon before the "new issue" link. 2008-08-02 22:26:50 +02:00
Loic d'Anterroches
a105f971c2 Added the FAQ view. 2008-08-02 21:58:00 +02:00
Loic d'Anterroches
4119a160aa Fixed the copyright. 2008-08-02 15:18:19 +02:00
Loic d'Anterroches
495e18cf58 Added installation instructions and configuration example. 2008-08-02 15:13:21 +02:00
Loic d'Anterroches
49f339c7c5 Added the ability to download a zip file of the code at a given commit. 2008-08-02 13:51:42 +02:00
Loic d'Anterroches
d46ddea543 Added tests for the Git class and removed unused code. 2008-08-02 10:36:29 +02:00
Loic d'Anterroches
cf9360a1a8 Fixed the middleware to correctly return a 404 error if the project is
not found.
2008-08-02 09:48:55 +02:00
Loic d'Anterroches
953d7c4ecd Added the name of the login view. 2008-08-02 09:48:35 +02:00
Loic d'Anterroches
f4f8f9615a Added the link to commit and issues in the changelog. 2008-08-02 09:42:05 +02:00
Loic d'Anterroches
4c5bd8d2be Fixed bug when looking at a commit which is a merge.
When you merge you do not get always a diff of files. This fix correct
that for the case of commit 9a2b8e249a.
2008-08-02 09:38:06 +02:00
Loic d'Anterroches
fd6031d7f5 Fixed SQL to follow the standards. 2008-08-01 22:18:58 +02:00
Loic d'Anterroches
83658ac860 Fixed to have standard SQL. 2008-08-01 22:15:49 +02:00
Loic d'Anterroches
593afd9b8b Fix for PostgreSQL. 2008-08-01 22:13:25 +02:00
88 changed files with 7658 additions and 166 deletions

3
.gitignore vendored
View File

@@ -2,3 +2,6 @@ tmp
src/IDF/conf/idf.php
src/IDF/conf/idf.test.php
www/test.php
www/media/upload
src/IDF/gettexttemplates
indefero-*.zip

280
COPYING Normal file
View File

@@ -0,0 +1,280 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS

39
INSTALL.mdtext Normal file
View File

@@ -0,0 +1,39 @@
# Quick installation instruction
* Checkout the trunk of [Pluf](http://www.pluf.org).
* Make a copy of `src/IDF/conf/idf.php-dist` as `src/IDF/conf/idf.php`.
* Update the idf.php file to match your system.
* Run `php /path/to/pluf/src/migrate.php --conf=IDF/conf/idf.php -a -i -d -u` to test the installation of the tables.
* Run `php /path/to/pluf/src/migrate.php --conf=IDF/conf/idf.php -a -i -d` to really install the tables.
* Create a bootsrap file to create the first project and admin user for example `www/bootstrap.php`:
<?php
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).'/../src');
set_include_path(get_include_path().PATH_SEPARATOR.'/path/to/pluf/src');
require 'Pluf.php';
Pluf::start(dirname(__FILE__).'/IDF/conf/idf.php');
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
$project = new IDF_Project();
$project->name = 'Your project';
$project->shortname = 'yourproject'; //Only letters digits
$project->description = 'This is your project.';
$project->create();
$user = new Pluf_User();
$user->first_name = 'John';
$user->last_name = 'Doe';
$user->login = 'doe';
$user->email = 'doe@example.com';
$user->password = 'yourpassword'; // the password is salted/hashed
// in the database, so do not worry :)
$user->administrator = true;
$user->active = true;
$user->create();
print "Bootstrap ok\n";
?>
* Run `php bootstrap.php`.
* Remove the `bootstrp.php` file.
Now you can login with this user into the interface.

109
logo/indefero-logo.svg Normal file
View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1052.3622"
height="744.09448"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
version="1.0"
sodipodi:docname="indefero-logo.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs4">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="538.31563"
inkscape:cy="375.31517"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1279"
inkscape:window-height="951"
inkscape:window-x="0"
inkscape:window-y="25" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g2401"
transform="matrix(1.21,0,0,1.21,-216.85419,33.719307)"
style="fill:#8ae234;stroke:#4e9a06;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none"
inkscape:export-filename="/home/loa/Projects/indefero/logo/powered-by-indefero.png"
inkscape:export-xdpi="12.330909"
inkscape:export-ydpi="12.330909">
<path
id="path2383"
d="M 396.19089,173.14471 C 388.51468,173.95132 381.78894,178.53877 376.60988,184.03602 C 369.37391,191.91606 364.91246,202.1151 363.2879,212.63964 C 361.5643,223.92137 363.02865,235.84599 368.36476,246.01235 C 372.15083,253.25619 377.89637,259.93574 385.66177,262.98007 C 389.52655,264.51944 394.64539,264.01291 397.34089,260.56971 C 399.98446,257.00261 400.03552,252.33571 400.19845,248.08104 C 400.24345,240.4705 399.65096,232.8256 400.65463,225.24911 C 401.52594,215.74288 404.69407,206.6816 407.37075,197.5806 C 408.53317,193.13727 409.62169,188.55252 409.34574,183.93072 C 408.85757,179.30596 405.76515,174.6203 401.04934,173.52402 C 399.47445,173.0752 397.81522,173.03454 396.19089,173.14471 z"
style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#4e9a06;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
id="path2391"
d="M 433.14691,149.28687 C 440.35281,152.05276 445.66203,158.22465 449.24185,164.87502 C 454.19176,174.35936 455.86147,185.3656 454.70671,195.95197 C 453.45166,207.29539 448.95089,218.43468 441.16537,226.87356 C 435.63345,232.89065 428.35489,237.85554 420.06619,238.78632 C 415.93465,239.27292 411.12133,237.45884 409.40885,233.43528 C 407.77858,229.30552 408.93715,224.78444 409.88097,220.63259 C 411.80725,213.26972 414.35818,206.03866 415.34967,198.46058 C 416.96842,189.05274 416.25348,179.48024 416.02353,169.99656 C 416.05073,165.40378 416.18593,160.69353 417.64868,156.30064 C 419.31719,151.95982 423.51697,148.23419 428.35584,148.3958 C 429.99322,148.36988 431.60645,148.76004 433.14691,149.28687 z"
style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#4e9a06;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
<text
xml:space="preserve"
style="font-size:144px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:FromageCondOblique;-inkscape-font-specification:FromageCondOblique"
x="337"
y="355.09448"
id="text2397"
sodipodi:linespacing="100%"
inkscape:export-filename="/home/loa/Projects/indefero/logo/powered-by-indefero.png"
inkscape:export-xdpi="12.330909"
inkscape:export-ydpi="12.330909"><tspan
sodipodi:role="line"
id="tspan2399"
x="337"
y="355.09448">InDefero</tspan></text>
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:semi-condensed;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#888a85;fill-opacity:1;stroke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans Semi-Condensed"
x="346"
y="241.09448"
id="text3195"
sodipodi:linespacing="125%"
inkscape:export-filename="/home/loa/Projects/indefero/logo/powered-by-indefero.png"
inkscape:export-xdpi="12.330909"
inkscape:export-ydpi="12.330909"><tspan
sodipodi:role="line"
id="tspan3197"
x="346"
y="241.09448">Powered by</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

172
logo/star.svg Normal file
View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
width="96"
height="48"
id="svg0"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docname="star.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<metadata
id="metadata2536">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
inkscape:cy="15"
inkscape:cx="49.903061"
inkscape:zoom="4"
inkscape:window-height="755"
inkscape:window-width="969"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
guidetolerance="10.0"
gridtolerance="10.0"
objecttolerance="10.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
showgrid="false"
inkscape:window-x="220"
inkscape:window-y="108"
inkscape:current-layer="svg0" />
<defs
id="defs">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective2538" />
<linearGradient
id="G0">
<stop
id="s1"
style="stop-color:#e6cf00;stop-opacity:1"
offset="0" />
<stop
id="s2"
style="stop-color:#fde94a;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
id="G1">
<stop
id="s3"
style="stop-color:#fcf9fb;stop-opacity:1"
offset="0" />
<stop
id="s4"
style="stop-color:#fcf9fb;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="G2">
<stop
id="s5"
style="stop-color:#000000;stop-opacity:0.63"
offset="0" />
<stop
id="s6"
style="stop-color:#000000;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
x1="14.660452"
y1="7.0243196"
x2="24.030643"
y2="34.826122"
id="G3"
xlink:href="#G1"
gradientUnits="userSpaceOnUse" />
<radialGradient
cx="24"
cy="22"
r="22"
fx="24"
fy="22"
id="R0"
xlink:href="#G0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,-0.2,0.2,1,-3.8,6.8)" />
<radialGradient
cx="17.3125"
cy="25.53125"
r="9.6875"
fx="17.3125"
fy="25.53125"
id="R1"
xlink:href="#G2"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.4,0,0,0.67,-17.1,22.4)" />
<linearGradient
inkscape:collect="always"
xlink:href="#G1"
id="linearGradient2548"
gradientUnits="userSpaceOnUse"
x1="14.660452"
y1="7.0243196"
x2="24.030643"
y2="34.826122"
gradientTransform="translate(47.948863,0.5)" />
</defs>
<path
d="M 37.310531,41.5 L 24.030644,34.826121 L 10.735101,41.471448 L 13.289408,27.369086 L 2.5511635,17.368344 L 17.409702,15.326301 L 24.068641,2.5 L 30.697416,15.340488 L 45.551111,17.414249 L 34.789382,27.392039 L 37.310531,41.5 z"
id="star"
style="fill:url(#R0);stroke:#c4a000"
inkscape:export-xdpi="36"
inkscape:export-ydpi="36" />
<path
d="M 17.731857,15.79089 L 24.06374,3.5868195 L 30.369452,15.794804 L 44.440148,17.761498 L 40.820395,21.121507 C 24.382895,17.434007 31.36502,28.341981 13.251748,30.364721 L 13.819799,27.184324 L 3.6604518,17.718688 C 3.6604518,17.718688 17.731857,15.79089 17.731857,15.79089 z"
id="hl"
style="opacity:0.8;fill:url(#G3);stroke:none"
inkscape:export-filename="/home/loa/Projects/indefero/www/media/idf/img/star.png"
inkscape:export-xdpi="36"
inkscape:export-ydpi="36" />
<path
d="M 35.973752,39.712833 L 24.028333,33.709386 L 12.082531,39.678326 L 14.362768,27.008543 L 4.7776396,18.070249 L 18.056076,16.247921 L 24.062792,4.6783526 L 30.045232,16.255741 L 43.322959,18.11306 L 33.713703,27.034785 L 35.973752,39.712833 z"
id="ol"
style="opacity:0.68999999;fill:none;stroke:#ffffff"
inkscape:export-filename="/home/loa/Projects/indefero/www/media/idf/img/star.png"
inkscape:export-xdpi="36"
inkscape:export-ydpi="36" />
<path
d="M 85.259394,42 L 71.979507,35.326121 L 58.683964,41.971448 L 61.238271,27.869086 L 50.500026,17.868344 L 65.358565,15.826301 L 72.017504,3 L 78.646279,15.840488 L 93.499974,17.914249 L 82.738245,27.892039 L 85.259394,42 z"
id="path2540"
style="fill:#d3d7cf;stroke:#babdb6"
inkscape:export-filename="/home/loa/Projects/indefero/www/media/idf/img/star-grey.png"
inkscape:export-xdpi="36"
inkscape:export-ydpi="36" />
<path
d="M 65.68072,16.29089 L 72.012603,4.08682 L 78.318315,16.294804 L 92.389011,18.261498 L 88.769258,21.621507 C 72.331758,17.934007 79.313883,28.841981 61.200611,30.864721 L 61.768662,27.684324 L 51.609315,18.218688 C 51.609315,18.218688 65.68072,16.29089 65.68072,16.29089 z"
id="path2542"
style="opacity:0.8;fill:url(#linearGradient2548);stroke:none"
inkscape:export-filename="/home/loa/Projects/indefero/www/media/idf/img/star-grey.png"
inkscape:export-xdpi="36"
inkscape:export-ydpi="36" />
<path
d="M 83.922615,40.212833 L 71.977196,34.209386 L 60.031394,40.178326 L 62.311631,27.508543 L 52.726502,18.570249 L 66.004939,16.747921 L 72.011655,5.178353 L 77.994095,16.755741 L 91.271822,18.61306 L 81.662566,27.534785 L 83.922615,40.212833 z"
id="path2544"
style="opacity:0.68999999;fill:none;stroke:#ffffff"
inkscape:export-filename="/home/loa/Projects/indefero/www/media/idf/img/star-grey.png"
inkscape:export-xdpi="36"
inkscape:export-ydpi="36" />
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

10
release-script Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/sh
last="$1"
new="$2"
echo "xgettext -o idf.pot -p ./IDF/locale --force-po --from-code=UTF-8 --keyword --keyword=__ --keyword=_n:1,2 -L PHP ./IDF/*.php"
echo "find ./ -iname \"*.php\" -exec xgettext -o idf.pot -p ./IDF/locale/ --from-code=UTF-8 -j --keyword --keyword=__ --keyword=_n:1,2 -L PHP {} \;"
echo "# git tag v$new"
echo "git archive --format=zip --prefix=indefero-$new/ v$new > indefero-$new.zip"
echo "git log --no-merges v$new ^v$last > ChangeLog-$new"
echo "git shortlog --no-merges v$new ^v$last > ShortLog"
echo "git diff --stat --summary -M v$last v$new > diffstat-$new"

View File

@@ -86,7 +86,7 @@ class IDF_Conf extends Pluf_Model
function initCache()
{
$this->datacache = new ArrayObject();
$sql = new Pluf_SQL('project=%s', $this->_project);
$sql = new Pluf_SQL('project=%s', $this->_project->id);
foreach ($this->getList(array('filter' => $sql->gen())) as $val) {
$this->datacache[$val->vkey] = $val->vdesc;
}
@@ -122,7 +122,7 @@ class IDF_Conf extends Pluf_Model
function delVal($key, $initcache=true)
{
$gconf = new IDF_Conf();
$sql = new Pluf_SQL('vkey=%s AND project=%s', array($key, $this->_project));
$sql = new Pluf_SQL('vkey=%s AND project=%s', array($key, $this->_project->id));
foreach ($gconf->getList(array('filter' => $sql->gen())) as $c) {
$c->delete();
}

View File

@@ -46,6 +46,7 @@ class IDF_Diff
$current_chunk = 0;
$lline = 0;
$rline = 0;
$files = array();
foreach ($this->lines as $line) {
if (0 === strpos($line, 'diff --git a')) {
$current_file = self::getFile($line);
@@ -55,6 +56,14 @@ class IDF_Diff
$current_chunk = 0;
continue;
}
if (0 === strpos($line, 'Index: ')) {
$current_file = self::getSvnFile($line);
$files[$current_file] = array();
$files[$current_file]['chunks'] = array();
$files[$current_file]['chunks_def'] = array();
$current_chunk = 0;
continue;
}
if (0 === strpos($line, '@@ ')) {
$files[$current_file]['chunks_def'][] = self::getChunk($line);
$files[$current_file]['chunks'][] = array();
@@ -94,6 +103,11 @@ class IDF_Diff
return trim(substr($line, 3, $n-3));
}
public static function getSvnFile($line)
{
return substr(trim($line), 7);
}
/**
* Return the html version of a parsed diff.
*/

View File

@@ -220,9 +220,6 @@ class IDF_Form_IssueCreate extends Pluf_Form
foreach ($tags as $tag) {
$issue->setAssoc($tag);
}
$issue->setAssoc($this->user); // the user is
// automatically
// interested.
// add the first comment
$comment = new IDF_IssueComment();
$comment->issue = $issue;

View File

@@ -253,6 +253,10 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
$comment->changes = $changes;
$comment->create();
$this->issue->update();
if ($this->issue->owner != $this->user->id and
$this->issue->submitter != $this->user->id) {
$this->issue->setAssoc($this->user); // interested user.
}
return $this->issue;
}
throw new Exception(__('Cannot save the model from an invalid form.'));

View File

@@ -36,12 +36,12 @@ class IDF_Form_Register extends Pluf_Form
$this->fields['login'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Your login'),
'max_length' => 8,
'max_length' => 15,
'min_length' => 3,
'initial' => $login,
'help_text' => __('The login must be between 3 and 8 characters long and contains only letters and digits.'),
'help_text' => __('The login must be between 3 and 15 characters long and contains only letters and digits.'),
'widget_attrs' => array(
'maxlength' => 8,
'maxlength' => 15,
'size' => 10,
),
));

57
src/IDF/Form/TabsConf.php Normal file
View File

@@ -0,0 +1,57 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Configuration of the tabs access.
*/
class IDF_Form_TabsConf extends Pluf_Form
{
public $conf = null;
public function initFields($extra=array())
{
$this->conf = $extra['conf'];
$ak = array('downloads_access_rights' => __('Downloads'),
'source_access_rights' => __('Source'),
'issues_access_rights' => __('Issues'),);
foreach ($ak as $key=>$label) {
$this->fields[$key] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => $label,
'initial' => $this->conf->getVal($key, 'all'),
'widget_attrs' => array('choices' =>
array(
__('Open to all') => 'all',
__('Signed in users') => 'login',
__('Project members') => 'members',
__('Project owners') => 'owners',
__('Closed') => 'none',
)
),
'widget' => 'Pluf_Form_Widget_SelectInput',
));
}
}
}

View File

@@ -0,0 +1,141 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Update a file for download.
*
*/
class IDF_Form_UpdateUpload extends Pluf_Form
{
public $user = null;
public $project = null;
public $upload = null;
public function initFields($extra=array())
{
$this->user = $extra['user'];
$this->project = $extra['project'];
$this->upload = $extra['upload'];
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Summary'),
'initial' => $this->upload->summary,
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
$tags = $this->upload->get_tags_list();
for ($i=1;$i<7;$i++) {
$initial = '';
if (isset($tags[$i-1])) {
if ($tags[$i-1]->class != 'Other') {
$initial = (string) $tags[$i-1];
} else {
$initial = $tags[$i-1]->name;
}
}
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Labels'),
'initial' => $initial,
'widget_attrs' => array(
'maxlength' => 50,
'size' => 20,
),
));
}
}
/**
* Validate the interconnection in the form.
*/
public function clean()
{
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (split(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
}
$count = array();
for ($i=1;$i<7;$i++) {
$this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]);
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(mb_strtolower(trim($class)),
trim($name));
} else {
$class = 'other';
$name = $this->cleaned_data['label'.$i];
}
if (!isset($count[$class])) $count[$class] = 1;
else $count[$class] += 1;
if (in_array($class, $onemax) and $count[$class] > 1) {
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to an issue.'), $class);
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
}
}
return $this->cleaned_data;
}
/**
* Save the model in the database.
*
* @param bool Commit in the database or not. If not, the object
* is returned but not saved in the database.
* @return Object Model with data set from the form.
*/
function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
// Add a tag for each label
$tags = array();
for ($i=1;$i<7;$i++) {
if (strlen($this->cleaned_data['label'.$i]) > 0) {
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(trim($class), trim($name));
} else {
$class = 'Other';
$name = trim($this->cleaned_data['label'.$i]);
}
$tag = IDF_Tag::add($name, $this->project, $class);
$tags[] = $tag->id;
}
}
// Create the upload
$this->upload->summary = trim($this->cleaned_data['summary']);
$this->upload->modif_dtime = gmdate('Y-m-d H:i:s');
$this->upload->update();
$this->upload->batchAssoc('IDF_Tag', $tags);
return $this->upload;
}
}

153
src/IDF/Form/Upload.php Normal file
View File

@@ -0,0 +1,153 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Upload a file for download.
*
*/
class IDF_Form_Upload extends Pluf_Form
{
public $user = null;
public $project = null;
public function initFields($extra=array())
{
$this->user = $extra['user'];
$this->project = $extra['project'];
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Summary'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
$this->fields['file'] = new Pluf_Form_Field_File(
array('required' => true,
'label' => __('File'),
'initial' => '',
'move_function_params' => array('upload_path' => Pluf::f('upload_path').'/'.$this->project->shortname.'/files',
'upload_path_create' => true,
'upload_overwrite' => false),
));
for ($i=1;$i<7;$i++) {
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Labels'),
'widget_attrs' => array(
'maxlength' => 50,
'size' => 20,
),
));
}
}
public function clean_file()
{
if (!preg_match('/\.(png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|wmv|zip)$/i', $this->cleaned_data['file'])) {
throw new Pluf_Form_Invalid(__('For security reason, you cannot upload a file with this extension.'));
}
return $this->cleaned_data['file'];
}
/**
* Validate the interconnection in the form.
*/
public function clean()
{
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (split(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
}
$count = array();
for ($i=1;$i<7;$i++) {
$this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]);
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(mb_strtolower(trim($class)),
trim($name));
} else {
$class = 'other';
$name = $this->cleaned_data['label'.$i];
}
if (!isset($count[$class])) $count[$class] = 1;
else $count[$class] += 1;
if (in_array($class, $onemax) and $count[$class] > 1) {
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to an issue.'), $class);
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
}
}
return $this->cleaned_data;
}
/**
* Save the model in the database.
*
* @param bool Commit in the database or not. If not, the object
* is returned but not saved in the database.
* @return Object Model with data set from the form.
*/
function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
// Add a tag for each label
$tags = array();
for ($i=1;$i<7;$i++) {
if (strlen($this->cleaned_data['label'.$i]) > 0) {
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(trim($class), trim($name));
} else {
$class = 'Other';
$name = trim($this->cleaned_data['label'.$i]);
}
$tags[] = IDF_Tag::add($name, $this->project, $class);
}
}
// Create the upload
$upload = new IDF_Upload();
$upload->project = $this->project;
$upload->submitter = $this->user;
$upload->summary = trim($this->cleaned_data['summary']);
$upload->file = $this->cleaned_data['file'];
$upload->filesize = filesize(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->cleaned_data['file']);
$upload->downloads = 0;
$upload->create();
foreach ($tags as $tag) {
$upload->setAssoc($tag);
}
return $upload;
}
}

View File

@@ -0,0 +1,70 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Configuration of the labels etc. for the uploaded files.
*/
class IDF_Form_UploadConf extends Pluf_Form
{
/**
* Defined as constants to easily access the value in the
* form in the case nothing is in the db yet.
*/
const init_predefined = 'Featured = Listed on project home page
Type:Executable = Executable application
Type:Installer = Download and run to install application
Type:Package = Your OS package manager installs this
Type:Archive = Download, unarchive, then follow directions
Type:Source = Source code archive
Type:Docs = This file contains documentation
OpSys:All = Works with all operating systems
OpSys:Windows = Works with Windows
OpSys:Linux = Works with Linux
OpSys:OSX = Works with Mac OS X
Deprecated = Most users should NOT download this';
const init_one_max = 'Type';
public function initFields($extra=array())
{
$this->fields['labels_download_predefined'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Predefined download labels'),
'initial' => self::init_predefined,
'widget_attrs' => array('rows' => 13,
'cols' => 75),
'widget' => 'Pluf_Form_Widget_TextareaInput',
));
$this->fields['labels_download_one_max'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Each download may have at most one label with each of these classes'),
'initial' => self::init_one_max,
'widget_attrs' => array('size' => 60),
));
}
}

View File

@@ -0,0 +1,136 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Allow a user to update its details.
*/
class IDF_Form_UserAccount extends Pluf_Form
{
public $user = null;
public function initFields($extra=array())
{
$this->user = $extra['user'];
$this->fields['first_name'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('First name'),
'initial' => $this->user->first_name,
'widget_attrs' => array(
'maxlength' => 50,
'size' => 15,
),
));
$this->fields['last_name'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Last name'),
'initial' => $this->user->last_name,
'widget_attrs' => array(
'maxlength' => 50,
'size' => 20,
),
));
$this->fields['password'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Your password'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_PasswordInput',
'help_text' => Pluf_Template::markSafe(__('Leave blank if you do not want to change your password.').'<br />'.__('Your password must be hard for other people to find it, but easy for you to remember.')),
'widget_attrs' => array(
'maxlength' => 50,
'size' => 15,
),
));
$this->fields['password2'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Confirm your password'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_PasswordInput',
'widget_attrs' => array(
'maxlength' => 50,
'size' => 15,
),
));
}
/**
* Save the model in the database.
*
* @param bool Commit in the database or not. If not, the object
* is returned but not saved in the database.
* @return Object Model with data set from the form.
*/
function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
unset($this->cleaned_data['password2']);
if (strlen($this->cleaned_data['password']) == 0) {
unset($this->cleaned_data['password']);
}
$this->user->setFromFormData($this->cleaned_data);
if ($commit) {
$this->user->update();
}
return $this->user;
}
function clean_last_name()
{
$last_name = trim($this->cleaned_data['last_name']);
if ($last_name == mb_strtoupper($last_name)) {
return mb_convert_case(mb_strtolower($last_name),
MB_CASE_TITLE, 'UTF-8');
}
return $last_name;
}
function clean_first_name()
{
$first_name = trim($this->cleaned_data['first_name']);
if ($first_name == mb_strtoupper($first_name)) {
return mb_convert_case(mb_strtolower($first_name),
MB_CASE_TITLE, 'UTF-8');
}
return $first_name;
}
/**
* Check to see if the 2 passwords are the same.
*/
public function clean()
{
if (!isset($this->errors['password'])
&& !isset($this->errors['password2'])) {
$password1 = $this->cleaned_data['password'];
$password2 = $this->cleaned_data['password2'];
if ($password1 != $password2) {
throw new Pluf_Form_Invalid(__('The passwords do not match. Please give them again.'));
}
}
return $this->cleaned_data;
}
}

View File

@@ -132,11 +132,16 @@ class IDF_Issue extends Pluf_Model
function _toIndex()
{
return '';
$r = array();
foreach ($this->get_comments_list() as $c) {
$r[] = $c->_toIndex();
}
$str = str_repeat($this->summary.' ', 4).' '.implode(' ', $r);
return Pluf_Text::cleanString(html_entity_decode($str, ENT_QUOTES, 'UTF-8'));
}
function preSave()
function preSave($create=false)
{
if ($this->id == '') {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
@@ -144,15 +149,8 @@ class IDF_Issue extends Pluf_Model
$this->modif_dtime = gmdate('Y-m-d H:i:s');
}
function postSave()
function postSave($create=false)
{
// This will be used to fire the indexing or send a
// notification email to the interested people, etc.
$q = new Pluf_Queue();
$q->model_class = __CLASS__;
$q->model_id = $this->id;
$q->action = 'updated';
$q->lock = 0;
$q->create();
IDF_Search::index($this);
}
}

View File

@@ -90,7 +90,7 @@ class IDF_IssueComment extends Pluf_Model
function changedIssue()
{
return count($this->changes) > 0;
return (is_array($this->changes) and count($this->changes) > 0);
}
function _toIndex()
@@ -98,27 +98,14 @@ class IDF_IssueComment extends Pluf_Model
return $this->content;
}
function preSave()
function preSave($create=false)
{
if ($this->id == '') {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
}
}
function postSave()
function postSave($create=false)
{
// This will be used to fire the indexing or send a
// notification email to the interested people, etc.
$q = new Pluf_Queue();
$q->model_class = __CLASS__;
$q->model_id = $this->id;
$q->action = 'updated';
$q->lock = 0;
$q->create();
}
}
function IDF_IssueComment_Filter($text)
{
return wordwrap($text, 80, "\n", true);
}

View File

@@ -24,6 +24,7 @@
/**
* Project middleware.
*
* It must be after the session middleware.
*/
class IDF_Middleware
{
@@ -44,7 +45,20 @@ class IDF_Middleware
{
$match = array();
if (preg_match('#^/p/(\w+)/#', $request->query, $match)) {
$request->project = IDF_Project::getOr404($match[1]);
try {
$request->project = IDF_Project::getOr404($match[1]);
} catch (Pluf_HTTP_Error404 $e) {
return new Pluf_HTTP_Response_NotFound(sprintf(__('The page <em>%s</em> was not found on the server.'), htmlspecialchars($request->query)));
}
$request->conf = new IDF_Conf();
$request->conf->setProject($request->project);
$ak = array('downloads_access_rights' => 'hasDownloadsAccess',
'source_access_rights' => 'hasSourceAccess',
'issues_access_rights' => 'hasIssuesAccess');
$request->rights = array();
foreach ($ak as $key=>$val) {
$request->rights[$val] = (true === IDF_Precondition::accessTabGeneric($request, $key));
}
}
return false;
}
@@ -54,12 +68,14 @@ class IDF_Middleware
function IDF_Middleware_ContextPreProcessor($request)
{
$c = array();
$c['request'] = $request;
if (isset($request->project)) {
$c['project'] = $request->project;
$c['isOwner'] = $request->user->hasPerm('IDF.project-owner',
$request->project);
$c['isMember'] = $request->user->hasPerm('IDF.project-member',
$request->project);
$c = array_merge($c, $request->rights);
}
return $c;
}

View File

@@ -0,0 +1,52 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Add the download of files.
*/
function IDF_Migrations_1Download_up($params=null)
{
$models = array(
'IDF_Upload',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->createTables();
}
}
function IDF_Migrations_1Download_down($params=null)
{
$models = array(
'IDF_Upload',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->dropTables();
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Add the download of files.
*/
function IDF_Migrations_2Search_up($params=null)
{
$models = array(
'IDF_Search_Occ',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->createTables();
}
foreach (Pluf::factory('IDF_Issue')->getList() as $i) {
IDF_Search::index($i);
}
}
function IDF_Migrations_2Search_down($params=null)
{
$models = array(
'IDF_Search_Occ',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->dropTables();
}
}

View File

@@ -35,6 +35,8 @@ function IDF_Migrations_Install_setup($params=null)
'IDF_Issue',
'IDF_IssueComment',
'IDF_Conf',
'IDF_Upload',
'IDF_Search_Occ',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
@@ -64,6 +66,8 @@ function IDF_Migrations_Install_teardown($params=null)
$perm = Pluf_Permission::getFromString('IDF.project-owner');
if ($perm) $perm->delete();
$models = array(
'IDF_Search_Occ',
'IDF_Upload',
'IDF_Conf',
'IDF_IssueComment',
'IDF_Issue',

View File

@@ -40,4 +40,74 @@ class IDF_Precondition
}
return new Pluf_HTTP_Response_Forbidden($request);
}
/**
* Check if the user is project owner or member.
*
* @param Pluf_HTTP_Request
* @return mixed
*/
static public function projectMemberOrOwner($request)
{
$res = Pluf_Precondition::loginRequired($request);
if (true !== $res) {
return $res;
}
if ($request->user->hasPerm('IDF.project-owner', $request->project)
or
$request->user->hasPerm('IDF.project-member', $request->project)
) {
return true;
}
return new Pluf_HTTP_Response_Forbidden($request);
}
/**
* Check if the user can access a given element.
*
* The rights are:
* - 'all' (default)
* - 'none'
* - 'login'
* - 'members'
* - 'owners'
*
* The order of the rights is such that a 'owner' is also a
* 'member' and of course a logged in person.
*
* @param Pluf_HTTP_Request
* @param string Control key
* @return mixed
*/
static public function accessTabGeneric($request, $key)
{
switch ($request->conf->getVal($key, 'all')) {
case 'none':
return new Pluf_HTTP_Response_Forbidden($request);
case 'login':
return Pluf_Precondition::loginRequired($request);
case 'members':
return self::projectMemberOrOwner($request);
case 'owners':
return self::projectOwner($request);
case 'all':
default:
return true;
}
}
static public function accessSource($request)
{
return self::accessTabGeneric($request, 'source_access_rights');
}
static public function accessIssues($request)
{
return self::accessTabGeneric($request, 'issues_access_rights');
}
static public function accessDownloads($request)
{
return self::accessTabGeneric($request, 'downloads_access_rights');
}
}

View File

@@ -31,9 +31,11 @@ class IDF_Project extends Pluf_Model
{
public $_model = __CLASS__;
public $_extra_cache = array();
protected $_pconf = null;
function init()
{
$this->_pconf = null;
$this->_extra_cache = array();
$this->_a['table'] = 'idf_projects';
$this->_a['model'] = __CLASS__;
@@ -89,8 +91,8 @@ class IDF_Project extends Pluf_Model
return '';
}
function preSave()
function preSave($create=false)
{
if ($this->id == '') {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
@@ -191,8 +193,7 @@ class IDF_Project extends Pluf_Model
*/
public function getTagsFromConfig($cfg_key, $default, $dclass='Other')
{
$conf = new IDF_Conf();
$conf->setProject($this);
$conf = $this->getConf();
$tags = array();
foreach (preg_split("/\015\012|\015|\012/", $conf->getVal($cfg_key, $default), -1, PREG_SPLIT_NO_EMPTY) as $s) {
$_s = split('=', $s, 2);
@@ -263,22 +264,37 @@ class IDF_Project extends Pluf_Model
*
* Return an array of tags sorted class, then name. Each tag get
* the extra property 'nb_use' for the number of use in the
* project. Only open issues are used to generate the cloud.
* project. For issues, only open issues are used to generate the
* cloud.
*
* @param string ('issues') 'closed_issues' or 'downloads'
* @return ArrayObject of IDF_Tag
*/
public function getTagCloud()
public function getTagCloud($what='issues')
{
$tag_t = Pluf::factory('IDF_Tag')->getSqlTable();
$issue_t = Pluf::factory('IDF_Issue')->getSqlTable();
$asso_t = $this->_con->pfx.'idf_issue_idf_tag_assoc';
$ostatus = $this->getTagIdsByStatus('open');
if (count($ostatus) == 0) $ostatus[] = 0;
$sql = sprintf('SELECT '.$tag_t.'.id AS id, COUNT(*) AS nb_use FROM '.$tag_t.' '."\n".
if ($what == 'issues' or $what == 'closed_issues') {
$what_t = Pluf::factory('IDF_Issue')->getSqlTable();
$asso_t = $this->_con->pfx.'idf_issue_idf_tag_assoc';
if ($what == 'issues') {
$ostatus = $this->getTagIdsByStatus('open');
} else {
$ostatus = $this->getTagIdsByStatus('closed');
}
if (count($ostatus) == 0) $ostatus[] = 0;
$sql = sprintf('SELECT '.$tag_t.'.id AS id, COUNT(*) AS nb_use FROM '.$tag_t.' '."\n".
'LEFT JOIN '.$asso_t.' ON idf_tag_id='.$tag_t.'.id '."\n".
'LEFT JOIN '.$issue_t.' ON idf_issue_id='.$issue_t.'.id '."\n".
'WHERE idf_tag_id NOT NULL AND '.$issue_t.'.status IN (%s) AND '.$issue_t.'.project='.$this->id.' GROUP BY '.$tag_t.'.id ORDER BY '.$tag_t.'.class ASC, '.$tag_t.'.name ASC',
'LEFT JOIN '.$what_t.' ON idf_issue_id='.$what_t.'.id '."\n".
'WHERE idf_tag_id IS NOT NULL AND '.$what_t.'.status IN (%s) AND '.$what_t.'.project='.$this->id.' GROUP BY '.$tag_t.'.id, '.$tag_t.'.class, '.$tag_t.'.name ORDER BY '.$tag_t.'.class ASC, '.$tag_t.'.name ASC',
implode(', ', $ostatus));
} else {
$what_t = Pluf::factory('IDF_Upload')->getSqlTable();
$asso_t = $this->_con->pfx.'idf_tag_idf_upload_assoc';
$sql = 'SELECT '.$tag_t.'.id AS id, COUNT(*) AS nb_use FROM '.$tag_t.' '."\n".
'LEFT JOIN '.$asso_t.' ON idf_tag_id='.$tag_t.'.id '."\n".
'LEFT JOIN '.$what_t.' ON idf_upload_id='.$what_t.'.id '."\n".
'WHERE idf_tag_id IS NOT NULL AND '.$what_t.'.project='.$this->id.' GROUP BY '.$tag_t.'.id, '.$tag_t.'.class, '.$tag_t.'.name ORDER BY '.$tag_t.'.class ASC, '.$tag_t.'.name ASC';
}
$tags = array();
foreach ($this->_con->select($sql) as $idc) {
$tag = new IDF_Tag($idc['id']);
@@ -287,4 +303,61 @@ class IDF_Project extends Pluf_Model
}
return $tags;
}
/**
* Get the remote access url to the repository.
*
*/
public function getRemoteAccessUrl()
{
$conf = $this->getConf();
$scm = $conf->getVal('scm', 'git');
$scms = Pluf::f('allowed_scm');
return call_user_func(array($scms[$scm], 'getRemoteAccessUrl'),
$this);
}
/**
* Get the root name of the project scm
*
* @return string SCM root
*/
public function getScmRoot()
{
$conf = $this->getConf();
$roots = array('git' => 'master', 'svn' => 'HEAD');
$scm = $conf->getVal('scm', 'git');
return $roots[$scm];
}
/**
* Check that the object belongs to the project or rise a 404
* error.
*
* By convention, all the objects belonging to a project have the
* 'project' property set, so this is easy to check.
*
* @param Pluf_Model
*/
public function inOr404($obj)
{
if ($obj->project != $this->id) {
throw new Pluf_HTTP_Error404();
}
}
/**
* Utility function to get a configuration object.
*
* @return IDF_Conf
*/
public function getConf()
{
if ($this->_pconf == null) {
$this->_pconf = new IDF_Conf();
$this->_pconf->setProject($this);
}
return $this->_pconf;
}
}

44
src/IDF/Scm.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Manage differents SCM systems
*/
class IDF_Scm
{
/**
* Returns an instance of the correct scm backend object.
*
* @return Object
*/
public static function get($request=null)
{
// Get scm type from project conf ; defaults to git
$scm = $request->conf->getVal('scm', 'git');
$scms = Pluf::f('allowed_scm');
return call_user_func(array($scms[$scm], 'factory'),
$request->project);
}
}

View File

@@ -25,7 +25,7 @@
* Git utils.
*
*/
class IDF_Git
class IDF_Scm_Git
{
public $repo = '';
public $mediumtree_fmt = 'commit %H%nAuthor: %an <%ae>%nTree: %T%nDate: %ai%n%n%s%n%n%b';
@@ -35,14 +35,44 @@ class IDF_Git
$this->repo = $repo;
}
/**
* Returns the URL of the git daemon.
*
* @param IDF_Project
* @return string URL
*/
public static function getRemoteAccessUrl($project)
{
$url = Pluf::f('git_remote_url');
if (Pluf::f('git_repositories_unique', true)) {
return $url;
}
return $url.'/'.$project->shortname.'.git';
}
/**
* Returns this object correctly initialized for the project.
*
* @param IDF_Project
* @return IDF_Scm_Git
*/
public static function factory($project)
{
$rep = Pluf::f('git_repositories');
if (false == Pluf::f('git_repositories_unique', false)) {
$rep = $rep.'/'.$project->shortname.'.git';
}
return new IDF_Scm_Git($rep);
}
/**
* Test a given object hash.
*
* @param string Object hash.
* @param null to be svn client compatible
* @return mixed false if not valid or 'blob', 'tree', 'commit'
*/
public function testHash($hash)
public function testHash($hash, $dummy=null)
{
$cmd = sprintf('GIT_DIR=%s git cat-file -t %s',
escapeshellarg($this->repo),
@@ -297,8 +327,24 @@ class IDF_Git
}
}
$c['full_message'] = trim($c['full_message']);
$res[] = (object) $c;
return $res;
}
/**
* Generate the command to create a zip archive at a given commit.
*
* @param string Commit
* @param string Prefix ('git-repo-dump')
* @return string Command
*/
public function getArchiveCommand($commit, $prefix='git-repo-dump/')
{
return sprintf('GIT_DIR=%s git archive --format=zip --prefix=%s %s',
escapeshellarg($this->repo),
escapeshellarg($prefix),
escapeshellarg($commit));
}
}

425
src/IDF/Scm/Svn.php Normal file
View File

@@ -0,0 +1,425 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* SVN utils.
*
*/
class IDF_Scm_Svn
{
public $repo = '';
public $username = '';
public $password = '';
private $assoc = array('dir' => 'tree',
'file' => 'blob');
public function __construct($repo, $username='', $password='')
{
$this->repo = $repo;
$this->username = $username;
$this->password = $password;
}
/**
* Returns the URL of the subversion repository.
*
* @param IDF_Project
* @return string URL
*/
public static function getRemoteAccessUrl($project)
{
$conf = $project->getConf();
if (false !== ($url=$conf->getVal('svn_remote_url', false))) {
// Remote repository
return $url;
}
$url = Pluf::f('svn_remote_url');
if (Pluf::f('svn_repositories_unique', true)) {
return $url;
}
return $url.'/'.$project->shortname;
}
/**
* Returns this object correctly initialized for the project.
*
* @param IDF_Project
* @return IDF_Scm_Svn
*/
public static function factory($project)
{
$conf = $project->getConf();
// Find the repository
if (false !== ($rep=$conf->getVal('svn_remote_url', false))) {
// Remote repository
return new IDF_Scm_Svn($rep,
$conf->getVal('svn_username'),
$conf->getVal('svn_password'));
} else {
$rep = Pluf::f('svn_repositories');
if (false == Pluf::f('svn_repositories_unique', false)) {
$rep = $rep.'/'.$project->shortname;
}
return new IDF_Scm_Svn($rep);
}
}
/**
* Test a given object hash.
*
* @param string Object hash.
* @return mixed false if not valid or 'blob', 'tree', 'commit'
*/
public function testHash($rev, $path='')
{
// OK if HEAD on /
if ($rev === 'HEAD' && $path === '') {
return 'commit';
}
// Else, test the path on revision
$cmd = sprintf('svn info --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.$path),
escapeshellarg($rev));
$xmlInfo = shell_exec($cmd);
// If exception is thrown, return false
try {
$xml = simplexml_load_string($xmlInfo);
}
catch (Exception $e) {
return false;
}
// If the entry node does exists, params are wrong
if (!isset($xml->entry)) {
return false;
}
// Else, enjoy it :)
return 'commit';
}
/**
* Given a commit hash returns an array of files in it.
*
* A file is a class with the following properties:
*
* 'perm', 'type', 'size', 'hash', 'file'
*
* @param string Commit ('HEAD')
* @param string Base folder ('')
* @return array
*/
public function filesAtCommit($rev='HEAD', $folder='')
{
$cmd = sprintf('svn ls --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.$folder),
escapeshellarg($rev));
$xmlLs = shell_exec($cmd);
$xml = simplexml_load_string($xmlLs);
$res = array();
foreach ($xml->list->entry as $entry) {
$file = array();
$file['type'] = $this->assoc[(string) $entry['kind']];
$file['file'] = (string) $entry->name;
$file['fullpath'] = $folder.'/'.((string) $entry->name);
$file['date'] = gmdate('Y-m-d H:i:s',
strtotime((string) $entry->commit->date));
$file['rev'] = (string) $entry->commit['revision'];
// Get commit message
$currentReposFile = $this->repo.'/'.$folder.'/'.$file['file'];
$file['log'] = $this->getCommitMessage($currentReposFile, $rev);
// Get the size if the type is blob
if ($file['type'] == 'blob') {
$file['size'] = (string) $entry->size;
}
$file['perm'] = '';
$res[] = (object) $file;
}
return $res;
}
/**
* Get a commit message for given file and revision.
*
* @param string File
* @param string Commit ('HEAD')
*
* @return String commit message
*/
private function getCommitMessage($file, $rev='HEAD')
{
$cmd = sprintf('svn log --xml --limit 1 --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($file),
escapeshellarg($rev));
$xmlLog = shell_exec($cmd);
$xml = simplexml_load_string($xmlLog);
return (string) $xml->logentry->msg;
}
/**
* Get the file info.
*
* @param string File
* @param string Commit ('HEAD')
* @return false Information
*/
public function getFileInfo($totest, $rev='HEAD')
{
$cmd = sprintf('svn info --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.$totest),
escapeshellarg($rev));
$xmlInfo = shell_exec($cmd);
$xml = simplexml_load_string($xmlInfo);
$entry = $xml->entry;
$file = array();
$file['fullpath'] = $totest;
$file['hash'] = (string) $entry->repository->uuid;
$file['type'] = $this->assoc[(string) $entry['kind']];
$file['file'] = $totest;
$file['rev'] = (string) $entry->commit['revision'];
$file['author'] = (string) $entry->author;
$file['date'] = gmdate('Y-m-d H:i:s', strtotime((string) $entry->commit->date));
$file['size'] = (string) $entry->size;
$file['log'] = '';
return (object) $file;
}
/**
* Get a blob.
*
* @param string Blob hash
* @return string Raw blob
*/
public function getBlob($path, $rev)
{
$cmd = sprintf('svn cat --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.$path),
escapeshellarg($rev));
return shell_exec($cmd);
}
/**
* Get the branches.
*
* @return array Branches.
*/
public function getBranches()
{
$res = array('HEAD');
return $res;
}
/**
* Get commit details.
*
* @param string Commit ('HEAD').
* @return array Changes.
*/
public function getCommit($rev='HEAD')
{
$res = array();
$cmd = sprintf('svn log --xml -v --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo),
escapeshellarg($rev));
$xmlRes = shell_exec($cmd);
$xml = simplexml_load_string($xmlRes);
$res['author'] = (string) $xml->logentry->author;
$res['date'] = gmdate('Y-m-d H:i:s', strtotime((string) $xml->logentry->date));
$res['title'] = (string) $xml->logentry->msg;
$res['commit'] = (string) $xml->logentry['revision'];
$res['changes'] = $this->getDiff($rev);
$res['tree'] = '';
return (object) $res;
}
private function getDiff($rev='HEAD')
{
$res = array();
$cmd = sprintf('svn diff -c %s --username=%s --password=%s %s',
escapeshellarg($rev),
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo));
return shell_exec($cmd);
}
/**
* Get latest changes.
*
* @param string Commit ('HEAD').
* @param int Number of changes (10).
*
* @return array Changes.
*/
public function getChangeLog($rev='HEAD', $n=10)
{
$res = array();
$cmd = sprintf('svn log --xml -v --limit %s --username=%s --password=%s %s@%s',
escapeshellarg($n),
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo),
escapeshellarg($rev));
$xmlRes = shell_exec($cmd);
$xml = simplexml_load_string($xmlRes);
$res = array();
foreach ($xml->logentry as $entry) {
$log = array();
$log['author'] = (string) $entry->author;
$log['date'] = gmdate('Y-m-d H:i:s', strtotime((string) $entry->date));
$log['title'] = (string) $entry->msg;
$log['commit'] = (string) $entry['revision'];
$log['full_message'] = '';
$res[] = (object) $log;
}
return $res;
}
/**
* Generate the command to create a zip archive at a given commit.
* Unsupported feature in subversion
*
* @param string dummy
* @param string dummy
* @return Exception
*/
public function getArchiveCommand($commit, $prefix='git-repo-dump/')
{
throw new Exception(('Unsupported feature.'));
}
/**
* Get additionnals properties on path and revision
*
* @param string File
* @param string Commit ('HEAD')
* @return array
*/
public function getProperties($rev, $path='')
{
$res = array();
$cmd = sprintf('svn proplist --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.$path),
escapeshellarg($rev));
$xmlProps = shell_exec($cmd);
$props = simplexml_load_string($xmlProps);
// No properties, returns an empty array
if (!isset($props->target)) {
return $res;
}
// Get the value of each property
foreach ($props->target->property as $prop) {
$key = (string) $prop['name'];
$res[$key] = $this->getProperty($key, $rev, $path);
}
return $res;
}
/**
* Get a specific additionnal property on path and revision
*
* @param string Property
* @param string File
* @param string Commit ('HEAD')
* @return string the property value
*/
private function getProperty($property, $rev, $path='')
{
$res = array();
$cmd = sprintf('svn propget --xml %s --username=%s --password=%s %s@%s',
escapeshellarg($property),
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.$path),
escapeshellarg($rev));
$xmlProp = shell_exec($cmd);
$prop = simplexml_load_string($xmlProp);
return (string) $prop->target->property;
}
/**
* Get the number of the last commit in the repository.
*
* @param string Commit ('HEAD').
*
* @return String last number commit
*/
public function getLastCommit($rev='HEAD')
{
$xmlInfo = '';
$cmd = sprintf('svn info --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo),
escapeshellarg($rev));
$xmlInfo = shell_exec($cmd);
$xml = simplexml_load_string($xmlInfo);
return (string) $xml->entry->commit['revision'];
}
}

160
src/IDF/Search.php Normal file
View File

@@ -0,0 +1,160 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# Based on work under GNU LGPL copyright, from the Pluf Framework
# Copyright (C) 2001-2007 Loic d'Anterroches and contributors.
#
# ***** END LICENSE BLOCK ***** */
/**
* Class implementing the search engine
*
* It is a modified version of the Pluf_Search class to be able to
* cluster the results by project.
*/
class IDF_Search extends Pluf_Search
{
/**
* Search.
*
* Returns an array of array with model_class, model_id and
* score. The list is already sorted by score descending.
*
* You can then filter the list as you wish with another set of
* weights.
*
* @param string Query string.
* @param int Project id to limit the results (null)
* @param string Stemmer class.
* @return array Results.
*/
public static function mySearch($query, $project=null, $stemmer='Pluf_Text_Stemmer_Porter')
{
$query = Pluf_Text::cleanString(html_entity_decode($query, ENT_QUOTES, 'UTF-8'));
$words = Pluf_Text::tokenize($query);
if ($stemmer != null) {
$words = self::stem($words, $stemmer);
}
$words_flat = array();
foreach ($words as $word=>$c) {
$words_flat[] = $word;
}
$word_ids = self::getWordIds($words_flat);
if (in_array(null, $word_ids)) {
return array();
}
return self::mySearchDocuments($word_ids, $project);
}
/**
* Search documents.
*
* Only the total of the ponderated occurences is used to sort the
* results.
*
* @param array Ids.
* @param IDF_Project Project to limit the search.
* @return array Sorted by score, returns model_class, model_id and score.
*/
public static function mySearchDocuments($wids, $project)
{
$db =& Pluf::db();
$gocc = new IDF_Search_Occ();
$where = array();
foreach ($wids as $id) {
$where[] = $db->qn('word').'='.(int)$id;
}
$prj = (is_null($project)) ? '' : ' AND project='.(int)$project->id;
$select = 'SELECT model_class, model_id, SUM(pondocc) AS score FROM '.$gocc->getSqlTable().' WHERE '.implode(' OR ', $where).$prj.' GROUP BY model_class, model_id HAVING COUNT(*)='.count($wids).' ORDER BY score DESC';
return $db->select($select);
}
/**
* Index a document.
*
* See Pluf_Search for the disclaimer and informations.
*
* @param Pluf_Model Document to index.
* @param Stemmer used. ('Pluf_Text_Stemmer_Porter')
* @return array Statistics.
*/
public static function index($doc, $stemmer='Pluf_Text_Stemmer_Porter')
{
$words = Pluf_Text::tokenize($doc->_toIndex());
if ($stemmer != null) {
$words = self::stem($words, $stemmer);
}
// Get the total number of words.
$total = 0.0;
$words_flat = array();
foreach ($words as $word => $occ) {
$total += (float) $occ;
$words_flat[] = $word;
}
// Drop the last indexation.
$gocc = new IDF_Search_Occ();
$sql = new Pluf_SQL('DELETE FROM '.$gocc->getSqlTable().' WHERE model_class=%s AND model_id=%s', array($doc->_model, $doc->id));
$db =& Pluf::db();
$db->execute($sql->gen());
// Get the ids for each word.
$ids = self::getWordIds($words_flat);
// Insert a new word for the missing words and add the occ.
$n = count($ids);
$new_words = 0;
$done = array();
for ($i=0;$i<$n;$i++) {
if ($ids[$i] === null) {
$word = new Pluf_Search_Word();
$word->word = $words_flat[$i];
$word->create();
$ids[$i] = $word->id;
$new_words++;
}
if (isset($done[$ids[$i]])) {
continue;
}
$done[$ids[$i]] = true;
$occ = new IDF_Search_Occ();
$occ->word = new Pluf_Search_Word($ids[$i]);
$occ->model_class = $doc->_model;
$occ->model_id = $doc->id;
$occ->project = $doc->get_project();
$occ->occ = $words[$words_flat[$i]];
$occ->pondocc = $words[$words_flat[$i]]/$total;
$occ->create();
}
// update the stats
$sql = new Pluf_SQL('model_class=%s AND model_id=%s',
array($doc->_model, $doc->id));
$last_index = Pluf::factory('Pluf_Search_Stats')->getList(array('filter' => $sql->gen()));
if ($last_index->count() == 0) {
$stats = new Pluf_Search_Stats();
$stats->model_class = $doc->_model;
$stats->model_id = $doc->id;
$stats->indexations = 1;
$stats->create();
} else {
$last_index[0]->indexations += 1;
$last_index[0]->update();
}
return array('total' => $total, 'new' => $new_words, 'unique'=>$n);
}
}

99
src/IDF/Search/Occ.php Normal file
View File

@@ -0,0 +1,99 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Storage of the occurence of the words.
*/
class IDF_Search_Occ extends Pluf_Model
{
public $_model = 'IDF_Search_Occ';
function init()
{
$this->_a['verbose'] = __('occurence');
$this->_a['table'] = 'idf_search_occs';
$this->_a['model'] = 'IDF_Search_Occ';
$this->_a['cols'] = array(
// It is mandatory to have an "id" column.
'id' =>
array(
'type' => 'Pluf_DB_Field_Sequence',
//It is automatically added.
'blank' => true,
),
'word' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_Search_Word',
'blank' => false,
'verbose' => __('word'),
),
'model_class' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 150,
'verbose' => __('model class'),
),
'model_id' =>
array(
'type' => 'Pluf_DB_Field_Integer',
'blank' => false,
'verbose' => __('model id'),
),
'project' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Project',
'blank' => false,
'verbose' => __('project'),
),
'occ' =>
array(
'type' => 'Pluf_DB_Field_Integer',
'blank' => false,
'verbose' => __('occurences'),
),
'pondocc' =>
array(
'type' => 'Pluf_DB_Field_Float',
'blank' => false,
'verbose' => __('ponderated occurence'),
),
);
$this->_a['idx'] = array(
'model_class_id_combo_word_idx' =>
array(
'type' => 'unique',
'col' => 'model_class, model_id, word',
),
);
}
function __toString()
{
return $this->word;
}
}

View File

@@ -89,7 +89,7 @@ class IDF_Tag extends Pluf_Model
);
}
function preSave()
function preSave($create=false)
{
$this->lcname = mb_strtolower($this->name);
}

View File

@@ -29,21 +29,27 @@ Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
class IDF_Template_IssueComment extends Pluf_Template_Tag
{
private $project = null;
private $git = null;
private $request = null;
private $scm = null;
function start($text, $project)
function start($text, $request)
{
$this->project = $project;
$this->git = new IDF_Git(Pluf::f('git_repository'));
$text = wordwrap($text, 80, "\n", true);
$this->project = $request->project;
$this->request = $request;
$this->scm = IDF_Scm::get($request);
$text = wordwrap($text, 69, "\n", true);
$text = Pluf_esc($text);
$text = ereg_replace('[[:alpha:]]+://[^<>[:space:]]+[[:alnum:]/]',
'<a href="\\0" rel="nofollow">\\0</a>',
$text);
$text = preg_replace_callback('#(issues?|bugs?|tickets?)\s+(\d+)((\s+and|\s+or|,)\s+(\d+)){0,}#im',
array($this, 'callbackIssues'), $text);
$text = preg_replace_callback('#(commit\s+)([0-9a-f]{5,40})#im',
array($this, 'callbackCommit'), $text);
if ($request->rights['hasIssuesAccess']) {
$text = preg_replace_callback('#(issues?|bugs?|tickets?)\s+(\d+)((\s+and|\s+or|,)\s+(\d+)){0,}#im',
array($this, 'callbackIssues'), $text);
}
if ($request->rights['hasSourceAccess']) {
$text = preg_replace_callback('#(commit\s+)([0-9a-f]{1,40})#im',
array($this, 'callbackCommit'), $text);
}
echo $text;
}
@@ -84,10 +90,10 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
function callbackCommit($m)
{
if ($this->git->testHash($m[2]) != 'commit') {
if ($this->scm->testHash($m[2]) != 'commit') {
return $m[0];
}
$co = $this->git->getCommit($m[2]);
$co = $this->scm->getCommit($m[2]);
return '<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit', array($this->project->shortname, $co->commit)).'">'.$m[1].$m[2].'</a>';
}

View File

@@ -0,0 +1,102 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
Pluf::loadFunction('Pluf_Text_MarkDown_parse');
function IDF_Template_Markdown_filter($mdtext)
{
$filter = new IDF_Template_Markdown();
return Pluf_Template::markSafe(Pluf_Text_MarkDown_parse($filter->go($mdtext)));
}
/**
* Strict class to only allow entities.
*/
class IDF_Template_Markdown extends Pluf_Text_HTML_Filter
{
public $allowed = array();
public $always_close = array();
public $remove_blanks = array();
public $allowed_entities = array(
'amp',
'gt',
'lt',
'quot',
'nbsp',
'ndash',
'rdquo',
'ldquo',
'Alpha',
'Beta',
'Gamma',
'Delta',
'Epsilon',
'Zeta',
'Eta',
'Theta',
'Iota',
'Kappa',
'Lambda',
'Mu',
'Nu',
'Xi',
'Omicron',
'Pi',
'Rho',
'Sigma',
'Tau',
'Upsilon',
'Phi',
'Chi',
'Psi',
'Omega',
'alpha',
'beta',
'gamma',
'delta',
'epsilon',
'zeta',
'eta',
'theta',
'iota',
'kappa',
'lambda',
'mu',
'nu',
'xi',
'omicron',
'pi',
'rho',
'sigmaf',
'sigma',
'tau',
'upsilon',
'phi',
'chi',
'psi',
'omega',
'thetasym',
'upsih',
'piv',
);
}

42
src/IDF/Tests/TestGit.php Normal file
View File

@@ -0,0 +1,42 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Test the git class.
*/
class IDF_Tests_TestGit extends UnitTestCase
{
public function __construct()
{
parent::__construct('Test the git class.');
}
public function testParseLog()
{
$log_lines = preg_split("/\015\012|\015|\012/", file_get_contents(dirname(__FILE__).'/test-log.txt'));
$log = IDF_Git::parseLog($log_lines, 3);
$this->assertEqual('Fixed the middleware to correctly return a 404 error if the project is', $log[0]->title);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Test the source class.
*/
class IDF_Tests_TestSource extends UnitTestCase
{
public function __construct()
{
parent::__construct('Test the source class.');
}
public function testGetMimeType()
{
$files = array(
'whatever.php' => 'application/x-httpd-php',
'whatever.pht' => 'application/x-httpd-php',
'README' => 'text/plain',
);
foreach ($files as $file => $mime) {
$m = IDF_Views_Source::getMimeType($file);
$this->assertEqual($mime, $m[0]);
}
}
}

210
src/IDF/Tests/test-log.txt Normal file
View File

@@ -0,0 +1,210 @@
commit cf9360a1a87f8a98dc506a316055537b95ec245f
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sat Aug 2 09:48:55 2008 +0200
Fixed the middleware to correctly return a 404 error if the project is
not found.
commit 953d7c4ecd37e2c89058b656ee586ae73bfe1e8f
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sat Aug 2 09:48:35 2008 +0200
Added the name of the login view.
commit f4f8f9615ab460b9966509bd60b740c84ec9ecc9
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sat Aug 2 09:42:05 2008 +0200
Added the link to commit and issues in the changelog.
commit 4c5bd8d2be673c9d86d8b6b5108d551982491d5c
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sat Aug 2 09:38:06 2008 +0200
Fixed bug when looking at a commit which is a merge.
When you merge you do not get always a diff of files. This fix correct
that for the case of commit 9a2b8e249acad9f844.
commit fd6031d7f50085b2e42bb539a886fc9175f6fe27
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Aug 1 22:18:58 2008 +0200
Fixed SQL to follow the standards.
commit 83658ac860328568d8570cbbadde565de8348be9
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Aug 1 22:15:49 2008 +0200
Fixed to have standard SQL.
commit 593afd9b8b18efde2a7817d57e8f101112aaa6fe
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Aug 1 22:13:25 2008 +0200
Fix for PostgreSQL.
commit a831c749d849f903f84698c36666affd391aa04d
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Aug 1 22:03:56 2008 +0200
Fixed boolean for PostgreSQL.
commit 7d806325579547facd27a0c44b54872fe9d7fb2d
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Aug 1 00:50:44 2008 +0200
Added code indentation.
commit 80ce87009e0249b7faf1143075bb4006bd2f8e35
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Aug 1 00:38:29 2008 +0200
Added a better commit diff.
commit cb15f036fe91edb9c07ff07f13bc592a8877f3c2
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Thu Jul 31 22:54:02 2008 +0200
Fixed some typos.
commit 1b3a42940d30abb342d9c4b8ff166a586701b784
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Thu Jul 31 22:50:21 2008 +0200
Added the registration procedure.
commit 416d13e249da7d8717ebb15966b9dbbd5c0188ba
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Tue Jul 29 22:33:13 2008 +0200
Added the registration form.
Still need a lot :)
commit c50e218704dcc81eb346cc757e4c1bdbd0808d8b
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Tue Jul 29 21:18:02 2008 +0200
Added a project home.
commit ccf24df13120ae1e64225ec27db1ee647c9df4d4
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Tue Jul 29 15:13:40 2008 +0200
Link to the corresponding tree in the commit view.
commit a6716c77160c9bbb4829eff5c6e11c7b9a4f6d50
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Mon Jul 28 22:09:17 2008 +0200
Added the visualisation of a commit.
commit 3fb47562ce0453af4481b1b470581f5bcbbc6b3b
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Mon Jul 28 20:31:23 2008 +0200
Automatically create links in the issue description and comments.
Link to issues but also commits.
commit 3dbae6272a9f239499038f069f6472dfceb5e73e
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sun Jul 27 21:38:49 2008 +0200
Added smooth handling of bad files and commits.
Now simply redirect to the root of the first available branch.
commit 8a0a35726cd82e3003675d8ba432efe68f7c4f74
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sun Jul 27 17:46:04 2008 +0200
Fixed typo.
commit b6084cbf070bf9e78101b1088d5d6c0d852ed6dc
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sun Jul 27 17:43:51 2008 +0200
Improved the support of the git browser.
Now the reference is always a commit as from a commit it is always possible to travel back to the corresponding tree and blobs.
commit 9a2b8e249acad9f84401846cf87bf040ffd585b3
Merge: 789c7ed... 24e8c80...
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sun Jul 27 01:51:55 2008 +0200
Merge branch 'gitbrowser'
commit 789c7ed43d2b551ba9de97dc8af87cb8d6fa208c
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sun Jul 27 01:51:08 2008 +0200
Take into account if in one branch but listing another.
Need to check how this is working on a bar repository.
commit 24e8c80dfbd358710464cda0e5570655d5f200c6
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sun Jul 27 01:37:43 2008 +0200
Updated the docstring.
commit c915f8fccfc20a1a491bb88dd7848005dc1641ff
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sun Jul 27 01:31:10 2008 +0200
Added the changelog and fixed the source browser.
The source browser is normally a little bit more robust.
commit f57a0ec08bfbaa830f4f7fb39f1f7e39d400ecb3
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sat Jul 26 20:17:18 2008 +0200
Cosmetic improvements.
commit 876e206742179ba640667430b5cad6b45c651250
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Sat Jul 26 18:42:41 2008 +0200
First work on the git browser.
commit 2e711bee8d2b7c6680d6d44cb796f2cc67c9ed68
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Jul 25 14:25:50 2008 +0200
Added a nice formatting of the issue comments.
commit 4eea3797b26cb3ab34c5b4e1b7ff5098f2284deb
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Jul 25 14:15:58 2008 +0200
Added the autocompletion of the owner and cosmetic changes.
commit e5d296d8638db9bd59a85360d80e126b901befeb
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Jul 25 11:17:56 2008 +0200
Added the Shift+w hotkey to get the working list of the user.
commit d19dbb441c86740d6a2df2501cb5875a5fc34e19
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Jul 25 11:16:18 2008 +0200
Added a hotkey tag to easily add them.
Added a new Shift+m hotkey to access the list of submitted issues.
commit cc5ec30e6a06a3c855848b907512e4b796950981
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Jul 25 10:28:35 2008 +0200
Correctly set the copyrights.
commit efbd82fccb20b0b4c9ac82777b8bdf7d83be0e40
Author: Loic d'Anterroches <titoo@users.sourceforge.net>
Date: Fri Jul 25 10:26:05 2008 +0200
Initial commit.

148
src/IDF/Upload.php Normal file
View File

@@ -0,0 +1,148 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* File upload.
*
* You can set labels on files.
*/
class IDF_Upload extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_uploads';
$this->_a['model'] = __CLASS__;
$this->_a['cols'] = array(
// It is mandatory to have an "id" column.
'id' =>
array(
'type' => 'Pluf_DB_Field_Sequence',
'blank' => true,
),
'project' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Project',
'blank' => false,
'verbose' => __('project'),
'relate_name' => 'issues',
),
'summary' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 250,
'verbose' => __('summary'),
),
'file' =>
array(
'type' => 'Pluf_DB_Field_File',
'blank' => false,
'default' => 0,
'verbose' => __('file'),
'help_text' => __('The path is relative to the upload path.'),
),
'filesize' =>
array(
'type' => 'Pluf_DB_Field_Integer',
'blank' => false,
'default' => 0,
'verbose' => __('file size in bytes'),
),
'submitter' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User',
'blank' => false,
'verbose' => __('submitter'),
'relate_name' => 'submitted_issue',
),
'tags' =>
array(
'type' => 'Pluf_DB_Field_Manytomany',
'blank' => true,
'model' => 'IDF_Tag',
'verbose' => __('labels'),
),
'downloads' =>
array(
'type' => 'Pluf_DB_Field_Integer',
'blank' => false,
'default' => 0,
'verbose' => __('number of downloads'),
),
'creation_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('creation date'),
),
'modif_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('modification date'),
),
);
$this->_a['idx'] = array(
'modif_dtime_idx' =>
array(
'col' => 'modif_dtime',
'type' => 'normal',
),
);
$table = $this->_con->pfx.'idf_tag_idf_upload_assoc';
$this->_a['views'] = array(
'join_tags' =>
array(
'join' => 'LEFT JOIN '.$table
.' ON idf_upload_id=id',
),
);
}
function __toString()
{
return $this->file;
}
function _toIndex()
{
return '';
}
function preSave($create=false)
{
if ($this->id == '') {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
$this->modif_dtime = gmdate('Y-m-d H:i:s');
}
}
function getAbsoluteUrl($project)
{
return Pluf::f('url_media').'/upload/'.$project->shortname.'/files/'.$this->file;
}
}

View File

@@ -163,4 +163,20 @@ class IDF_Views
'form' => $form),
$request);
}
/**
* FAQ.
*/
public function faq($request, $match)
{
$title = __('Here to Help You!');
$projects = Pluf::factory('IDF_Project')->getList();
return Pluf_Shortcuts_RenderToResponse('faq.html',
array(
'page_title' => $title,
'projects' => $projects,
),
$request);
}
}

318
src/IDF/Views/Download.php Normal file
View File

@@ -0,0 +1,318 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
Pluf::loadFunction('Pluf_Shortcuts_RenderToResponse');
Pluf::loadFunction('Pluf_Shortcuts_GetObjectOr404');
Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
/**
* Download's views.
*
* - List all the files.
* - Upload a file.
* - See the details of a file.
*/
class IDF_Views_Download
{
/**
* List the files available for download.
*/
public $index_precond = array('IDF_Precondition::accessDownloads');
public function index($request, $match)
{
$prj = $request->project;
$title = sprintf(__('%s Downloads'), (string) $prj);
// Paginator to paginate the files to download.
$pag = new Pluf_Paginator(new IDF_Upload());
$pag->class = 'recent-issues';
$pag->item_extra_props = array('project_m' => $prj,
'shortname' => $prj->shortname);
$pag->summary = __('This table shows the files to download.');
$pag->action = array('IDF_Views_Download::index', array($prj->shortname));
$pag->edit_action = array('IDF_Views_Download::view', 'shortname', 'id');
$pag->forced_where = new Pluf_SQL('project=%s', array($prj->id));
$list_display = array(
'file' => __('File'),
array('summary', 'IDF_Views_Download_SummaryAndLabels', __('Summary')),
array('filesize', 'IDF_Views_Download_Size', __('Size')),
array('creation_dtime', 'Pluf_Paginator_DateYMD', __('Uploaded')),
);
$pag->configure($list_display, array(), array('file', 'filesize', 'creation_dtime'));
$pag->items_per_page = 10;
$pag->no_results_text = __('No downloads were found.');
$pag->sort_order = array('creation_dtime', 'DESC');
$pag->setFromRequest($request);
$tags = $prj->getTagCloud('downloads');
return Pluf_Shortcuts_RenderToResponse('downloads/index.html',
array(
'page_title' => $title,
'downloads' => $pag,
'tags' => $tags,
),
$request);
}
/**
* View details of a file.
*/
public $view_precond = array('IDF_Precondition::accessDownloads');
public function view($request, $match)
{
$prj = $request->project;
$upload = Pluf_Shortcuts_GetObjectOr404('IDF_Upload', $match[2]);
$prj->inOr404($upload);
$title = sprintf(__('Download %s'), $upload->summary);
$form = false;
$ptags = self::getDownloadTags($prj);
$dtag = array_pop($ptags); // The last tag is the deprecated tag.
$tags = $upload->get_tags_list();
$deprecated = Pluf_Model_InArray($dtag, $tags);
if ($request->method == 'POST' and
true === IDF_Precondition::projectMemberOrOwner($request)) {
$form = new IDF_Form_UpdateUpload($request->POST,
array('project' => $prj,
'upload' => $upload,
'user' => $request->user));
if ($form->isValid()) {
$upload = $form->save();
$urlfile = Pluf_HTTP_URL_urlForView('IDF_Views_Download::view',
array($prj->shortname, $upload->id));
$request->user->setMessage(sprintf(__('The file <a href="%1$s">%2$s</a> has been updated.'), $urlfile, Pluf_esc($upload->file)));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::index',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} elseif (true === IDF_Precondition::projectMemberOrOwner($request)) {
$form = new IDF_Form_UpdateUpload(null,
array('upload' => $upload,
'project' => $prj,
'user' => $request->user));
}
return Pluf_Shortcuts_RenderToResponse('downloads/view.html',
array(
'file' => $upload,
'deprecated' => $deprecated,
'tags' => $tags,
'auto_labels' => self::autoCompleteArrays($prj),
'page_title' => $title,
'form' => $form,
),
$request);
}
/**
* Delete a file.
*/
public $delete_precond = array('IDF_Precondition::accessDownloads',
'IDF_Precondition::projectMemberOrOwner');
public function delete($request, $match)
{
$prj = $request->project;
$upload = Pluf_Shortcuts_GetObjectOr404('IDF_Upload', $match[2]);
$prj->inOr404($upload);
$title = sprintf(__('Delete Download %s'), $upload->summary);
$form = false;
$ptags = self::getDownloadTags($prj);
$dtag = array_pop($ptags); // The last tag is the deprecated tag.
$tags = $upload->get_tags_list();
$deprecated = Pluf_Model_InArray($dtag, $tags);
if ($request->method == 'POST') {
$fname = $upload->file;
@unlink(Pluf::f('upload_path').'/'.$prj->shortname.'/files/'.$fname);
$upload->delete();
$request->user->setMessage(__('The file has been deleted.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::index',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
return Pluf_Shortcuts_RenderToResponse('downloads/delete.html',
array(
'file' => $upload,
'deprecated' => $deprecated,
'tags' => $tags,
'page_title' => $title,
),
$request);
}
/**
* Download a file.
*/
public $download_precond = array('IDF_Precondition::accessDownloads');
public function download($request, $match)
{
$prj = $request->project;
$upload = Pluf_Shortcuts_GetObjectOr404('IDF_Upload', $match[2]);
$prj->inOr404($upload);
$upload->downloads += 1;
$upload->update();
return new Pluf_HTTP_Response_Redirect($upload->getAbsoluteUrl($prj));
}
/**
* Submit a new file for download.
*/
public $submit_precond = array('IDF_Precondition::accessDownloads',
'IDF_Precondition::projectMemberOrOwner');
public function submit($request, $match)
{
$prj = $request->project;
$title = __('New Download');
if ($request->method == 'POST') {
$form = new IDF_Form_Upload(array_merge($request->POST, $request->FILES),
array('project' => $prj,
'user' => $request->user));
if ($form->isValid()) {
$upload = $form->save();
$urlfile = Pluf_HTTP_URL_urlForView('IDF_Views_Download::view',
array($prj->shortname, $upload->id));
$request->user->setMessage(sprintf(__('The <a href="%s">file</a> has been uploaded.'), $urlfile));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::index',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$form = new IDF_Form_Upload(null,
array('project' => $prj,
'user' => $request->user));
}
return Pluf_Shortcuts_RenderToResponse('downloads/submit.html',
array(
'auto_labels' => self::autoCompleteArrays($prj),
'page_title' => $title,
'form' => $form,
),
$request);
}
/**
* Create the autocomplete arrays for the little AJAX stuff.
*/
public static function autoCompleteArrays($project)
{
$conf = new IDF_Conf();
$conf->setProject($project);
$st = preg_split("/\015\012|\015|\012/",
$conf->getVal('labels_downloads_predefined', IDF_Form_UploadConf::init_predefined), -1, PREG_SPLIT_NO_EMPTY);
$auto = '';
foreach ($st as $s) {
$v = '';
$d = '';
$_s = split('=', $s, 2);
if (count($_s) > 1) {
$v = trim($_s[0]);
$d = trim($_s[1]);
} else {
$v = trim($_s[0]);
}
$auto .= sprintf('{ name: "%s", to: "%s" }, ',
Pluf_esc($d), Pluf_esc($v));
}
return substr($auto, 0, -1);
}
/**
* View list of downloads with a given label.
*/
public $listLabel_precond = array('IDF_Precondition::accessDownloads');
public function listLabel($request, $match)
{
$prj = $request->project;
$tag = Pluf_Shortcuts_GetObjectOr404('IDF_Tag', $match[2]);
$prj->inOr404($tag);
$title = sprintf(__('%1$s Downloads with Label %2$s'), (string) $prj,
(string) $tag);
// Paginator to paginate the downloads
$pag = new Pluf_Paginator(new IDF_Upload());
$pag->model_view = 'join_tags';
$pag->class = 'recent-issues';
$pag->item_extra_props = array('project_m' => $prj,
'shortname' => $prj->shortname);
$pag->summary = sprintf(__('This table shows the downloads with label %s.'), (string) $tag);
$pag->forced_where = new Pluf_SQL('project=%s AND idf_tag_id=%s', array($prj->id, $tag->id));
$pag->action = array('IDF_Views_Download::index', array($prj->shortname));
$pag->edit_action = array('IDF_Views_Download::view', 'shortname', 'id');
$list_display = array(
'file' => __('File'),
array('summary', 'IDF_Views_Download_SummaryAndLabels', __('Summary')),
array('filesize', 'IDF_Views_Download_Size', __('Size')),
array('creation_dtime', 'Pluf_Paginator_DateYMD', __('Uploaded')),
);
$pag->configure($list_display, array(), array('file', 'filesize', 'creation_dtime'));
$pag->items_per_page = 10;
$pag->no_results_text = __('No downloads were found.');
$pag->sort_order = array('creation_dtime', 'DESC');
$pag->setFromRequest($request);
$tags = $prj->getTagCloud('downloads');
return Pluf_Shortcuts_RenderToResponse('downloads/index.html',
array(
'page_title' => $title,
'label' => $tag,
'downloads' => $pag,
'tags' => $tags,
),
$request);
}
/**
* Get the download tags.
*
* @param IDF_Project
* @return ArrayObject The tags
*/
public static function getDownloadTags($project)
{
return $project->getTagsFromConfig('labels_downloads_predefined',
IDF_Form_UploadConf::init_predefined);
}
}
/**
* Display the summary of a download, then on a new line, display the
* list of labels.
*
* The summary of the download is linking to the download.
*/
function IDF_Views_Download_SummaryAndLabels($field, $down, $extra='')
{
$tags = array();
foreach ($down->get_tags_list() as $tag) {
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::listLabel',
array($down->shortname, $tag->id));
$tags[] = sprintf('<a href="%s" class="label">%s</a>', $url, Pluf_esc((string) $tag));
}
$out = '';
if (count($tags)) {
$out = '<br /><span class="note">'.implode(', ', $tags).'</span>';
}
return Pluf_esc($down->summary).$out;
}
function IDF_Views_Download_Size($field, $down)
{
return Pluf_Utils::prettySize($down->$field);
}

View File

@@ -34,10 +34,11 @@ class IDF_Views_Issue
/**
* View list of issues for a given project.
*/
public $index_precond = array('IDF_Precondition::accessIssues');
public function index($request, $match)
{
$prj = $request->project;
$title = sprintf(__('%s Recent Issues'), (string) $prj);
$title = sprintf(__('%s Open Issues'), (string) $prj);
// Get stats about the issues
$open = $prj->getIssueCountByStatus('open');
$closed = $prj->getIssueCountByStatus('closed');
@@ -45,13 +46,15 @@ class IDF_Views_Issue
$pag = new Pluf_Paginator(new IDF_Issue());
$pag->class = 'recent-issues';
$pag->item_extra_props = array('project_m' => $prj,
'shortname' => $prj->shortname);
$pag->summary = __('This table shows the open recent issues.');
'shortname' => $prj->shortname,
'current_user' => $request->user);
$pag->summary = __('This table shows the open issues.');
$otags = $prj->getTagIdsByStatus('open');
if (count($otags) == 0) $otags[] = 0;
$pag->forced_where = new Pluf_SQL('project=%s AND status IN ('.implode(', ', $otags).')', array($prj->id));
$pag->action = array('IDF_Views_Issue::index', array($prj->shortname));
$pag->sort_order = array('modif_dtime', 'DESC');
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
$pag->sort_reverse_order = array('modif_dtime');
$list_display = array(
'id' => __('Id'),
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
@@ -68,6 +71,7 @@ class IDF_Views_Issue
'open' => $open,
'closed' => $closed,
'issues' => $pag,
'cloud' => 'issues',
),
$request);
}
@@ -77,7 +81,8 @@ class IDF_Views_Issue
*
* Only open issues are shown.
*/
public $myIssues_precond = array('Pluf_Precondition::loginRequired');
public $myIssues_precond = array('IDF_Precondition::accessIssues',
'Pluf_Precondition::loginRequired');
public function myIssues($request, $match)
{
$prj = $request->project;
@@ -99,11 +104,13 @@ class IDF_Views_Issue
$pag = new Pluf_Paginator(new IDF_Issue());
$pag->class = 'recent-issues';
$pag->item_extra_props = array('project_m' => $prj,
'shortname' => $prj->shortname);
$pag->summary = __('This table shows the open recent issues.');
'shortname' => $prj->shortname,
'current_user' => $request->user);
$pag->summary = __('This table shows the open issues.');
$pag->forced_where = $f_sql;
$pag->action = array('IDF_Views_Issue::myIssues', array($prj->shortname, $match[2]));
$pag->sort_order = array('modif_dtime', 'DESC');
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
$pag->sort_reverse_order = array('modif_dtime');
$list_display = array(
'id' => __('Id'),
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
@@ -124,7 +131,8 @@ class IDF_Views_Issue
$request);
}
public $create_precond = array('Pluf_Precondition::loginRequired');
public $create_precond = array('IDF_Precondition::accessIssues',
'Pluf_Precondition::loginRequired');
public function create($request, $match)
{
$prj = $request->project;
@@ -141,6 +149,24 @@ class IDF_Views_Issue
$urlissue = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($prj->shortname, $issue->id));
$request->user->setMessage(sprintf(__('<a href="%s">Issue %d</a> has been created.'), $urlissue, $issue->id));
if (null != $issue->get_owner() and $issue->owner != $issue->submitter) {
$comments = $issue->get_comments_list(array('order' => 'id ASC'));
$context = new Pluf_Template_Context(
array(
'issue' => $issue,
'comment' => $comments[0],
'project' => $prj,
'url_base' => Pluf::f('url_base'),
)
);
$oemail = $issue->get_owner()->email;
$email = new Pluf_Mail(Pluf::f('from_email'), $oemail,
sprintf(__('Issue %s - %s (InDefero)'),
$issue->id, $issue->summary));
$tmpl = new Pluf_Template('issues/issue-created-email.txt');
$email->addTextMessage($tmpl->render($context));
$email->sendMail();
}
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
@@ -157,19 +183,63 @@ class IDF_Views_Issue
$request);
}
public $search_precond = array('IDF_Precondition::accessIssues');
public function search($request, $match)
{
$prj = $request->project;
if (!isset($request->REQUEST['q']) or trim($request->REQUEST['q']) == '') {
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
$q = $request->REQUEST['q'];
$title = sprintf(__('Search Issues - %s'), Pluf_esc($q));
$issues = new Pluf_Search_ResultSet(IDF_Search::mySearch($q, $prj));
if (count($issues) > 100) {
// no more than 100 results as we do not care
$issues->results = array_slice($issues->results, 0, 100);
}
$pag = new Pluf_Paginator();
$pag->items = $issues;
$pag->class = 'recent-issues';
$pag->item_extra_props = array('project_m' => $prj,
'shortname' => $prj->shortname,
'current_user' => $request->user);
$pag->summary = __('This table shows the found issues.');
$pag->action = array('IDF_Views_Issue::search', array($prj->shortname), array('q'=> $q));
$list_display = array(
'id' => __('Id'),
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
);
$pag->configure($list_display);
$pag->items_per_page = 100;
$pag->no_results_text = __('No issues were found.');
$pag->setFromRequest($request);
$params = array('page_title' => $title,
'issues' => $pag,
'q' => $q,
);
return Pluf_Shortcuts_RenderToResponse('issues/search.html', $params, $request);
}
public $view_precond = array('IDF_Precondition::accessIssues');
public function view($request, $match)
{
$prj = $request->project;
$issue = Pluf_Shortcuts_GetObjectOr404('IDF_Issue', $match[2]);
if ($issue->project != $prj->id) {
throw new Pluf_HTTP_Error404();
}
$prj->inOr404($issue);
$comments = $issue->get_comments_list(array('order' => 'id ASC'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($prj->shortname, $issue->id));
$title = Pluf_Template::markSafe(sprintf(__('Issue <a href="%s">%d</a>: %s'), $url, $issue->id, $issue->summary));
$form = false; // The form is available only if logged in.
$starred = false;
$closed = in_array($issue->status, $prj->getTagIdsByStatus('closed'));
if (!$request->user->isAnonymous()) {
$starred = Pluf_Model_InArray($request->user, $issue->get_interested_list());
$params = array(
'project' => $prj,
'user' => $request->user,
@@ -184,6 +254,38 @@ class IDF_Views_Issue
$urlissue = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($prj->shortname, $issue->id));
$request->user->setMessage(sprintf(__('<a href="%s">Issue %d</a> has been updated.'), $urlissue, $issue->id));
// Get the list of interested person + owner + submitter
$interested = $issue->get_interested_list();
if (!Pluf_Model_InArray($issue->get_submitter(), $interested)) {
$interested[] = $issue->get_submitter();
}
if (null != $issue->get_owner() and
!Pluf_Model_InArray($issue->get_owner(), $interested)) {
$interested[] = $issue->get_owner();
}
$comments = $issue->get_comments_list(array('order' => 'id DESC'));
$context = new Pluf_Template_Context(
array(
'issue' => $issue,
'comments' => $comments,
'project' => $prj,
'url_base' => Pluf::f('url_base'),
)
);
$tmpl = new Pluf_Template('issues/issue-updated-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail_Batch(Pluf::f('from_email'));
foreach ($interested as $user) {
if ($user->id != $request->user->id) {
$email->setSubject(sprintf(__('Updated Issue %s - %s (InDefero)'),
$issue->id, $issue->summary));
$email->setTo($user->email);
$email->setReturnPath(Pluf::f('from_email'));
$email->addTextMessage($text_email);
$email->sendMail();
}
}
$email->close();
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
@@ -193,11 +295,13 @@ class IDF_Views_Issue
$arrays = self::autoCompleteArrays($prj);
return Pluf_Shortcuts_RenderToResponse('issues/view.html',
array_merge(
array('project' => $prj,
array(
'issue' => $issue,
'comments' => $comments,
'form' => $form,
'starred' => $starred,
'page_title' => $title,
'closed' => $closed,
),
$arrays),
$request);
@@ -206,6 +310,7 @@ class IDF_Views_Issue
/**
* View list of issues for a given project with a given status.
*/
public $listStatus_precond = array('IDF_Precondition::accessIssues');
public function listStatus($request, $match)
{
$prj = $request->project;
@@ -218,13 +323,15 @@ class IDF_Views_Issue
$pag = new Pluf_Paginator(new IDF_Issue());
$pag->class = 'recent-issues';
$pag->item_extra_props = array('project_m' => $prj,
'shortname' => $prj->shortname);
'shortname' => $prj->shortname,
'current_user' => $request->user);
$pag->summary = __('This table shows the closed issues.');
$otags = $prj->getTagIdsByStatus('closed');
if (count($otags) == 0) $otags[] = 0;
$pag->forced_where = new Pluf_SQL('project=%s AND status IN ('.implode(', ', $otags).')', array($prj->id));
$pag->action = array('IDF_Views_Issue::index', array($prj->shortname));
$pag->sort_order = array('modif_dtime', 'DESC');
$pag->action = array('IDF_Views_Issue::listStatus', array($prj->shortname, $status));
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
$pag->sort_reverse_order = array('modif_dtime');
$list_display = array(
'id' => __('Id'),
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
@@ -241,6 +348,7 @@ class IDF_Views_Issue
'open' => $open,
'closed' => $closed,
'issues' => $pag,
'cloud' => 'closed_issues',
),
$request);
}
@@ -248,6 +356,7 @@ class IDF_Views_Issue
/**
* View list of issues for a given project with a given label.
*/
public $listLabel_precond = array('IDF_Precondition::accessIssues');
public function listLabel($request, $match)
{
$prj = $request->project;
@@ -271,13 +380,15 @@ class IDF_Views_Issue
$pag->model_view = 'join_tags';
$pag->class = 'recent-issues';
$pag->item_extra_props = array('project_m' => $prj,
'shortname' => $prj->shortname);
'shortname' => $prj->shortname,
'current_user' => $request->user);
$pag->summary = sprintf(__('This table shows the issues with label %s.'), (string) $tag);
$otags = $prj->getTagIdsByStatus($status);
if (count($otags) == 0) $otags[] = 0;
$pag->forced_where = new Pluf_SQL('project=%s AND idf_tag_id=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $tag->id));
$pag->action = array('IDF_Views_Issue::listLabel', array($prj->shortname, $tag->id, $status));
$pag->sort_order = array('modif_dtime', 'DESC');
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
$pag->sort_reverse_order = array('modif_dtime');
$list_display = array(
'id' => __('Id'),
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
@@ -305,6 +416,31 @@ class IDF_Views_Issue
$request);
}
/**
* Star/Unstar an issue.
*/
public $star_precond = array('IDF_Precondition::accessIssues',
'Pluf_Precondition::loginRequired');
public function star($request, $match)
{
$prj = $request->project;
$issue = Pluf_Shortcuts_GetObjectOr404('IDF_Issue', $match[2]);
$prj->inOr404($issue);
if ($request->method == 'POST') {
$starred = Pluf_Model_InArray($request->user, $issue->get_interested_list());
if ($starred) {
$issue->delAssoc($request->user);
$request->user->setMessage(__('The issue has been removed from your watch list.'));
} else {
$issue->setAssoc($request->user);
$request->user->setMessage(__('The issue has been added to your watch list.'));
}
}
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($prj->shortname, $issue->id));
return new Pluf_HTTP_Response_Redirect($url);
}
/**
* Create the autocomplete arrays for the little AJAX stuff.
*/
@@ -372,11 +508,16 @@ function IDF_Views_Issue_SummaryAndLabels($field, $issue, $extra='')
array($issue->shortname, $tag->id, 'open'));
$tags[] = sprintf('<a class="label" href="%s">%s</a>', $url, Pluf_esc((string) $tag));
}
$s = '';
if (!$issue->current_user->isAnonymous() and
Pluf_Model_InArray($issue->current_user, $issue->get_interested_list())) {
$s = '<img style="vertical-align: text-bottom;" src="'.Pluf_Template_Tag_MediaUrl::url('/idf/img/star.png').'" alt="'.__('On your watch list.').'" /> ';
}
$out = '';
if (count($tags)) {
$out = '<br /><span class="note">'.implode(', ', $tags).'</span>';
}
return sprintf('<a href="%s">%s</a>', $edit, Pluf_esc($issue->summary)).$out;
return $s.sprintf('<a href="%s">%s</a>', $edit, Pluf_esc($issue->summary)).$out;
}
/**

View File

@@ -39,10 +39,17 @@ class IDF_Views_Project
$prj = $request->project;
$team = $prj->getMembershipData();
$title = (string) $prj;
$downloads = array();
if ($request->rights['hasDownloadsAccess']) {
$tags = IDF_Views_Download::getDownloadTags($prj);
// the first tag is the featured, the last is the deprecated.
$downloads = $tags[0]->get_idf_upload_list();
}
return Pluf_Shortcuts_RenderToResponse('project-home.html',
array(
'page_title' => $title,
'team' => $team,
'downloads' => $downloads,
),
$request);
}
@@ -82,7 +89,7 @@ class IDF_Views_Project
/**
* Administrate the issue tracking of a project.
*/
public $adminIssueTracking_precond = array('IDF_Precondition::projectOwner');
public $adminIssues_precond = array('IDF_Precondition::projectOwner');
public function adminIssues($request, $match)
{
$prj = $request->project;
@@ -123,6 +130,49 @@ class IDF_Views_Project
$request);
}
/**
* Administrate the downloads of a project.
*/
public $adminDownloads_precond = array('IDF_Precondition::projectOwner');
public function adminDownloads($request, $match)
{
$prj = $request->project;
$title = sprintf(__('%s Downloads Configuration'), (string) $prj);
$conf = new IDF_Conf();
$conf->setProject($prj);
if ($request->method == 'POST') {
$form = new IDF_Form_UploadConf($request->POST);
if ($form->isValid()) {
foreach ($form->cleaned_data as $key=>$val) {
$conf->setVal($key, $val);
}
$request->user->setMessage(__('The downloads configuration has been saved.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminDownloads',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$params = array();
$keys = array('labels_download_predefined', 'labels_download_one_max');
foreach ($keys as $key) {
$_val = $conf->getVal($key, false);
if ($_val !== false) {
$params[$key] = $_val;
}
}
if (count($params) == 0) {
$params = null; //Nothing in the db, so new form.
}
$form = new IDF_Form_UploadConf($params);
}
return Pluf_Shortcuts_RenderToResponse('admin/downloads.html',
array(
'page_title' => $title,
'form' => $form,
),
$request);
}
/**
* Administrate the members of a project.
*/
@@ -154,4 +204,49 @@ class IDF_Views_Project
),
$request);
}
/**
* Administrate the access rights to the tabs.
*/
public $adminTabs_precond = array('IDF_Precondition::projectOwner');
public function adminTabs($request, $match)
{
$prj = $request->project;
$title = sprintf(__('%s Tabs Access Rights'), (string) $prj);
$extra = array(
'conf' => $request->conf,
);
if ($request->method == 'POST') {
$form = new IDF_Form_TabsConf($request->POST, $extra);
if ($form->isValid()) {
foreach ($form->cleaned_data as $key=>$val) {
$request->conf->setVal($key, $val);
}
$request->user->setMessage(__('The project tabs access rights have been saved.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminTabs',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$params = array();
$keys = array('downloads_access_rights', 'source_access_rights',
'issues_access_rights');
foreach ($keys as $key) {
$_val = $request->conf->getVal($key, false);
if ($_val !== false) {
$params[$key] = $_val;
}
}
if (count($params) == 0) {
$params = null; //Nothing in the db, so new form.
}
$form = new IDF_Form_TabsConf($params, $extra);
}
return Pluf_Shortcuts_RenderToResponse('admin/tabs.html',
array(
'page_title' => $title,
'form' => $form,
),
$request);
}
}

View File

@@ -31,13 +31,16 @@ Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
*/
class IDF_Views_Source
{
public $changeLog_precond = array('IDF_Precondition::accessSource');
public function changeLog($request, $match)
{
$title = sprintf('%s Git Change Log', (string) $request->project);
$git = new IDF_Git(Pluf::f('git_repository'));
$branches = $git->getBranches();
$title = sprintf(__('%s %s Change Log'), (string) $request->project,
$this->getScmType($request));
$scm = IDF_Scm::get($request);
$branches = $scm->getBranches();
$commit = $match[2];
$res = $git->getChangeLog($commit, 50);
$res = $scm->getChangeLog($commit, 25);
$scmConf = $request->conf->getVal('scm', 'git');
return Pluf_Shortcuts_RenderToResponse('source/changelog.html',
array(
'page_title' => $title,
@@ -45,27 +48,36 @@ class IDF_Views_Source
'changes' => $res,
'commit' => $commit,
'branches' => $branches,
'scm' => $scmConf,
),
$request);
}
public $treeBase_precond = array('IDF_Precondition::accessSource');
public function treeBase($request, $match)
{
$title = sprintf('%s Git Source Tree', (string) $request->project);
$git = new IDF_Git(Pluf::f('git_repository'));
$title = sprintf(__('%s %s Source Tree'), (string) $request->project,
$this->getScmType($request));
$scm = IDF_Scm::get($request);
$commit = $match[2];
$branches = $git->getBranches();
if ('commit' != $git->testHash($commit)) {
$branches = $scm->getBranches();
if ('commit' != $scm->testHash($commit)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$branches[0]));
return new Pluf_HTTP_Response_Redirect($url);
}
$res = $git->filesAtCommit($commit);
$cobject = $git->getCommit($commit);
$res = $scm->filesAtCommit($commit);
$cobject = $scm->getCommit($commit);
$tree_in = in_array($commit, $branches);
return Pluf_Shortcuts_RenderToResponse('source/tree.html',
$scmConf = $request->conf->getVal('scm', 'git');
$props = null;
if ($scmConf === 'svn') {
$props = $scm->getProperties($commit);
}
return Pluf_Shortcuts_RenderToResponse('source/'.$scmConf.'/tree.html',
array(
'page_title' => $title,
'title' => $title,
@@ -74,25 +86,28 @@ class IDF_Views_Source
'commit' => $commit,
'tree_in' => $tree_in,
'branches' => $branches,
'props' => $props,
),
$request);
}
public $tree_precond = array('IDF_Precondition::accessSource');
public function tree($request, $match)
{
$title = sprintf('%s Git Source Tree', (string) $request->project);
$git = new IDF_Git(Pluf::f('git_repository'));
$branches = $git->getBranches();
$title = sprintf(__('%s %s Source Tree'), (string) $request->project,
$this->getScmType($request));
$scm = IDF_Scm::get($request);
$branches = $scm->getBranches();
$commit = $match[2];
if ('commit' != $git->testHash($commit)) {
$request_file = $match[3];
if ('commit' != $scm->testHash($commit, $request_file)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$branches[0]));
return new Pluf_HTTP_Response_Redirect($url);
}
$request_file = $match[3];
$request_file_info = $git->getFileInfo($request_file, $commit);
$request_file_info = $scm->getFileInfo($request_file, $commit);
if (!$request_file_info) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
@@ -101,19 +116,33 @@ class IDF_Views_Source
return new Pluf_HTTP_Response_Redirect($url);
}
if ($request_file_info->type != 'tree') {
return new Pluf_HTTP_Response($git->getBlob($request_file_info->hash),
'application/octet-stream');
$info = self::getMimeType($request_file_info->file);
if (Pluf::f('src') == 'git') {
$rep = new Pluf_HTTP_Response($scm->getBlob($request_file_info->hash),
$info[0]);
}
else {
$rep = new Pluf_HTTP_Response($scm->getBlob($request_file_info->fullpath, $commit),
$info[0]);
}
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$info[1].'"';
return $rep;
}
$bc = self::makeBreadCrumb($request->project, $commit, $request_file_info->file);
$page_title = $bc.' - '.$title;
$cobject = $git->getCommit($commit);
$cobject = $scm->getCommit($commit);
$tree_in = in_array($commit, $branches);
$res = $git->filesAtCommit($commit, $request_file);
$res = $scm->filesAtCommit($commit, $request_file);
// try to find the previous level if it exists.
$prev = split('/', $request_file);
$l = array_pop($prev);
$previous = substr($request_file, 0, -strlen($l.' '));
return Pluf_Shortcuts_RenderToResponse('source/tree.html',
$scmConf = $request->conf->getVal('scm', 'git');
$props = null;
if ($scmConf === 'svn') {
$props = $scm->getProperties($commit, $request_file);
}
return Pluf_Shortcuts_RenderToResponse('source/'.$scmConf.'/tree.html',
array(
'page_title' => $page_title,
'title' => $title,
@@ -125,6 +154,7 @@ class IDF_Views_Source
'prev' => $previous,
'tree_in' => $tree_in,
'branches' => $branches,
'props' => $props,
),
$request);
}
@@ -146,24 +176,25 @@ class IDF_Views_Source
return '<span class="breadcrumb">'.implode('<span class="sep">'.$sep.'</span>', $out).'</span>';
}
public $commit_precond = array('IDF_Precondition::accessSource');
public function commit($request, $match)
{
$git = new IDF_Git(Pluf::f('git_repository'));
$scm = IDF_Scm::get($request);
$commit = $match[2];
$branches = $git->getBranches();
if ('commit' != $git->testHash($commit)) {
$branches = $scm->getBranches();
if ('commit' != $scm->testHash($commit)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$branches[0]));
return new Pluf_HTTP_Response_Redirect($url);
}
$title = sprintf('%s Commit Details', (string) $request->project);
$page_title = sprintf('%s Commit Details - %s', (string) $request->project, $commit);
$cobject = $git->getCommit($commit);
$title = sprintf(__('%s Commit Details'), (string) $request->project);
$page_title = sprintf(__('%s Commit Details - %s'), (string) $request->project, $commit);
$cobject = $scm->getCommit($commit);
$diff = new IDF_Diff($cobject->changes);
$diff->parse();
$scmConf = $request->conf->getVal('scm', 'git');
return Pluf_Shortcuts_RenderToResponse('source/commit.html',
array(
'page_title' => $page_title,
@@ -172,11 +203,141 @@ class IDF_Views_Source
'cobject' => $cobject,
'commit' => $commit,
'branches' => $branches,
'scm' => $scmConf,
),
$request);
}
/**
* Get a zip archive of the current commit.
*
*/
public $download_precond = array('IDF_Precondition::accessSource');
public function download($request, $match)
{
$commit = trim($match[2]);
$scm = IDF_Scm::get($request);
$branches = $scm->getBranches();
if ('commit' != $scm->testHash($commit)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$branches[0]));
return new Pluf_HTTP_Response_Redirect($url);
}
$base = $request->project->shortname.'-'.$commit;
$cmd = $scm->getArchiveCommand($commit, $base.'/');
$rep = new Pluf_HTTP_Response_CommandPassThru($cmd, 'application/x-zip');
$rep->headers['Content-Transfer-Encoding'] = 'binary';
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$base.'.zip"';
return $rep;
}
/**
* Display tree of a specific SVN revision
*
*/
public function treeRev($request, $match)
{
$prj = $request->project;
// Redirect to tree base if not svn
if ($request->conf->getVal('scm', 'git') != 'svn') {
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($prj->shortname, $prj->getScmRoot()));
return new Pluf_HTTP_Response_Redirect($url);
}
// Get revision value
if (!isset($request->REQUEST['rev']) or trim($request->REQUEST['rev']) == '') {
$scmRoot = $prj->getScmRoot();
}
else {
$scmRoot = $request->REQUEST['rev'];
}
// Get source if not /
if (isset($request->REQUEST['sourcefile']) and trim($request->REQUEST['sourcefile']) != '') {
$scmRoot .= '/'.$request->REQUEST['sourcefile'];
}
// Redirect
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($prj->shortname, $scmRoot));
return new Pluf_HTTP_Response_Redirect($url);
}
/**
* Display SVN changelog from specific revision
*
*/
public function changelogRev($request, $match)
{
$prj = $request->project;
// Redirect to tree base if not svn
if ($request->conf->getVal('scm', 'git') != 'svn') {
$scmRoot = $prj->getScmRoot();
}
// Get revision value if svn
else {
if (!isset($request->REQUEST['rev']) or trim($request->REQUEST['rev']) == '') {
$scmRoot = $prj->getScmRoot();
}
else {
$scmRoot = $request->REQUEST['rev'];
}
}
// Redirect
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::changeLog',
array($prj->shortname, $scmRoot));
return new Pluf_HTTP_Response_Redirect($url);
}
/**
* Find the mime type of a file.
*
* Use /etc/mime.types to find the type.
*
* @param string Filename/Filepath
* @param string Path to the mime types database ('/etc/mime.types')
* @param array Mime type found or 'application/octet-stream' and basename
*/
public static function getMimeType($file, $src='/etc/mime.types')
{
$mimes = preg_split("/\015\012|\015|\012/", file_get_contents($src));
$info = pathinfo($file);
if (isset($info['extension'])) {
foreach ($mimes as $mime) {
if ('#' != substr($mime, 0, 1)) {
$elts = preg_split('/ |\t/', $mime, -1, PREG_SPLIT_NO_EMPTY);
if (in_array($info['extension'], $elts)) {
return array($elts[0], $info['basename']);
}
}
}
} else {
// we consider that if no extension and base name is all
// uppercase, then we have a text file.
if ($info['basename'] == strtoupper($info['basename'])) {
return array('text/plain', $info['basename']);
}
}
return array('application/octet-stream', $info['basename']);
}
/**
* Get the scm type for page title
*
* @return String
*/
private function getScmType($request)
{
return mb_convert_case($request->conf->getVal('scm', 'git'),
MB_CASE_TITLE, 'UTF-8');
}
}
function IDF_Views_Source_PrettySize($size)

78
src/IDF/Views/User.php Normal file
View File

@@ -0,0 +1,78 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
Pluf::loadFunction('Pluf_Shortcuts_RenderToResponse');
/**
* User management views.
*
* Edit your account.
* Add emails for the link between a commit and an account.
*/
class IDF_Views_User
{
/**
* Simple management of the base info of the user.
*/
public $myAccount_precond = array('Pluf_Precondition::loginRequired');
public function myAccount($request, $match)
{
$params = array('user' => $request->user);
if ($request->method == 'POST') {
$form = new IDF_Form_UserAccount($request->POST, $params);
if ($form->isValid()) {
$user = $form->save();
$url = Pluf_HTTP_URL_urlForView('IDF_Views::index');
$request->user->setMessage(__('Your personal information have been updated.'));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$form = new IDF_Form_UserAccount($request->user->getData(), $params);
}
return Pluf_Shortcuts_RenderToResponse('user/myaccount.html',
array('page_title' => __('Your Account'),
'form' => $form),
$request);
}
/**
* Public profile of a user.
*/
public function view($request, $match)
{
$projects = Pluf::factory('IDF_Project')->getList();
$sql = new Pluf_SQL('login=%s', array($match[1]));
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
if (count($users) != 1 or !$users[0]->active) {
throw new Pluf_HTTP_Error404();
}
return Pluf_Shortcuts_RenderToResponse('user/public.html',
array('page_title' => (string) $users[0],
'member' => $users[0],
'projects' => $projects,
),
$request);
}
}

134
src/IDF/conf/idf.php-dist Normal file
View File

@@ -0,0 +1,134 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
$cfg = array();
// to start with, it can be practical.
$cfg['debug'] = false;
// available languages
$cfg['languages'] = array('en', 'fr');
# SCM base configuration
$cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git',
'svn' => 'IDF_Scm_Svn',
);
// if you have a single git repository, just put the full path to it
// without trailing slash.
// If within a folder you have a series of git repository, just put
// the folder without a trailing slash.
// InDefero will automatically append a slash, the project shortname
// and .git to create the name of the repository.
$cfg['git_repositories'] = '/home/git/repositories/indefero.git';
$cfg['git_repositories_unique'] = true;
$cfg['git_remote_url'] = 'git://projects.ceondo.com/indefero.git';
// One git repository per project. "/".$project->shortname.".git"
// is automatically added to the end of the path/url.
//$cfg['git_repositories'] = '/home/git/repositories';
//$cfg['git_repositories_unique'] = false;
//$cfg['git_remote_url'] = 'git://projects.ceondo.com';
// Same as for git, you can have multiple repositories, one for each
// project or a single one for all the projects.
$cfg['svn_repositories'] = 'file:///home/svn/repositories/indefero';
$cfg['svn_repositories_unique'] = true;
$cfg['svn_remote_url'] = 'http://projects.ceondo.com/svn/indefero';
// admins will get an email in case of errors in the system in non
// debug mode.
$cfg['admins'] = array(
array('Admin', 'you@example.com'),
);
// Email configuration
$cfg['send_emails'] = true;
$cfg['mail_backend'] = 'smtp';
$cfg['mail_host'] = 'localhost';
$cfg['mail_port'] = 25;
$cfg['pear_path'] = '/usr/share/php';
// Paths/Url configuration
$cfg['idf_base'] = ''; // put '/whatever/index.php if you are not
// using mod_rewrite and installing in a
// subfolder.
$cfg['url_base'] = 'http://projects.ceondo.com';
$cfg['url_media'] = 'http://projects.ceondo.com/media';
$cfg['url_upload'] = 'http://projects/ceondo.com/media/upload';
$cfg['upload_path'] = '/path/to/media/upload';
$cfg['login_success_url'] = '/';
$cfg['after_logout_page'] = '/';
// write here a long random string unique for this installation. This
// is critical to put a long string.
$cfg['secret_key'] = '';
// the sender of all the emails.
$cfg['from_email'] = 'sender@example.com';
// Email address for the bounced messages.
$cfg['bounce_email'] = 'no-reply@example.com';
// Temporary folder where the script is writing the compiled templates,
// cached data and other temporary resources.
// It must be writeable by your webserver instance.
// It is mandatory if you are using the template system.
$cfg['tmp_folder'] = '/tmp';
// The folder in which the templates of the application are located.
$cfg['template_folders'] = array(
dirname(__FILE__).'/../templates',
);
// Database configuration
// For testing we are using in memory SQLite database.
$cfg['db_login'] = 'www';
$cfg['db_password'] = '';
$cfg['db_server'] = '';
$cfg['db_version'] = '';
$cfg['db_table_prefix'] = '';
$cfg['db_engine'] = 'PostgreSQL'; // SQLite is also well tested or MySQL
$cfg['db_database'] = 'website';
// From this point you should not need to update anything.
$cfg['installed_apps'] = array('Pluf', 'IDF');
$cfg['pluf_use_rowpermission'] = true;
$cfg['middleware_classes'] = array(
'Pluf_Middleware_Session',
'IDF_Middleware',
'Pluf_Middleware_Translation',
);
$cfg['template_context_processors'] = array('IDF_Middleware_ContextPreProcessor');
$cfg['idf_views'] = dirname(__FILE__).'/views.php';
$cfg['template_tags'] = array(
'hotkey' => 'IDF_Template_HotKey',
'issuetext' => 'IDF_Template_IssueComment',
);
$cfg['template_modifiers'] = array(
'size' => 'IDF_Views_Source_PrettySize',
'markdown' => 'IDF_Template_Markdown_filter',
);
return $cfg;

View File

@@ -34,7 +34,20 @@ $ctl[] = array('regex' => '#^/login/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'login');
'method' => 'login',
'name' => 'login_view');
$ctl[] = array('regex' => '#^/preferences/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_User',
'method' => 'myAccount');
$ctl[] = array('regex' => '#^/u/(.*)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_User',
'method' => 'view');
$ctl[] = array('regex' => '#^/register/$#',
'base' => $base,
@@ -78,12 +91,24 @@ $ctl[] = array('regex' => '#^/p/(\w+)/issues/$#',
'model' => 'IDF_Views_Issue',
'method' => 'index');
$ctl[] = array('regex' => '#^/p/(\w+)/issues/search/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'search');
$ctl[] = array('regex' => '#^/p/(\w+)/issues/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'view');
$ctl[] = array('regex' => '#^/p/(\w+)/issues/(\d+)/star/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'star');
$ctl[] = array('regex' => '#^/p/(\w+)/issues/status/(\w+)/$#',
'base' => $base,
'priority' => 4,
@@ -108,7 +133,7 @@ $ctl[] = array('regex' => '#^/p/(\w+)/issues/my/(\w+)/$#',
'model' => 'IDF_Views_Issue',
'method' => 'myIssues');
// ---------- GIT ----------------------------------------
// ---------- SCM ----------------------------------------
$ctl[] = array('regex' => '#^/p/(\w+)/source/$#',
'base' => $base,
@@ -140,6 +165,62 @@ $ctl[] = array('regex' => '#^/p/(\w+)/source/commit/(\w+)/$#',
'model' => 'IDF_Views_Source',
'method' => 'commit');
$ctl[] = array('regex' => '#^/p/(\w+)/source/download/(\w+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source',
'method' => 'download');
$ctl[] = array('regex' => '#^/p/(\w+)/source/treerev/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source',
'method' => 'treeRev');
$ctl[] = array('regex' => '#^/p/(\w+)/source/changesrev/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source',
'method' => 'changelogRev');
// ---------- Downloads ------------------------------------
$ctl[] = array('regex' => '#^/p/(\w+)/downloads/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'index');
$ctl[] = array('regex' => '#^/p/(\w+)/downloads/label/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'listLabel');
$ctl[] = array('regex' => '#^/p/(\w+)/downloads/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'view');
$ctl[] = array('regex' => '#^/p/(\w+)/downloads/(\d+)/get/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'download');
$ctl[] = array('regex' => '#^/p/(\w+)/downloads/create/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'submit');
$ctl[] = array('regex' => '#^/p/(\w+)/downloads/(\d+)/delete/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'delete');
// ---------- ADMIN --------------------------------------
@@ -155,10 +236,22 @@ $ctl[] = array('regex' => '#^/p/(\w+)/admin/issues/$#',
'model' => 'IDF_Views_Project',
'method' => 'adminIssues');
$ctl[] = array('regex' => '#^/p/(\w+)/admin/downloads/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'adminDownloads');
$ctl[] = array('regex' => '#^/p/(\w+)/admin/members/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'adminMembers');
$ctl[] = array('regex' => '#^/p/(\w+)/admin/tabs/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'adminTabs');
return $ctl;

1546
src/IDF/locale/fr/idf.po Normal file

File diff suppressed because it is too large Load Diff

1483
src/IDF/locale/idf.pot Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -26,5 +26,8 @@ $m['IDF_Tag'] = array('relate_to' => array('IDF_Project'));
$m['IDF_Issue'] = array('relate_to' => array('IDF_Project', 'Pluf_User', 'IDF_Tag'),
'relate_to_many' => array('IDF_Tag', 'Pluf_User'));
$m['IDF_IssueComment'] = array('relate_to' => array('IDF_Issue', 'Pluf_User'));
$m['IDF_Upload'] = array('relate_to' => array('IDF_Project', 'Pluf_User'),
'relate_to_many' => array('IDF_Tag'));
$m['IDF_Search_Occ'] = array('relate_to' => array('IDF_Project'),);
return $m;

View File

@@ -4,6 +4,8 @@
<div id="sub-tabs">
<a {if $inSummary}class="active" {/if}href="{url 'IDF_Views_Project::admin', array($project.shortname)}">{trans 'Project Summary'}</a> |
<a {if $inMembers}class="active" {/if}href="{url 'IDF_Views_Project::adminMembers', array($project.shortname)}">{trans 'Project Members'}</a> |
<a {if $inIssueTracking}class="active" {/if}href="{url 'IDF_Views_Project::adminIssues', array($project.shortname)}">{trans 'Issue Tracking'}</a>
<a {if $inIssueTracking}class="active" {/if}href="{url 'IDF_Views_Project::adminIssues', array($project.shortname)}">{trans 'Issue Tracking'}</a> |
<a {if $inDownloads}class="active" {/if}href="{url 'IDF_Views_Project::adminDownloads', array($project.shortname)}">{trans 'Downloads'}</a> |
<a {if $inTabs}class="active" {/if}href="{url 'IDF_Views_Project::adminTabs', array($project.shortname)}">{trans 'Tabs Access'}</a>
</div>
{/block}

View File

@@ -0,0 +1,34 @@
{extends "admin/base.html"}
{block docclass}yui-t1{assign $inDownloads = true}{/block}
{block body}
<form method="post" action=".">
<table class="form" summary="">
<tr>
<td colspan="2"><strong>{$form.f.labels_download_predefined.labelTag}:</strong><br />
{if $form.f.labels_download_predefined.errors}{$form.f.labels_download_predefined.fieldErrors}{/if}
{$form.f.labels_download_predefined|unsafe}
</td>
</tr>
<tr>
<td colspan="2">{$form.f.labels_download_one_max.labelTag}:<br />
{if $form.f.labels_download_one_max.errors}{$form.f.labels_download_one_max.fieldErrors}{/if}
{$form.f.labels_download_one_max|unsafe}
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="{trans 'Save Changes'}" name="submit" />
</td>
</tr>
</table>
</form>
{/block}
{block context}
<div class="issue-submit-info">
{blocktrans}
<p><strong>Instructions:</strong></p>
<p>List one status value per line in desired sort-order.</p>
<p>Optionally, use an equals-sign to document the meaning of each status value.</p>
{/blocktrans}
</div>
{/block}

View File

@@ -0,0 +1,45 @@
{extends "admin/base.html"}
{block docclass}yui-t1{assign $inTabs = true}{/block}
{block body}
{if $form.errors}
<div class="px-message-error">
<p>{trans 'The form contains some errors. Please correct them to update the access rights.'}</p>
{if $form.get_top_errors}
{$form.render_top_errors|unsafe}
{/if}
</div>
{/if}
<form method="post" action=".">
<table class="form" summary="">
<tr>
<th><strong>{$form.f.downloads_access_rights.labelTag}:</strong></th>
<td>{if $form.f.downloads_access_rights.errors}{$form.f.downloads_access_rights.fieldErrors}{/if}
{$form.f.downloads_access_rights|unsafe}
</td>
</tr>
<tr>
<th><strong>{$form.f.issues_access_rights.labelTag}:</strong></th>
<td>{if $form.f.issues_access_rights.errors}{$form.f.issues_access_rights.fieldErrors}{/if}
{$form.f.issues_access_rights|unsafe}
</td>
</tr>
<tr>
<th><strong>{$form.f.source_access_rights.labelTag}:</strong></th>
<td>{if $form.f.source_access_rights.errors}{$form.f.source_access_rights.fieldErrors}{/if}
{$form.f.source_access_rights|unsafe}
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="{trans 'Save Changes'}" name="submit" />
</td>
</tr>
</table>
</form>
{/block}
{block context}
<div class="issue-submit-info">
<p><strong>{trans 'Instructions:'}</strong></p>
<p>{blocktrans}You can configure here the project tabs access rights.{/blocktrans}</p>
</div>
{/block}

View File

@@ -33,15 +33,15 @@
<body>
<div id="{block docid}doc3{/block}" class="{block docclass}yui-t3{/block}">
<div id="hd">
<p class="top">
{if !$user.isAnonymous()}{blocktrans}Welcome, <strong>{$user.first_name} {$user.last_name}</strong>.{/blocktrans} <a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a>{else}<a href="{url 'IDF_Views::login'}">{trans 'Sign in or create your account'}</a>{/if}
<p class="top"><a href="#title" accesskey="2"></a>
{if !$user.isAnonymous()}{aurl 'url', 'IDF_Views_User::myAccount'}{blocktrans}Welcome, <strong><a class="userw" href="{$url}">{$user}</a></strong>.{/blocktrans} <a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a>{else}<a href="{url 'IDF_Views::login'}">{trans 'Sign in or create your account'}</a>{/if}
| <a href="{url 'IDF_Views::faq'}">{trans 'Help'}</a>
</p>
{* <div id="header">
</div> *}
<h1 class="title">{block title}{$page_title}{/block}</h1>
<h1 id="title" class="title">{block title}{$page_title}{/block}</h1>
</div>
<div id="bd">
@@ -55,7 +55,7 @@
</div>
<div class="yui-b context">{block context}{/block}</div>
</div>
<div id="ft">{block foot}{/block}</div>
<div id="ft">{block foot}<a href="http://www.indefero.net" title="InDefero, bug tracking and more"><img src="{media '/idf/img/powered-by-indefero.png'}" alt="InDefero Logo" /></a>{/block}</div>
</div>
<script type="text/javascript" src="{media '/idf/js/jquery-1.2.6.min.js'}"></script>
{block javascript}{/block}

View File

@@ -34,23 +34,24 @@
<div id="{block docid}doc3{/block}" class="{block docclass}yui-t3{/block}">
<div id="hd">
{if $project}<h1 class="project-title">{$project}</h1>{/if}
<p class="top">
{if !$user.isAnonymous()}{blocktrans}Welcome, <strong>{$user.first_name} {$user.last_name}</strong>.{/blocktrans} <a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a>{else}<a href="{url 'IDF_Views::login'}">{trans 'Sign in or create your account'}</a>{/if}
<p class="top"><a href="#title" accesskey="2"></a>
{if !$user.isAnonymous()}{aurl 'url', 'IDF_Views_User::myAccount'}{blocktrans}Welcome, <strong><a class="userw" href="{$url}">{$user}</a></strong>.{/blocktrans} <a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a>{else}<a href="{url 'IDF_Views::login'}">{trans 'Sign in or create your account'}</a>{/if}
| <a href="{url 'IDF_Views::faq'}">{trans 'Help'}</a>
</p>
<div id="header">
<div id="main-tabs">
{if $project}
<a href="{url 'IDF_Views_Project::home', array($project.shortname)}"{block tabhome}{/block}>{trans 'Project Home'}</a>
<a href="{url 'IDF_Views_Issue::index', array($project.shortname)}"{block tabissues}{/block}>{trans 'Issues'}</a>
<a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, 'master')}"{block tabsource}{/block}>{trans 'Source'}</a>
<a accesskey="1" href="{url 'IDF_Views_Project::home', array($project.shortname)}"{block tabhome}{/block}>{trans 'Project Home'}</a>
{if $hasIssuesAccess} <a href="{url 'IDF_Views_Issue::index', array($project.shortname)}"{block tabissues}{/block}>{trans 'Issues'}</a>{/if}
{if $hasDownloadsAccess} <a href="{url 'IDF_Views_Download::index', array($project.shortname)}"{block tabdownloads}{/block}>{trans 'Downloads'}</a>{/if}
{if $hasSourceAccess} <a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}"{block tabsource}{/block}>{trans 'Source'}</a>{/if}
{if $isOwner}
<a href="{url 'IDF_Views_Project::admin', array($project.shortname)}"{block tabadmin}{/block}>{trans 'Administer'}</a>{/if}{/if}
</div>
{block subtabs}{if $user.isAnonymous()} | {aurl 'url', 'IDF_Views::login'}{blocktrans}<a href="{$url}">Sign in or create your account</a> to create issues or add comments{/blocktrans}{/if}{/block}
</div>
<h1 class="title">{block title}{$page_title}{/block}</h1>
<h1 class="title" id="title">{block titleicon}{/block}{block title}{$page_title}{/block}</h1>
</div>
<div id="bd">
@@ -64,7 +65,7 @@
</div>
<div class="yui-b context">{block context}{/block}</div>
</div>
<div id="ft">{block foot}{/block}</div>
<div id="ft">{block foot}<a href="http://www.indefero.net" title="InDefero, bug tracking and more"><img src="{media '/idf/img/powered-by-indefero.png'}" alt="InDefero Logo" /></a>{/block}</div>
</div>
<script type="text/javascript" src="{media '/idf/js/jquery-1.2.6.min.js'}"></script>
{include 'js-hotkeys.html'}

View File

@@ -0,0 +1,7 @@
{extends "base.html"}
{block tabdownloads} class="active"{/block}
{block subtabs}
<div id="sub-tabs">
<a {if $inDownloads}class="active" {/if}href="{url 'IDF_Views_Download::index', array($project.shortname)}">{trans 'Downloads'}</a> {if $isOwner or $isMember}| <a {if $inSubmit}class="active" {/if}href="{url 'IDF_Views_Download::submit', array($project.shortname)}">{trans 'New Download'}</a> {/if}
</div>
{/block}

View File

@@ -0,0 +1,40 @@
{extends "downloads/base.html"}
{block docclass}yui-t3{assign $inDownloads=true}{/block}
{block body}
<div class="download-file">
<a href="{url 'IDF_Views_Download::download', array($project.shortname, $file.id)}">{$file}</a> - {$file.filesize|size}
</div>
<p>{blocktrans}<strong>Attention!</strong> If you want to delete a specific version of your software, maybe, someone is depending on this specific version to run his systems. Are you sure, you will not affect anybody when removing this file?{/blocktrans}</p>
{if !$deprecated}{aurl 'url', 'IDF_Views_Download::view', array($project.shortname, $file.id)}
<p>{blocktrans}Instead of deleting the file, you could <a href="{$url}">mark it as deprecated</a>.{/blocktrans}</p>{/if}
<form method="post" action=".">
<table class="form" summary="">
<tr>
<td>&nbsp;</td>
<td><input type="submit" value="{trans 'Delete File'}" name="submit" /> | <a href="{url 'IDF_Views_Download::index', array($project.shortname)}">{trans 'Cancel'}</a>
</td>
</tr>
</table>
</form>
{/block}
{block context}
{assign $submitter = $file.get_submitter()}
<p><strong>{trans 'Uploaded:'}</strong> <span class="nobrk">{$file.creation_dtime|dateago}</span> <span class="nobrk">{blocktrans}by {$submitter}{/blocktrans}</span></p>
{if $file.modif_dtime != $file.creation_dtime}<p>
<strong>{trans 'Updated:'}</strong> <span class="nobrk">{$file.modif_dtime|dateago}</span></p>{/if}
<p>
<strong>{trans 'Downloads:'}</strong> <span class="nobrk">{$file.downloads}</span></p>
{if $tags.count()}
<p>
<strong>{trans 'Labels:'}</strong><br />
{foreach $tags as $tag}{aurl 'url', 'IDF_Views_Download::listLabel', array($project.shortname, $tag.id)}
<span class="label"><a href="{$url}" class="label"><strong>{$tag.class}:</strong>{$tag.name}</a></span><br />
{/foreach}
</p>{/if}
{/block}

View File

@@ -0,0 +1,19 @@
{extends "downloads/base.html"}
{block docclass}yui-t1{assign $inDownloads=true}{/block}
{block body}
{$downloads.render}
{if $isOwner or $isMember}
{aurl 'url', 'IDF_Views_Download::submit', array($project.shortname)}
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'New Download'}</a></p>{/if}
{/block}
{block context}
<p><strong>{trans 'Number of files:'}</strong> {$downloads.nb_items}</p>
{assign $class = ''}{assign $i = 0}
<p class="smaller">{foreach $tags as $label}
{aurl 'url', 'IDF_Views_Download::listLabel', array($project.shortname, $label.id)}
{if $class != $label.class}{if $i != 0}<br />{/if}<strong class="label">{$label.class}:</strong> {/if}
<a href="{$url}" class="label">{$label.name}</a>,{assign $i = $i + 1}{assign $class = $label.class}{/foreach}</p>
{/block}

View File

@@ -0,0 +1,27 @@
{if $isOwner or $isMember}
<script type="text/javascript" src="{media '/idf/js/jquery.bgiframe.min.js'}"></script>
<script type="text/javascript" src="{media '/idf/js/jquery.autocomplete.min.js'}"></script>
<script type="text/javascript" charset="utf-8">
// <!-- {literal}
$(document).ready(function(){
var auto_labels = [{/literal}{$auto_labels|safe}{literal}];
var j=0;
for (j=1;j<7;j=j+1) {
$("#id_label"+j).autocomplete(auto_labels, {
minChars: 0,
width: 310,
matchContains: true,
max: 50,
highlightItem: false,
formatItem: function(row, i, max, term) {
return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + " <span style='font-size: 80%;'>" + row.name + "</span>";
},
formatResult: function(row) {
return row.to;
}
});
}
});
{/literal} //-->
</script>
{/if}

View File

@@ -0,0 +1,60 @@
{extends "downloads/base.html"}
{block docclass}yui-t3{assign $inSubmit=true}{/block}
{block body}
{if $form.errors}
<div class="px-message-error">
<p>{trans 'The form contains some errors. Please correct them to submit the file.'}</p>
{if $form.get_top_errors}
{$form.render_top_errors|unsafe}
{/if}
</div>
{/if}
<form method="post" enctype="multipart/form-data" action=".">
<table class="form" summary="">
<tr>
<th><strong>{$form.f.summary.labelTag}:</strong></th>
<td>{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if}
{$form.f.summary|unsafe}
</td>
</tr>
<tr>
<th><strong>{$form.f.file.labelTag}:</strong></th>
<td>{if $form.f.file.errors}{$form.f.file.fieldErrors}{/if}
{$form.f.file|unsafe}
</td>
</tr>
<tr>
<th>{$form.f.label1.labelTag}:</th>
<td>
{if $form.f.label1.errors}{$form.f.label1.fieldErrors}{/if}{$form.f.label1|unsafe}
{if $form.f.label2.errors}{$form.f.label2.fieldErrors}{/if}{$form.f.label2|unsafe}
{if $form.f.label3.errors}{$form.f.label3.fieldErrors}{/if}{$form.f.label3|unsafe}<br />
{if $form.f.label4.errors}{$form.f.label4.fieldErrors}{/if}{$form.f.label4|unsafe}
{if $form.f.label5.errors}{$form.f.label5.fieldErrors}{/if}{$form.f.label5|unsafe}
{if $form.f.label6.errors}{$form.f.label6.fieldErrors}{/if}{$form.f.label6|unsafe}
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td><input type="submit" value="{trans 'Submit File'}" name="submit" /> | <a href="{url 'IDF_Views_Download::index', array($project.shortname)}">{trans 'Cancel'}</a>
</td>
</tr>
</table>
</form>
{/block}
{block context}
<div class="issue-submit-info">
<h2>{trans 'Instructions'}</h2>
<p>{blocktrans}Each file must have a distinct name and file contents
cannot be changed, so be sure to include release numbers in each file
name.{/blocktrans}</p>
</div>
{/block}
{block javascript}
<script type="text/javascript">
document.getElementById('id_summary').focus()
</script>
{include 'downloads/js-autocomplete.html'}{/block}

View File

@@ -0,0 +1,69 @@
{extends "downloads/base.html"}
{block docclass}yui-t3{assign $inDownloads=true}{/block}
{block body}
<div class="download-file">
{if $deprecated}<p class="smaller">{blocktrans}<strong>Attention!</strong> This file is marked as deprecated, download it only if you are sure you need this specific version.{/blocktrans}</p>{/if}
<a href="{url 'IDF_Views_Download::download', array($project.shortname, $file.id)}">{$file}</a> - {$file.filesize|size}
</div>
{if $form}
{if $form.errors}
<div class="px-message-error">
<p>{trans 'The form contains some errors. Please correct them to update the file.'}</p>
{if $form.get_top_errors}
{$form.render_top_errors|unsafe}
{/if}
</div>
{/if}
<form method="post" enctype="multipart/form-data" action=".">
<table class="form" summary="">
<tr>
<th><strong>{$form.f.summary.labelTag}:</strong></th>
<td>{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if}
{$form.f.summary|unsafe}
</td>
</tr>
<tr>
<th>{$form.f.label1.labelTag}:</th>
<td>
{if $form.f.label1.errors}{$form.f.label1.fieldErrors}{/if}{$form.f.label1|unsafe}
{if $form.f.label2.errors}{$form.f.label2.fieldErrors}{/if}{$form.f.label2|unsafe}
{if $form.f.label3.errors}{$form.f.label3.fieldErrors}{/if}{$form.f.label3|unsafe}<br />
{if $form.f.label4.errors}{$form.f.label4.fieldErrors}{/if}{$form.f.label4|unsafe}
{if $form.f.label5.errors}{$form.f.label5.fieldErrors}{/if}{$form.f.label5|unsafe}
{if $form.f.label6.errors}{$form.f.label6.fieldErrors}{/if}{$form.f.label6|unsafe}
</td>
</tr>
<tr>
<td>&nbsp;</td>{aurl 'url', 'IDF_Views_Download::delete', array($project.shortname, $file.id)}
<td><input type="submit" value="{trans 'Update File'}" name="submit" /> | <a href="{url 'IDF_Views_Download::index', array($project.shortname)}">{trans 'Cancel'}</a> <span class="dellink"><a href="{$url}" title="{trans 'Remove this file'}"><img src="{media '/idf/img/trash.png'}" style="vertical-align: text-bottom;" alt="{trans 'Trash'}" /></a> <a href="{$url}" title="{trans 'Remove this file'}">Delete this file</a></span>
</td>
</tr>
</table>
</form>
{/if}
{/block}
{block context}
{assign $submitter = $file.get_submitter()}
<p><strong>{trans 'Uploaded:'}</strong> <span class="nobrk">{$file.creation_dtime|dateago}</span> <span class="nobrk">{blocktrans}by {$submitter}{/blocktrans}</span></p>
{if $file.modif_dtime != $file.creation_dtime}<p>
<strong>{trans 'Updated:'}</strong> <span class="nobrk">{$file.modif_dtime|dateago}</span></p>{/if}
<p>
<strong>{trans 'Downloads:'}</strong> <span class="nobrk">{$file.downloads}</span></p>
{if $tags.count()}
<p>
<strong>{trans 'Labels:'}</strong><br />
{foreach $tags as $tag}{aurl 'url', 'IDF_Views_Download::listLabel', array($project.shortname, $tag.id)}
<span class="label"><a href="{$url}" class="label"><strong>{$tag.class}:</strong>{$tag.name}</a></span><br />
{/foreach}
</p>{/if}
{/block}
{block javascript}{if $form}
<script type="text/javascript">
document.getElementById('id_summary').focus()
</script>
{include 'downloads/js-autocomplete.html'}{/if}{/block}

View File

@@ -0,0 +1,47 @@
{extends "base-simple.html"}
{block docclass}yui-t3{/block}
{block body}
<ul>
<li><a href="#q-keyboard">{trans 'What are the keyboard shortcuts?'}</a></li>
<li><a href="#q-duplicate">{trans 'How to mark an issue as duplicate?'}</a></li>
</ul>
<h2 id="q-keyboard">{trans 'What are the keyboard shortcuts?'}</h2>
<p>{trans 'If you are in a project, you have the following shortcuts:'}</p>
<ul>
<li>{trans '<kbd>Shift+h</kbd>: Project home.'}</li>
<li>{trans '<kbd>Shift+d</kbd>: Downloads.'}</li>
<li>{trans '<kbd>Shift+s</kbd>: Source.'}</li>
<li>{trans '<kbd>Shift+a</kbd>: Create a new issue.'}</li>
<li>{trans '<kbd>Shift+i</kbd>: List of open issues.'}</li>
<li>{trans '<kbd>Shift+m</kbd>: The issues you submitted.'}</li>
<li>{trans '<kbd>Shift+w</kbd>: The issues assigned to you.'}</li>
</ul>
<p>{trans 'You also have the standard access keys:'}</p>
<ul>
<li>{trans '<kbd>Alt+1</kbd>: Home.'}</li>
<li>{trans '<kbd>Alt+2</kbd>: Skip the menus.'}</li>
<li>{trans '<kbd>Alt+4</kbd>: Search (when available).'}</li>
</ul>
<h2 id="q-duplicate">{trans 'How to mark an issue as duplicate?'}</h2>
{blocktrans}<p>This is simple:</p>
<ol>
<li>Write in the comments "This is a duplicate of issue 123", change 123 with the corresponding issue number.</li>
<li>Change the status of the current issue to <em>Duplicate</em>.</li>
<li>Submit the changes.</li>
</ol>{/blocktrans}
{/block}
{block context}
<p>{trans 'Here we are, just to help you.'}</p>
<h2>{trans 'Projects'}</h2>
<ul>{foreach $projects as $p}
<li><a href="{url 'IDF_Views_Project::home', array($p.shortname)}">{$p}</a></li>
{/foreach}</ul>
{/block}

View File

@@ -2,8 +2,12 @@
{block tabissues} class="active"{/block}
{block subtabs}
<div id="sub-tabs">
<a href="{url 'IDF_Views_Issue::index', array($project.shortname)}">{trans 'Recent issues'}</a>
{if !$user.isAnonymous()} | <a href="{url 'IDF_Views_Issue::create', array($project.shortname)}">{trans 'New Issue'}</a> | <a href="{url 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}">{trans 'My Issues'}</a>{/if}
<a {if $inOpenIssues}class="active" {/if}href="{url 'IDF_Views_Issue::index', array($project.shortname)}">{trans 'Open issues'}</a>
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Issue::create', array($project.shortname)}">{trans 'New Issue'}</a> | <a {if $inMyIssues}class="active" {/if}href="{url 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}">{trans 'My Issues'}</a>{/if} |
<form class="star" action="{url 'IDF_Views_Issue::search', array($project.shortname)}" method="get">
<input accesskey="4" type="text" value="{$q}" name="q" size="20" />
<input type="submit" name="s" value="{trans 'Search'}" />
</form>
{superblock}
</div>
{/block}

View File

@@ -4,7 +4,7 @@
{$issues.render}
{if !$user.isAnonymous()}
{aurl 'url', 'IDF_Views_Issue::create', array($project.shortname)}
<p><a href="{$url}">{trans 'New Issue'}</a></p>{/if}
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'New Issue'}</a></p>{/if}
{/block}
{block context}

View File

@@ -1,4 +1,5 @@
{extends "issues/base.html"}
{block docclass}yui-t3{assign $inCreate = true}{/block}
{block body}
{if $form.errors}
<div class="px-message-error">
@@ -60,7 +61,7 @@
<ul>
<li>The steps to reproduce the problem.</li>
<li>The version of the software and your operating system.</li>
<li>Any information that can help the developpers to solve the issue.</li>
<li>Any information that can help the developers to solve the issue.</li>
<li><strong>Do not provide any password or confidential information!</strong></li>
</ul>{/blocktrans}
</div>

View File

@@ -4,7 +4,7 @@
{$issues.render}
{if !$user.isAnonymous()}
{aurl 'url', 'IDF_Views_Issue::create', array($project.shortname)}
<p><a href="{$url}">{trans 'New Issue'}</a></p>{/if}
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'New Issue'}</a></p>{/if}
{/block}
{block context}
@@ -12,9 +12,9 @@
{aurl 'closed_url', 'IDF_Views_Issue::listStatus', array($project.shortname, 'closed')}
{blocktrans}<p><strong>Open issues:</strong> <a href="{$open_url}">{$open}</a></p>
<p><strong>Closed issues:</strong> <a href="{$closed_url}">{$closed}</a></p>{/blocktrans}
<p class="smaller">{foreach $project.getTagCloud() as $label}
{assign $class = ''}{assign $i = 0}
<p class="smaller">{foreach $project.getTagCloud($cloud) as $label}
{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')}
<a href="{$url}" class="label"><strong>{$label.class}:</strong>{$label.name}</a>{/foreach}</p>
{if $class != $label.class}{if $i != 0}<br />{/if}<strong class="label">{$label.class}:</strong> {/if}
<a href="{$url}" class="label">{$label.name}</a>,{assign $class = $label.class}{assign $i = $i + 1}{/foreach}</p>
{/block}

View File

@@ -0,0 +1,18 @@
{trans 'Hello,'}
{blocktrans}A new issue has been created and assigned
to you:{/blocktrans}
{$issue.id} - {$issue.summary|safe}
{trans 'Project:'} {$project.name|safe}
{trans 'Status:'} {$issue.get_status.name|safe}
{trans 'Reported by:'} {$issue.get_submitter|safe}
{assign $tags = $issue.get_tags_list()}{if $tags.count()}{trans 'Labels:'}
{foreach $tags as $tag} {$tag.class|safe}:{$tag.name|safe}
{/foreach}{/if}
{trans 'Description:'}
{$comment.content|safe}
--
{trans 'Issue:'} {$url_base}{url 'IDF_Views_Issue::view', array($project.shortname, $issue.id)}

View File

@@ -0,0 +1,24 @@
{trans 'Hello,'}
{blocktrans}The following issue has been updated:{/blocktrans}
{$issue.id} - {$issue.summary|safe}
{trans 'Project:'} {$project.name|safe}
{trans 'Status:'} {$issue.get_status.name}
{trans 'Reported by:'} {$issue.get_submitter|safe}
{trans 'URL:'} {$url_base}{url 'IDF_Views_Issue::view', array($project.shortname, $issue.id)}
{assign $tags = $issue.get_tags_list()}{if $tags.count()}{trans 'Labels:'}
{foreach $tags as $tag} {$tag.class|safe}:{$tag.name|safe}
{/foreach}
{/if}{trans 'Comments (last first):'}
{foreach $comments as $c}{assign $who = $c.get_submitter()}# {blocktrans}By {$who|safe}, {$c.creation_dtime|date}:{/blocktrans}
{if strlen($c.content) > 0}{$c.content|safe}{else}{trans '(No comments were given for this change.)'}{/if}{if $c.changedIssue()}
{foreach $c.changes as $w => $v}
{if $w == 'su'}{trans 'Summary:'}{/if}{if $w == 'st'}{trans 'Status:'}{/if}{if $w == 'ow'}{trans 'Owner:'}{/if}{if $w == 'lb'}{trans 'Labels:'}{/if} {if $w == 'lb'}{assign $l = implode(', ', $v)}{$l}{else}{$v}{/if}{/foreach}{/if}
{/foreach}
--
{trans 'Issue:'} {$url_base}{url 'IDF_Views_Issue::view', array($project.shortname, $issue.id)}

View File

@@ -1,10 +1,10 @@
{extends "issues/base.html"}
{block docclass}yui-t2{/block}
{block docclass}yui-t2{assign $inMyIssues = true}{/block}
{block body}
{$issues.render}
{if !$user.isAnonymous()}
{aurl 'url', 'IDF_Views_Issue::create', array($project.shortname)}
<p><a href="{$url}">{trans 'New Issue'}</a></p>{/if}
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'New Issue'}</a></p>{/if}
{/block}
{block context}

View File

@@ -0,0 +1,12 @@
{extends "issues/base.html"}
{block docclass}yui-t2{/block}
{block body}
{$issues.render}
{if !$user.isAnonymous()}
{aurl 'url', 'IDF_Views_Issue::create', array($project.shortname)}
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'New Issue'}</a></p>{/if}
{/block}
{block context}
<p><strong>{trans 'Found issues:'}</strong> {$issues.nb_items}</p>
{/block}

View File

@@ -1,19 +1,20 @@
{extends "issues/base.html"}
{block titleicon}{if $form}<form class="star" method="post" action="{url 'IDF_Views_Issue::star', array($project.shortname, $issue.id)}"><input type="image" src="{if $starred}{media '/idf/img/star.png'}{else}{media '/idf/img/star-grey.png'}{/if}" name="submit" /></form> {/if}{/block}
{block body}
{assign $i = 0}
{assign $nc = $comments.count()}
{foreach $comments as $c}
<div class="issue-comment{if $i == 0} issue-comment-first{/if}{if $i == ($nc-1)} issue-comment-last{/if}" id="ic{$c.id}">
{if $i == 0}{assign $who = $issue.get_submitter()}
<p>{blocktrans}Reported by {$who}, {$c.creation_dtime|date}{/blocktrans}</p>
{else}{assign $who = $c.get_submitter()}
<div class="issue-comment{if $i == 0} issue-comment-first{/if}{if $i == ($nc-1)} issue-comment-last{/if}" id="ic{$c.id}">{assign $who = $c.get_submitter()}{aurl 'whourl', 'IDF_Views_User::view', array($who.login)}
{if $i == 0}
<p>{blocktrans}Reported by <a href="{$whourl}">{$who}</a>, {$c.creation_dtime|date}{/blocktrans}</p>
{else}
{aurl 'url', 'IDF_Views_Issue::view', array($project.shortname, $issue.id)}
{assign $id = $c.id}
{assign $url = $url~'#ic'~$c.id}
<p>{blocktrans}Comment <a href="{$url}">{$i}</a> by {$who}, {$c.creation_dtime|date}{/blocktrans}</p>
<p>{blocktrans}Comment <a href="{$url}">{$i}</a> by <a href="{$whourl}">{$who}</a>, {$c.creation_dtime|date}{/blocktrans}</p>
{/if}
<pre class="issue-comment-text">{if strlen($c.content) > 0}{issuetext $c.content, $project}{else}<i>{trans '(No comments were given for this change.)'}</i>{/if}</pre>
<pre class="issue-comment-text">{if strlen($c.content) > 0}{issuetext $c.content, $request}{else}<i>{trans '(No comments were given for this change.)'}</i>{/if}</pre>
{if $i> 0 and $c.changedIssue()}
<div class="issue-changes">
@@ -41,6 +42,10 @@
</div>
{/if}
{if $closed and (!$isOwner and !$isMember)}
<p><img src="{media '/idf/img/warning.png'}" style="vertical-align: text-bottom;" alt=" " /> {blocktrans}This issue is marked as closed, add a comment only if you think this issue is still valid and more work is needed to fully fix it.{/blocktrans}</p>
{/if}
<form method="post" enctype="multipart/form-data" action="{url 'IDF_Views_Issue::view', array($project.shortname, $issue.id)}" >
<table class="form" summary="">
<tr>
@@ -90,18 +95,18 @@
{/block}
{block context}
<div class="issue-info">
{assign $submitter = $issue.get_submitter()}
<p><strong>{trans 'Created:'}</strong> <span class="nobrk">{$issue.creation_dtime|dateago}</span> <span class="nobrk">{blocktrans}by {$submitter}{/blocktrans}</span></p>
<p>
<strong>{trans 'Updated:'}</strong> <span class="nobrk">{$issue.modif_dtime|dateago}</span></p>
{assign $submitter = $issue.get_submitter()}{aurl 'url', 'IDF_Views_User::view', array($submitter.login)}
<p><strong>{trans 'Created:'}</strong> <span class="nobrk">{$issue.creation_dtime|dateago}</span> <span class="nobrk">{blocktrans}by <a href="{$url}">{$submitter}</a>{/blocktrans}</span></p>
{if $issue.modif_dtime != $issue.creation_dtime}<p>
<strong>{trans 'Updated:'}</strong> <span class="nobrk">{$issue.modif_dtime|dateago}</span></p>{/if}
<p>
<strong>{trans 'Status:'}</strong> {$issue.get_status.name}</p>
<p>
<strong>{trans 'Owner:'}</strong> {if $issue.get_owner == null}{trans 'No owner'}{else}{$issue.get_owner}{/if}
</p>{assign $tags = $issue.get_tags_list()}{if $tags.count()}
{if $issue.get_owner != null}<p>{aurl 'url', 'IDF_Views_User::view', array($issue.get_owner().login)}
<strong>{trans 'Owner:'}</strong> <a href="{$url}">{$issue.get_owner}</a>
</p>{/if}{assign $tags = $issue.get_tags_list()}{if $tags.count()}
<p>
<strong>{trans 'Labels:'}</strong><br />
{foreach $issue.get_tags_list() as $tag}{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $tag.id, 'open')}
{foreach $tags as $tag}{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $tag.id, 'open')}
<span class="label"><a href="{$url}" class="label"><strong>{$tag.class}:</strong>{$tag.name}</a></span><br />
{/foreach}
</p>{/if}

View File

@@ -2,9 +2,12 @@
{if $project}
<script type="text/javascript" charset="utf-8">
// <!--
{hotkey 'Shift+a', 'IDF_Views_Issue::create', array($project.shortname)}
{hotkey 'Shift+r', 'IDF_Views_Issue::index', array($project.shortname)}
{if !$user.isAnonymous()}
{hotkey 'Shift+h', 'IDF_Views_Project::home', array($project.shortname)}
{if $hasIssuesAccess}{hotkey 'Shift+a', 'IDF_Views_Issue::create', array($project.shortname)}
{hotkey 'Shift+i', 'IDF_Views_Issue::index', array($project.shortname)}{/if}
{if $hasDownloadsAccess}{hotkey 'Shift+d', 'IDF_Views_Download::index', array($project.shortname)}{/if}
{if $hasSourceAccess}{hotkey 'Shift+s', 'IDF_Views_Source::treeBase', array($project.shortname, 'master')}{/if}
{if $hasIssuesAccess and !$user.isAnonymous()}
{hotkey 'Shift+m', 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}
{hotkey 'Shift+w', 'IDF_Views_Issue::myIssues', array($project.shortname, 'owner')}
{/if} //-->

View File

@@ -1,4 +1,5 @@
{extends "base.html"}
{block docclass}yui-t2{/block}
{block tabhome} class="active"{/block}
{block subtabs}
<div id="sub-tabs">
@@ -6,21 +7,27 @@
</div>
{/block}
{block body}
{$project.description}
{$project.description|markdown}
{/block}
{block context}
{if count($downloads) > 0}
<p><strong>{trans 'Featured Downloads'}</strong><br />
{foreach $downloads as $download}
<span class="label"><a href="{url 'IDF_Views_Download::view', array($project.shortname, $download.id)}" title="{$download.summary}">{$download}</a></span><br />
{/foreach}
<span class="label"> </span><span class="note"><a href="{url 'IDF_Views_Download::index', array($project.shortname)}">{trans 'show more...'}</a></span>
{/if}
{assign $ko = 'owners'}
{assign $km = 'members'}
<p><strong>{trans 'Development Team'}</strong><br />
{trans 'Admins'}<br />
{foreach $team[$ko] as $owner}
<span class="label">{$owner}</span><br />
{foreach $team[$ko] as $owner}{aurl 'url', 'IDF_Views_User::view', array($owner.login)}
<span class="label"><a class="label" href="{$url}">{$owner}</a></span><br />
{/foreach}
{if count($team[$km]) > 0}
{trans 'Happy Crew'}<br />
{foreach $team[$km] as $member}
<span class="label">{$member}</span><br />
{foreach $team[$km] as $member}{aurl 'url', 'IDF_Views_User::view', array($member.login)}
<span class="label"><a class="label" href="{$url}">{$member}</a></span><br />
{/foreach}
{/if}
</p>

View File

@@ -2,8 +2,8 @@
{block tabsource} class="active"{/block}
{block subtabs}
<div id="sub-tabs">
<a {if $inSourceTree}class="active" {/if}href="{url 'IDF_Views_Source::treeBase', array($project.shortname, 'master')}">{trans 'Source Tree'}</a> |
<a {if $inChangeLog}class="active" {/if}href="{url 'IDF_Views_Source::changeLog', array($project.shortname, 'master')}">{trans 'Change Log'}</a>
<a {if $inSourceTree}class="active" {/if}href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}">{trans 'Source Tree'}</a> |
<a {if $inChangeLog}class="active" {/if}href="{url 'IDF_Views_Source::changeLog', array($project.shortname, $project.getScmRoot())}">{trans 'Change Log'}</a>
{if $inCommit}| {trans 'Commit'}{/if}
</div>
{/block}

View File

@@ -6,7 +6,6 @@
<tr>
<th>{trans 'Age'}</th>
<th>{trans 'Message'}</th>
{* <th>{trans 'Details'}</th> *}
</tr>
</thead>
<tbody>
@@ -14,7 +13,7 @@
{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $change.commit)}
<tr class="log">
<td><a href="{$url}">{$change.date|dateago:"wihtout"}</a></td>
<td>{$change.title}{if $change.full_message}<br /><br />{$change.full_message}{/if}</td>
<td>{issuetext $change.title, $request}{if $change.full_message}<br /><br />{issuetext $change.full_message, $request}{/if}</td>
</tr>
<tr class="extra">
<td colspan="2">
@@ -29,11 +28,23 @@
</table>
{/block}
{block context}
{if $scm == 'git'}
<p><strong>{trans 'Branches:'}</strong><br />
{foreach $branches as $branch}
{aurl 'url', 'IDF_Views_Source::changeLog', array($project.shortname, $branch)}
<span class="label{if $commit == $branch} active{/if}"><a href="{$url}" class="label">{$branch}</a></span><br />
{/foreach}
</p>
{/if}
{if $scm == 'svn'}
<form class="star" action="{url 'IDF_Views_Source::changelogRev', array($project.shortname)}" method="get">
<p><strong>{trans 'Revison:'}</strong> {$commit}</p>
<p>
<input accesskey="4" type="text" value="{$commit}" name="rev" size="5" />
<input type="submit" name="s" value="{trans 'Go to revision'}" />
</p>
</form>
{/if}
{/block}

View File

@@ -15,8 +15,9 @@
<th><strong>{trans 'Tree:'}</strong></th><td class="mono"><a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $commit)}" title="{trans 'View corresponding source tree'}">{$cobject.tree}</a><br /><br /></td>
</tr>
<tr>
<th><strong>{trans 'Message:'}</strong></th><td>{issuetext $cobject.title, $project}{if isset($cobject.full_message)}<br /><br />{issuetext $cobject.full_message, $project}{/if}</td>
<th><strong>{trans 'Message:'}</strong></th><td>{issuetext $cobject.title, $request}{if isset($cobject.full_message)}<br /><br />{issuetext $cobject.full_message, $request}{/if}</td>
</tr>
{if count($diff.files)}
<tr>
<th><strong>{trans 'Files:'}</strong></th>
<td>
@@ -26,18 +27,22 @@
{/foreach}
</td>
</tr>
{/if}
</table>
{if count($diff.files)}
<h2>{trans 'Change Details'}</h2>
{$diff.as_html()|safe}
{/if}
{/block}
{block context}
{if $scm == 'git'}
<p><strong>{trans 'Branches:'}</strong><br />
{foreach $branches as $branch}
{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $branch)}
<span class="label{if $commit == $branch} active{/if}"><a href="{$url}" class="label">{$branch}</a></span><br />
{/foreach}
</p>
{/if}
{/block}

View File

@@ -2,6 +2,7 @@
{block docclass}yui-t1{assign $inSourceTree=true}{/block}
{block body}
<h2><a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $commit)}">{trans 'Root'}</a><span class="sep">/</span>{if $breadcrumb}{$breadcrumb|safe}{/if}</h2>
<table summary="" class="tree-list">
<thead>
<tr>
@@ -41,6 +42,9 @@
{/foreach}
</tbody>
</table>
{aurl 'url', 'IDF_Views_Source::download', array($project.shortname, $commit)}
<p class="right soft"><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/package-grey.png'}" alt="{trans 'Archive'}" align="bottom" /></a> <a href="{$url}">{trans 'Download this version'}</a> {trans 'or'} <kbd>git clone {$project.getRemoteAccessUrl()}</kbd></p>
{/block}
{block context}

View File

@@ -0,0 +1,72 @@
{extends "source/base.html"}
{block docclass}yui-t1{assign $inSourceTree=true}{/block}
{block body}
<h2><a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $commit)}">{trans 'Root'}</a><span class="sep">/</span>{if $breadcrumb}{$breadcrumb|safe}{/if}</h2>
<table summary="" class="tree-list">
<thead>
<tr>
<th colspan="2">{trans 'File'}</th>
<th>{trans 'Age'}</th>
<th>{trans 'Rev'}</th>
<th>{trans 'Message'}</th>
<th>{trans 'Size'}</th>
</tr>
</thead>{if !$tree_in || $props}
{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)}
<tfoot>
{if $props}
<tr><th colspan="6">
<ul>
{foreach $props as $prop => $val}
<li>{trans 'Property'} <strong>{$prop}</strong> {trans 'set to:'} <em>{$val}</em></li>
{/foreach}
</ul>
</th></tr>
{/if}
{if !$tree_in}
<tr><th colspan="6">{blocktrans}Source at commit <a class="mono" href="{$url}">{$commit}</a> created {$cobject.date|dateago}.{/blocktrans}<br />
<span class="smaller">{blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans}</span>
</th></tr>
{/if}
</tfoot>
{/if}<tbody>
{if $base}
<tr>
<td>&nbsp;</td>
<td>
<a href="{url 'IDF_Views_Source::tree', array($project.shortname, $commit, $prev)}">..</a></td>
<td colspan="3"></td>
</tr>
{/if}
{foreach $files as $file}
{aurl 'url', 'IDF_Views_Source::tree', array($project.shortname, $commit, $file.fullpath)}
<tr>
<td class="fileicon"><img src="{media '/idf/img/'~$file.type~'.png'}" alt="{$file.type}" /></td>
<td><a href="{$url}">{$file.file}</a></td>
<td><span class="smaller">{$file.date|dateago:"wihtout"}</span></td>
<td>{$file.rev}</td>
<td{if $file.type != 'blob'} colspan="2"{/if}><span class="smaller">{$file.log|nl2br}</span></td>
{if $file.type == 'blob'}
<td>{$file.size|size}</td>
{/if}
</tr>
{/foreach}
</tbody>
</table>
<p class="right soft"><kbd>svn co -r {$commit} {$project.getRemoteAccessUrl()}</kbd></p>
{/block}
{block context}
<form class="star" action="{url 'IDF_Views_Source::treeRev', array($project.shortname)}" method="get">
<p><strong>{trans 'Revison:'}</strong> {$commit}</p>
<p>
<input accesskey="4" type="text" value="{$commit}" name="rev" size="5" />
<input type="hidden" name="sourcefile" value="{$base}"/>
<input type="submit" name="s" value="{trans 'Go to revision'}" /></p>
</form>
{/block}

View File

@@ -0,0 +1,59 @@
{extends "base-simple.html"}
{block body}
{if $form.errors}
<div class="px-message-error">
<p>{trans 'Oups, please check the form for errors.'}</p>
{if $form.get_top_errors}
{$form.render_top_errors|unsafe}
{/if}
</div>
{/if}
<form method="post" action=".">
<table class="form" summary="">
<tr>
<th>{trans 'Login:'}</th>{aurl 'url', 'IDF_Views_User::view', array($user.login)}
<td><a href="{$url}">{$user.login}</a></td>
</tr>
<tr>
<th>{$form.f.first_name.labelTag}:</th>
<td>{if $form.f.first_name.errors}{$form.f.first_name.fieldErrors}{/if}
{$form.f.first_name|unsafe}
</td>
</tr>
<tr>
<th><strong>{$form.f.last_name.labelTag}:</strong></th>
<td>{if $form.f.last_name.errors}{$form.f.last_name.fieldErrors}{/if}
{$form.f.last_name|unsafe}
</td>
</tr>
<tr>
<th>{$form.f.password.labelTag}:</th>
<td>{if $form.f.password.errors}{$form.f.password.fieldErrors}{/if}
{$form.f.password|unsafe}<br />
<span class="helptext">{$form.f.password.help_text}</span>
</td>
</tr>
<tr>
<th>{$form.f.password2.labelTag}:</th>
<td>{if $form.f.password2.errors}{$form.f.password2.fieldErrors}{/if}
{$form.f.password2|unsafe}
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td><input type="submit" value="{trans 'Update Your Account'}" name="submit" /> | <a href="{url 'IDF_Views::index'}">{trans 'Cancel'}</a>
</td>
</tr>
</table>
</form>
{/block}
{block context}
<div class="issue-submit-info">
<p>{trans 'If possible, use your real name. By using your real name, people will have more trust in your comments and remarks.'}</p>
</div>{/block}
{block javascript}<script type="text/javascript">
document.getElementById('id_first_name').focus()
</script>
{/block}

View File

@@ -0,0 +1,28 @@
{extends "base-simple.html"}
{block body}
<table class="form" summary="">
<tr>
<th></th>
<td>{$member}</td>
</tr>
<tr>
<th>{trans 'Last time seen:'}</th>
<td>{$member.last_login|dateago}</td>
</tr>
<tr>
<th>{trans 'Member since:'}</th>
<td>{$member.date_joined|date}</td>
</tr>
</table>
{/block}
{block context}
<div class="issue-submit-info">
<p>{blocktrans}You are looking at the public profile of {$member}.{/blocktrans}</p>
</div>
<h2>{trans 'Projects'}</h2>
<ul>{foreach $projects as $p}
<li><a href="{url 'IDF_Views_Project::home', array($p.shortname)}">{$p}</a></li>
{/foreach}</ul>
{/block}

View File

@@ -27,10 +27,32 @@
text-align: right;
}
.dellink {
float: right;
position: relative;
margin-top: -1.6em;
}
.dellink a {
color: #a00;
}
a.userw {
color: #000;
}
.mono {
font-family: monospace;
}
.soft {
color: #777;
}
.soft a {
color: #777;
}
div.context {
padding-left: 1em;
}
@@ -38,6 +60,10 @@ div.context {
/**
* Form
*/
form.star {
display: inline;
}
table.form th, table.form td {
border: none;
vertical-align: top;
@@ -188,6 +214,10 @@ a.label {
text-decoration: none;
}
.label {
color: #204a87;
}
span.nobrk {
white-space: nowrap;
}
@@ -300,6 +330,11 @@ table.tree-list tfoot th a {
font-weight: normal;
}
table.tree-list tfoot th ul {
text-align: left;
font-size: 85%;
}
table.tree-list tr.log {
border-bottom: 1px solid #e7ebe3;
/* background-color: #eef2ea !important; */
@@ -444,3 +479,19 @@ table.diff tr.diff-next {
table.diff tr.diff-next td {
padding: 1px 5px;
}
/**
* Download
*/
div.download-file {
padding: 1em 1em 1em 3em;
background: url("../img/down-large.png");
background-repeat: no-repeat;
background-position: 1em 1em;
font-size: 140%;
margin-bottom: 3em;
background-color: #bbe394;
width: 40%;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}

BIN
www/media/idf/img/add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

BIN
www/media/idf/img/star.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

BIN
www/media/idf/img/trash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B