587 Commits

Author SHA1 Message Date
Loic d'Anterroches
d7843a55bd Updated the French translations. 2010-04-19 09:28:48 +02:00
Loic d'Anterroches
b7833b5d23 Updated to have the latest .pot file. 2010-04-19 09:27:37 +02:00
Loic d'Anterroches
0f617f8e09 Fixed to directly use a PHP internal function, thanks piouPiouM. 2010-04-19 09:16:56 +02:00
Loic d'Anterroches
fbc1fab68d Added the Mercurial hook connection to run on groupchange. 2010-04-19 09:14:29 +02:00
Loic d'Anterroches
68890c3c48 Improved the Mercurial documentation. 2010-04-19 09:10:43 +02:00
Loic d'Anterroches
10f08386f7 Fixed to have a simpler code. 2010-04-15 15:40:36 +02:00
Loic d'Anterroches
45fa309c07 Fixed to have a simpler code. 2010-04-15 15:38:03 +02:00
Loic d'Anterroches
2ad13a96f9 Added the changegroup hook for Mercurial.
This is the last commit for 1.0, yeah\!
2010-04-15 15:20:50 +02:00
Loic d'Anterroches
73f95de843 Fixed a stupid typo in the hook configuration check. 2010-04-15 10:48:39 +02:00
Loic d'Anterroches
c13d1aba30 Made the svn-post-commit script executable. 2010-04-15 10:36:31 +02:00
Loic d'Anterroches
42f561dc75 Added the subversion post commit hook. 2010-04-15 10:00:05 +02:00
Loic d'Anterroches
8280add935 Moved the event logging just before the action. 2010-04-14 13:43:58 +02:00
Loic d'Anterroches
bbc29c889f Added the post-update hook at the creation of the git repository. 2010-04-14 13:31:30 +02:00
Loic d'Anterroches
e5ee6d8fca Added the first work on the scm post commit hooks. 2010-04-14 10:54:16 +02:00
Loic d'Anterroches
738a8bdd60 Fixed issue 424, English grammar. 2010-04-12 20:55:00 +02:00
Loic d'Anterroches
119faf55cf Added ticket 419, render for .net files. 2010-04-12 20:43:04 +02:00
Loic d'Anterroches
7b0ece42f0 Fixed to convert in utf-8 earlier in the process. 2010-03-26 20:40:50 +01:00
Loic d'Anterroches
3c29e4e6ae Fixed to not use the assumption that the first line of a commit message set the encoding. 2010-03-26 19:57:18 +01:00
Loic d'Anterroches
a5f97c59d9 Fixed missing detection of ISO-8859-2 characters. 2010-03-26 14:53:43 +01:00
Loic d'Anterroches
77c7f8ecfe Updated the French translations and .pot file. 2010-03-16 15:26:17 +01:00
Loic d'Anterroches
dc34829afe Fixed issue 417, 403 after clicking 'delete this page'. 2010-03-16 15:21:17 +01:00
Raphaël Emourgeon
10245113d5 Fixed ticket 406, PHP 5.3.1 compatibility. 2010-03-16 10:40:29 +01:00
Raphaël Emourgeon
349970cfaf Fixed ticket 410, upload error message on new issue shouldn't be hidden. 2010-03-16 10:03:49 +01:00
Loic d'Anterroches
0056e3f0b2 Added ticket 414, references email header for the issue notifications. 2010-03-16 09:41:21 +01:00
Loic d'Anterroches
4e7d3618a7 Fixed issue 319, unable to browse a remote subversion repository. 2010-03-01 19:59:32 +01:00
Loic d'Anterroches
548a427148 Fixed issue 407, notifications emails are sent twice. 2010-03-01 15:02:09 +01:00
Loic d'Anterroches
49e5aa783d Fixed to correctly write into the temp folder. 2010-02-27 22:26:34 +01:00
Loic d'Anterroches
b9d8eeea9e Fixed to catch the error output of the key check. 2010-02-27 22:09:51 +01:00
Loic d'Anterroches
75777daf4b Fixed to prevent uploading several times the same key. 2010-02-27 18:09:02 +01:00
Loic d'Anterroches
d3b76975cd Added the option to have a strong check of the ssh key with ssh-keygen. 2010-02-27 17:42:09 +01:00
Loic d'Anterroches
64fb5b3bf0 Fixed issue 402, Project List shows '0 bytes' Repository size on Mac OS X. 2010-02-24 15:06:13 +01:00
Loic d'Anterroches
51842c02f6 Added the ability to manually create a user. 2010-02-24 10:43:28 +01:00
Loic d'Anterroches
cc6f1c7cd8 Added ticket 165, multiple SSH keys per user. 2010-02-22 22:27:31 +01:00
Loic d'Anterroches
5d24931d9b Changed to force the shortname to be lower case. 2010-02-22 20:50:54 +01:00
Loic d'Anterroches
e2bce19526 Removed the project list on the public profile page. 2010-02-22 20:41:12 +01:00
Loic d'Anterroches
9653f1a341 Correctly display the delete page link only to users with correct rights. 2010-02-20 13:03:06 +01:00
Loic d'Anterroches
a5a5c7b2b6 Fixed security issue, read access on a git project marked as private and source access marked as login in/all access. 2010-02-19 17:43:29 +01:00
Loic d'Anterroches
c486ca928b Added ticket 357, delete wiki article. 2010-02-19 11:46:43 +01:00
Loic d'Anterroches
3a28fe9d28 Fixed ticket 208, added more logging in GitServe and Scm backend. 2010-02-17 21:57:28 +01:00
Loic d'Anterroches
8da6fe7287 Added the support of war and jar files in the upload area. 2010-02-17 08:53:36 +01:00
Loic d'Anterroches
52be41186f Correctly request an account confirmation when trying to recover the password of a not yet activated account. 2010-02-15 22:40:34 +01:00
Loic d'Anterroches
96e8f4ae3c Added a better handling of simultaneous updates of the search index. 2010-02-14 22:01:46 +01:00
Loic d'Anterroches
355f9ca05f Updated to add another author. 2010-02-14 21:50:07 +01:00
Brian Armstrong
ef40b0a34e Fixed ticket 393, bad English on the Terms Page. 2010-02-14 21:21:29 +01:00
Loic d'Anterroches
73f6430a60 Fixed to prevent a password reset to login an inactive user. 2010-02-09 14:47:13 +01:00
Loic d'Anterroches
2b107c1610 Added ticket 391, password storage configuration variable for Mercurial. 2010-02-06 14:24:11 +01:00
Loic d'Anterroches
93d379f293 Added another author. 2010-02-04 14:35:06 +01:00
Loic d'Anterroches
6d415e9ec5 Fixed ticket 349, updated the prettify lib to the latest version. 2010-02-04 11:21:30 +01:00
Loic d'Anterroches
577aac2069 Fixed issue 348, no notifications on own actions. 2010-02-04 11:01:32 +01:00
Loic d'Anterroches
73641a03d5 Fixed issues 368 and 354, access to files with special characters. 2010-01-20 22:33:53 +01:00
Ludovic Bellière
0b580ba2ec Fixed issue 314, Mercurial shows empty files in directories 2010-01-20 21:27:18 +01:00
Loic d'Anterroches
a8a292a3c5 Updated the list of authors. 2010-01-15 10:59:12 +01:00
Denis Kot
ec1d21a625 Added the start of the Russian translations. 2010-01-15 10:57:04 +01:00
Samuel Suther
078c6feae8 Added the start on the German translations. 2010-01-15 10:51:42 +01:00
Loic d'Anterroches
63aedd6b42 Updated the documentation from ticket 369. 2010-01-15 10:42:07 +01:00
Janez Troha
1e3dfef438 Added ticket 347, partial Slovenian translations. 2009-11-26 10:30:27 +01:00
Loic d'Anterroches
c72e9f4eb0 Changed to accomodate the new way the session language is working. 2009-11-13 12:26:49 +01:00
Loic d'Anterroches
857c11933a Exclude path.php from Git. 2009-11-09 21:43:45 +01:00
Loic d'Anterroches
30a3515e94 Updated the French translations. 2009-11-06 20:38:23 +01:00
Loic d'Anterroches
ff4f8afde8 Added the support of the tags for Mercurial and Subversion. 2009-11-06 18:06:01 +01:00
Mehdi Kabab
8050463a12 Added ticket 271, support of the Git tags. 2009-11-06 16:12:54 +01:00
Mehdi Kabab
73dba2fa1a Added ticket 259, automatic linking of issues/commits to support TortoiseSVN preferred issue/commit naming. 2009-11-06 16:00:47 +01:00
Loic d'Anterroches
3c46b7734f Added the ability to use the username of a user to link a git commit. 2009-11-06 15:58:21 +01:00
Loic d'Anterroches
48ff314487 Updated the new logo. 2009-11-06 15:57:45 +01:00
Loic d'Anterroches
4fa7a20fd3 Added a try for an InDefero logo. 2009-11-05 10:35:42 +01:00
Mehdi Kabab
75280d6892 Fixed issue 325, unexpected reset SSH key. 2009-11-01 23:07:33 +01:00
Loic d'Anterroches
6c5fde77b4 Added the ability to link to a review from the comments. 2009-10-29 13:54:44 +01:00
Loic d'Anterroches
646cf6479b Fixed issue 145, code reviews don't show up in the timeline. 2009-10-29 13:25:50 +01:00
Loic d'Anterroches
1d24432f8d Fixed issue 315, atom feed of a wiki update is broken. 2009-10-28 09:28:06 +01:00
Loic d'Anterroches
37aa3d8b69 Fixed not to stat the size of a non existing repository. 2009-10-26 21:45:42 +01:00
Loic d'Anterroches
9437a19ee0 Added migrations to backup restore indefero to/from a JSON file. 2009-10-19 11:28:01 +02:00
Mehdi Kabab
6c04fa80bd Fixed issue 311, link Forge Management is missing in the admin pannel. 2009-10-15 15:12:37 +02:00
Loic d'Anterroches
a667227943 Updated the French translations. 2009-10-12 08:18:33 +02:00
Loic d'Anterroches
8d32905913 Fixed transient error when the git cron job creates the repository before the first push. 2009-10-09 10:35:13 +02:00
Loic d'Anterroches
58ab16432c Fixed issue 261, default paths for gitcron.php and gitserve.php can't find Pluf. 2009-10-09 10:16:00 +02:00
Loic d'Anterroches
aa383ffb1b Merge branch 'issue268-feedcorrupt' 2009-10-09 09:23:08 +02:00
Loic d'Anterroches
f3cadfe013 Fixed issue 268, atom feed corrupt. 2009-10-09 09:22:36 +02:00
Loic d'Anterroches
c7aa91fc29 Fixed issue 304, cannot display source files with brackets in their names. 2009-10-09 08:46:14 +02:00
Loic d'Anterroches
e5934e0a3a Fixed issue 167, timeline feed URL should only include auth token for private projects. 2009-10-08 13:27:15 +02:00
Loic d'Anterroches
b6c5e803cb Fixed issue 114, email sent in language of commenter. 2009-10-08 11:23:33 +02:00
Loic d'Anterroches
ec8ff4f76b Avoid unnecessary assignement. 2009-10-06 21:33:58 +02:00
Loic d'Anterroches
06668db697 Fixed issue 304, cannot display source files with brackets in their names. 2009-10-06 21:28:37 +02:00
Loic d'Anterroches
cab5d35771 Added a new contributor. 2009-10-06 21:05:42 +02:00
Loic d'Anterroches
a82b4b7b6b Fixed issue 305, improved the compatibility with PostgreSQL 8.4. 2009-10-06 20:54:28 +02:00
Loic d'Anterroches
37e0604647 Fixed bad conversion to support PHP 5.3, 2009-10-01 21:51:47 +02:00
David Feeney
4765ca2232 Fixed issue 282, Mercurial support in new projects. 2009-10-01 14:56:54 +02:00
Loic d'Anterroches
6edaf03faa Fixed issue 283, confusing with two 'Administer' tabs. 2009-10-01 14:44:39 +02:00
Loic d'Anterroches
0dedee4429 Fixed issue 296, meaningful message on status change. 2009-10-01 14:24:36 +02:00
Loic d'Anterroches
078c75514e Fixed typo in the git documentation. 2009-09-25 15:01:00 +02:00
Loic d'Anterroches
838645463d Remove the PHP 5.3 deprecated split function. 2009-09-24 20:40:22 +02:00
Loic d'Anterroches
157819195b Fixed issue 285, weird mkdir() error sometimes on first git push. 2009-09-21 10:03:33 +02:00
Loic d'Anterroches
25d7a5a776 Fixed issue 286, repository size fails on symlinks. 2009-09-21 09:58:29 +02:00
Loic d'Anterroches
9d9541ee11 Fixed issue 278, database with - in name causes exception on the admin projects list. 2009-09-21 09:46:46 +02:00
Loic d'Anterroches
c1e23ae9e2 Fixed to prevent the search on no available keywords. 2009-08-20 15:20:15 +02:00
Loic d'Anterroches
14e074b122 Fixed to prevent the display of an invalid tree. 2009-08-20 15:14:42 +02:00
Loic d'Anterroches
c3ae1970ca Updated the installation documentation for FreeBSD. 2009-08-07 20:03:52 +02:00
Loic d'Anterroches
fbcdacdccf Allowed the th HTML tag in the MarkDown output. 2009-08-04 10:25:29 +02:00
Andrew Nguyen
f473691479 Fixed issue 262, embbed documents from repository in Documentation wiki.
You can now embbed text documents frome the repository into the
documentation wiki. Changes in the repository are automatically
reflected into the wiki.

The syntax is [[[path/to/file, optional commit]]].
2009-08-03 22:38:14 +02:00
Loic d'Anterroches
f7a7ac39f3 Fixed issue 274, link to the markdown extra documentation. 2009-08-03 21:42:32 +02:00
Loic d'Anterroches
ca5270a074 Fixed issue 275, unable to activate a file already uploaded if first upload was in error. 2009-08-03 21:38:32 +02:00
Loic d'Anterroches
0e421c0b34 Fixed issue 267, description for code review not visible on Review screen. 2009-08-03 21:23:23 +02:00
Loic d'Anterroches
cb375dea26 Fixed issue 272, cfg max_upload_size ignored. 2009-08-03 21:10:06 +02:00
Loic d'Anterroches
6845f59150 Fixed variable used before being instanciated in the Mercurial backend. 2009-07-23 23:33:08 +02:00
Mehdi Kabab
021805f1e1 Fixed issue 270, 'src:' autolinking doesn't work if path contains spaces. 2009-07-20 20:07:53 +02:00
Loic d'Anterroches
95881bd7f1 Fixed the order of the model installation. 2009-07-16 16:01:48 +02:00
Loic d'Anterroches
3c59c80bcc Added a new contributor. 2009-07-16 10:51:09 +02:00
Loic d'Anterroches
b3bacf25ff Updated the French locale. 2009-07-16 10:46:07 +02:00
Loic d'Anterroches
99992442f5 Fixed the review backend to support multiple patches per review and line level comments. 2009-07-16 10:04:58 +02:00
Loic d'Anterroches
dd56d681b3 Merged master back into the code review work. 2009-07-15 14:18:50 +02:00
Andrew Nguyen
ba18f30c4f Fixed issue 249, in the changelog switching to another branch must stay in the changelog. 2009-07-14 22:23:30 +02:00
Loic d'Anterroches
82e2684004 Fixed to match with the name of configuration setting. 2009-07-13 16:11:20 +02:00
Loic d'Anterroches
dc6f25cdef Fixed to handle the case of bad commit. 2009-07-12 22:13:52 +02:00
Loic d'Anterroches
36c1f98438 Added inline documentation with respect to the source access. 2009-07-12 07:18:19 +02:00
Loic d'Anterroches
ac7be1bde2 Fixed issue 253, last login time for users is off. 2009-07-11 10:55:47 +02:00
Loic d'Anterroches
1a067ca107 Fixed crash of the Subversion backend when requesting a non existing revision. 2009-07-09 00:05:36 +02:00
Loic d'Anterroches
c321e4828b Fixed issue 181, add an icon in the project list for private projects. 2009-07-08 14:35:44 +02:00
Loic d'Anterroches
68cc1ed5d3 Improved the CSS style. 2009-07-08 14:28:30 +02:00
Loic d'Anterroches
69a7a58ce7 Fixed issue 146, display tickets by Id. 2009-07-08 14:23:01 +02:00
Loic d'Anterroches
c0dfd6b5dc Fixed issue 180, can't select branches containing / character in git projects. 2009-07-08 13:49:16 +02:00
Loic d'Anterroches
7d6cb22291 Fixed to have a better character encoding detection of the git log. 2009-07-03 10:23:49 +02:00
Loic d'Anterroches
32507085b4 Added another level of check on the reset password key. 2009-07-02 20:04:46 +02:00
Loic d'Anterroches
c488278ce1 Fixed bad use of Exception in the wiki view. 2009-07-02 19:59:06 +02:00
Loic d'Anterroches
ad17d797b2 Fixed parsing error. 2009-07-02 19:38:52 +02:00
Loic d'Anterroches
2b1a741946 Fixed issue 245, ['idf_exec_cmd_prefix'] configuration variable is not available anymore. 2009-07-02 13:05:43 +02:00
Loic d'Anterroches
cbae598893 Fixed issue 243, missing git documentation. 2009-07-02 12:42:16 +02:00
Charles Melbye
d153cd9049 Fixed issue 244, project List shouldn't show ',' if there is no short description. 2009-07-01 20:17:23 +02:00
Loic d'Anterroches
2a15e2a350 Fixed English typo. 2009-06-29 22:33:15 +02:00
Loic d'Anterroches
1815aaa909 Fixed to have a better workaround for Firefox not to break Safari. 2009-06-29 22:10:32 +02:00
Loic d'Anterroches
bdea6e4cbb Fixed English typo. 2009-06-29 21:45:55 +02:00
Charles Melbye
75af09062f Fixed issue 241, links to attach files in Issues should not actually open their href target. 2009-06-29 21:23:58 +02:00
Loic d'Anterroches
c366124917 Fixed the case of accessing the log of an unavailable project 2009-06-26 20:52:56 +02:00
Loic d'Anterroches
16ce0da5f9 Fixed issue 240, correctly link the long URLs in the issues. 2009-06-26 13:12:26 +02:00
Loic d'Anterroches
2ab52e7eaf Added the possible deletion of the repository when deleting a subversion project. 2009-06-25 21:37:46 +02:00
Loic d'Anterroches
fcefbe719f Fixed to better detect a bad commit. 2009-06-24 19:33:16 +02:00
Loic d'Anterroches
7d3f7e226c Fixed issue 238, missing return statement for the subversion backend. 2009-06-24 14:49:06 +02:00
Loic d'Anterroches
3848bd8d22 Added longer description for the downloads. 2009-06-22 21:08:33 +02:00
Loic d'Anterroches
0873d44162 Added the database usage statistics. 2009-06-19 22:42:44 +02:00
Loic d'Anterroches
6cf4f00f92 Fixed to make the command portable on BSD/Solaris. 2009-06-19 21:37:39 +02:00
Loic d'Anterroches
d6c0b7a680 Added forge size statistics. 2009-06-19 21:10:37 +02:00
Loic d'Anterroches
25e296fbb6 Added the display of the repository size in the source subtab of a project. 2009-06-19 17:31:45 +02:00
Mehdi Kabab
e235242ea6 Fixed issue 204, deleting a project does not remove the repository 2009-06-19 16:44:35 +02:00
Loic d'Anterroches
9b39d63104 Updated the list of authors 2009-06-19 16:24:19 +02:00
Loic d'Anterroches
8915b45948 Fixed issue 209, erroneous details with private repository 2009-06-19 16:20:33 +02:00
Baptiste Durand-Bret
29e053bf6b Fixed issue 217, can't link directly to a user comment when linking to an issue 2009-06-19 16:01:17 +02:00
Loic d'Anterroches
a15107558c Fixed issue 235, need consistent use of file and fullpath in the SCM backend 2009-06-19 15:51:31 +02:00
Charles Melbye
b320375d60 Fixed issue 231, small CSS bug in WebKit-based browsers 2009-06-19 15:20:59 +02:00
Loic d'Anterroches
fb66e1e98f Fixed to correctly parse the full message of an svn commit log. 2009-06-09 18:03:40 +02:00
Loic d'Anterroches
2f0156e63b Fixed to correctly get the main branch in case of error. 2009-06-09 08:42:59 +02:00
Loic d'Anterroches
e31d822d74 Fixed the case of a non-existing file. 2009-06-08 19:44:52 +02:00
Loic d'Anterroches
15cba014ba Fixed undefined variable. 2009-06-08 19:26:58 +02:00
Patrick Georgi
88a1bf9c94 Fixed issue 232, repository browser on SVN shows stacktraces 2009-06-08 13:21:03 +02:00
Loic d'Anterroches
d014b36ee2 Added more room at the top of the page. 2009-05-30 20:33:47 +02:00
Loic d'Anterroches
cefdceba13 Fixed issue 229, code review broken with new SCM backend. 2009-05-30 20:04:43 +02:00
Loic d'Anterroches
1c8490be6b Fixed to get the same method definition as the parent class. 2009-05-28 00:30:10 +02:00
Loic d'Anterroches
1243a8f6ad Test the preloading of the class. 2009-05-27 23:30:27 +02:00
Loic d'Anterroches
d98dda645e Pushed the constructor into the backend implementation. 2009-05-27 23:08:55 +02:00
Loic d'Anterroches
44ea8f1817 Added the getAuthAccessUrl method. 2009-05-27 22:54:26 +02:00
Loic d'Anterroches
6ef721d3a8 Fixed to sync the timeline only if the backend is available. 2009-05-27 18:04:33 +02:00
Loic d'Anterroches
7d84da4d7c Added the git cache in the installation. 2009-05-27 17:59:50 +02:00
Loic d'Anterroches
a53c6a1778 Fixes the case of an empty repository again. 2009-05-27 17:04:41 +02:00
Loic d'Anterroches
b75286375e Fixes the case of an empty repository. 2009-05-27 16:59:25 +02:00
Loic d'Anterroches
ad8a6d3071 Fixed error when accessing the help page. 2009-05-27 08:41:11 +02:00
Loic d'Anterroches
54b37ac5b6 Fixed issue 226, branches are displayed only on source tree view. 2009-05-26 21:59:02 +02:00
Loic d'Anterroches
bc434504b1 Fixed issue 210, sub-tabs links not follows the active branche in Source view. 2009-05-26 21:47:54 +02:00
Loic d'Anterroches
88ce10b8e6 Fixed issue 227, timeline fails on SVN repositories. 2009-05-26 21:20:10 +02:00
Loic d'Anterroches
0abb706ded Quick documentation improvement. 2009-05-26 15:02:30 +02:00
Loic d'Anterroches
54abd7bc7b Fixed typo in the translations. 2009-05-26 14:43:50 +02:00
Loic d'Anterroches
c866d09e27 Fixed issue 219, notification when adding a wrong user in a project. 2009-05-26 13:33:00 +02:00
Mehdi Kabab
668b49df40 Fixed issue 220, redirect to the issue or the issue comment after an updating. 2009-05-26 12:04:06 +02:00
Mehdi Kabab
3476541bf4 Fixed issue 221, scroll to the preview if exists. 2009-05-26 11:48:02 +02:00
Adrien Bustany
2c9cf96245 Fixed issue 224, add Vala support to source browser. 2009-05-26 11:33:20 +02:00
Loic d'Anterroches
39699ba723 Fixed to correctly return false on non-existing commits. 2009-05-26 11:28:36 +02:00
Loic d'Anterroches
0fd0c40e89 Fixed to update to use the new method signature. 2009-05-25 15:19:22 +02:00
Loic d'Anterroches
d31cd2aef4 Added the latest fixes for the new backend. 2009-05-25 14:18:48 +02:00
Loic d'Anterroches
c83e2e6f30 Merge branch 'master' into dev 2009-05-25 12:16:44 +02:00
Loic d'Anterroches
f55769a946 Improved the Mercurial backend. 2009-05-25 12:16:34 +02:00
Loic d'Anterroches
8345a02aaa Removed the old priority information. 2009-05-15 09:49:51 +02:00
Loic d'Anterroches
1c51437569 Fixed bad sequence of the tests. 2009-05-07 15:28:52 +02:00
Loic d'Anterroches
63cdede854 Fixed issue 205, clean up some gettext strings. 2009-05-07 15:23:23 +02:00
Loic d'Anterroches
d216e21858 Fixed issue 203, user doesn't show in active/inactive if first name is ---. 2009-05-07 15:07:02 +02:00
Loic d'Anterroches
358a774017 Fixed issue 213, saving the configuration of the doc/issues is sending to the wrong page. 2009-05-07 14:54:42 +02:00
Loic d'Anterroches
3c162486e4 Fixed crash with non UTF-8 encoded change log. 2009-05-07 09:54:09 +02:00
Loic d'Anterroches
a3f40447c0 Continued the refactoring of the Subversion backend. 2009-04-27 11:39:19 +02:00
Loic d'Anterroches
cf5acfb669 Base refactor of the Subversion backend. 2009-04-26 11:57:21 +02:00
Loic d'Anterroches
7c502b1745 Continued the SCM backend refactor.
The new backend is near completion.
2009-04-25 16:24:40 +02:00
Loic d'Anterroches
aab8720cac Added a DB backend for the Git blob info. 2009-04-21 17:45:35 +02:00
Loic d'Anterroches
86da0c0eed Added a first version of the new SCM backend. 2009-04-21 14:13:44 +02:00
Loic d'Anterroches
903c457439 Updated the contributor list. 2009-04-20 15:12:54 +02:00
Patrick Georgi
0efb0fec44 Fixed issue 199, some optimizations for SVN access. 2009-04-20 15:05:34 +02:00
Loic d'Anterroches
2b8743727a Fixed issue 198, error when trying to use git opertaions: Need SSH_ORIGINAL_COMMAND. 2009-04-19 14:44:56 +02:00
Mehdi Kabab
b94934eafb Fixed issue 195, added parsing of the commit plural forms. 2009-04-19 14:33:12 +02:00
Loic d'Anterroches
bf5eba2ecc Update the French translations. 2009-04-16 13:39:44 +02:00
Loic d'Anterroches
1656c99a48 Fixed issue 183, project can not be deleted because of foreign key integrity constraints. 2009-04-16 13:27:41 +02:00
Mehdi Kabab
b9773b555c Fixed issue 189, no rounded tab in webkit (safari / chrome). 2009-04-16 11:47:42 +02:00
Mehdi Kabab
62e173e312 Fixed issue 185, custom predefined download labels are not displayed in autocomplete. 2009-04-16 11:43:03 +02:00
Mehdi Kabab
194a081c90 Added the highlighted extensions as text extensions. 2009-04-07 11:38:24 +02:00
Benjamin Jorand
04c2115fc2 Improved the source browser on large Mercurial repositories. 2009-04-07 11:19:07 +02:00
Loic d'Anterroches
08bc58848f Fixed issue 178, improved reading of extra text files. 2009-04-07 10:41:13 +02:00
xavier Brochard
1d527b5866 Fixed issue 176, typos in the recovering pasword email (french version). 2009-04-07 10:37:11 +02:00
Mehdi Kabab
4276088410 Fixed issue 175, sub-tabs are displayed on two lines when the project title is too long. 2009-04-07 10:29:35 +02:00
Loic d'Anterroches
96784b6e7c Started again the work on the review. 2009-03-29 07:01:32 +02:00
Loic d'Anterroches
858d2c93e2 Fixed issue 133, updated the documentation with svn minimal revision. 2009-03-28 07:18:56 +01:00
Loic d'Anterroches
aabcd9a16f Added some more authors. 2009-03-27 10:51:53 +01:00
Loic d'Anterroches
9f673307e0 Added the dash in the explanations of the allowed character in the project name
This is solving issue 148 correctly.
2009-03-27 05:45:30 +01:00
Loic d'Anterroches
f25ebc1664 Fixed issue 161, download link in source tree has poor visibility. 2009-03-26 15:42:22 +01:00
Loic d'Anterroches
f028682900 Fixed issue 160, diff viewer in code reviews does not indent correctly. 2009-03-26 15:21:16 +01:00
Loic d'Anterroches
1b2a5b2718 Updated the list of contributors. 2009-03-26 12:31:07 +01:00
Ciaran Gultnieks
c16962e5f5 Fixed issue 168, code review shows wrong commits. 2009-03-26 12:28:03 +01:00
Mehdi Kabab
dd2dda1895 Fixed issue 158, improved MarkDown support. 2009-03-26 12:23:37 +01:00
Ciaran Gultnieks
0f39236883 Fixed issue 166, typo in confirmation email message. 2009-03-26 12:17:14 +01:00
Loic d'Anterroches
08a03f0629 Fixed issue 159, Problem with display of source files with extension of c, etc.
Updated the code documentation to avoid confusions.
2009-03-26 12:04:16 +01:00
Loic d'Anterroches
37cb05ff38 Added Patrick in the list of contributors. 2009-03-06 14:04:59 +01:00
Patrick Georgi
ef93d2da0a Fixed issue 143, Makefile not displayed as text. 2009-03-03 10:12:42 +01:00
Loic d'Anterroches
0460240337 Improved the layout to link to the personal closed tickets. 2009-02-27 15:25:41 +01:00
Loic d'Anterroches
bdba7aace9 Fixed to prevent bad wrap. 2009-02-27 15:13:43 +01:00
Loic d'Anterroches
28d5ce417a Added per project list of personal closed working/submitted tickets. 2009-02-27 15:08:56 +01:00
Loic d'Anterroches
8da821eef4 Fixed issue 131, added ability to preview before submitting an issue. 2009-02-27 14:21:09 +01:00
Loic d'Anterroches
7cc5a2dc58 Improved the external icon for the git submodules. 2009-02-27 13:51:23 +01:00
Loic d'Anterroches
cfafdbfde4 Fixed issues 137 and 138, support of the git submodules. 2009-02-27 13:38:22 +01:00
Loic d'Anterroches
e40485c56f Fixed last part of issue 105, update the details of a project.
From the forge administration part of a project, you now have a direct link to the project administration
tab.
2009-02-27 11:10:26 +01:00
Loic d'Anterroches
0b465c750b Updated the French translations. 2009-02-27 10:58:18 +01:00
Loic d'Anterroches
7f4f14e78d Fixed issue 105 point 2, added deletion of a project.
Note that the source code is not deleted at the moment.
2009-02-27 10:42:18 +01:00
Loic d'Anterroches
f986184254 Added a new author. 2009-02-26 11:40:06 +01:00
Loic d'Anterroches
32dc829a41 Fixed issue 142, control the patch when creating a new code review. 2009-02-26 11:09:38 +01:00
Loic d'Anterroches
4d3812fc35 Fixed issue 135, anonymous author on source/commit/[hash]/ 2009-02-25 14:36:08 +01:00
Manuel Eidenberger
42c6f53fbc Added the ability to configure the paths to the SCM executables. 2009-02-25 14:28:14 +01:00
Loic d'Anterroches
64dc8ec3b8 Fixed to prevent a crash when accessing the base page of a project with no /.
When accessing the base page of a project without a trailing slash, the regex to get the project in the
request object was not matching, resulting in a missing project in the request object.
2009-02-17 09:37:05 +01:00
Loic d'Anterroches
d1b139bb30 Better redirection to have canonical URLs. 2009-02-16 22:18:09 +01:00
Loic d'Anterroches
37f94d8581 Added smarter handling of trailing slash in the source view. 2009-02-16 21:33:19 +01:00
Loic d'Anterroches
ea55f9f793 Added nl2br in the display of the commit messages. 2009-02-16 18:12:21 +01:00
Loic d'Anterroches
75ec32765c Added the ability to add download extension and control the download size. 2009-02-16 18:04:03 +01:00
Loic d'Anterroches
9cd2d1c4ef Added the relations of the IDF_Commit model. 2009-02-16 11:55:14 +01:00
Loic d'Anterroches
b619b96a9d Cleaned the old translations. 2009-02-03 10:53:20 +01:00
Loic d'Anterroches
b18f172eca Updated the French translations. 2009-02-03 10:52:13 +01:00
Loic d'Anterroches
9bcf99e3a7 Fixed issue 126, easy link to a file in the repository from the tickets.
The syntax src:AUTHORS or src:src/IDF/Scm.php is allowed.
2009-02-02 22:55:45 +01:00
Loic d'Anterroches
47f84bcb55 Fixed in a dirty way, should be improved. 2009-02-02 21:47:50 +01:00
Loic d'Anterroches
b7d1afb01e Fixed bug in the diff display when a single empty line was removed. 2009-02-02 21:42:31 +01:00
Loic d'Anterroches
d594b3412a Fixed issue 129, code review crashes on git diff files.
The diff parser tries now to skip the header and possible footer.
2009-02-02 20:55:45 +01:00
Loic d'Anterroches
269f1db816 Improved the help text wording. 2009-02-02 17:41:08 +01:00
Loic d'Anterroches
6c83169f1c Fixed the submission of a code review and the display.
The display of a code review was crashing when a new file was added in
the diff.
The form to submit a review now proposes directly a choice of commits.
2009-02-02 17:38:48 +01:00
Loic d'Anterroches
088f426986 Added code tag to the list of allowed tags. 2009-02-01 14:24:04 +01:00
Loic d'Anterroches
a116c27c03 Fixed issue 124, JS dropdown lists not working in Issues page under IE 7 & 8.
IE does not like trailing comma in an array.
Bad: [1, 2, 3,] Good: [1, 2, 3]
2009-02-01 09:54:09 +01:00
Loic d'Anterroches
a1ecb4c0b0 Added the link to the git documentation. 2009-01-31 23:07:55 +01:00
Loic d'Anterroches
9d5176c614 Updated the documentation to render nicely with the php markdown class. 2009-01-31 23:04:59 +01:00
Loic d'Anterroches
3279245ab2 Updated the French translations. 2009-01-31 22:46:55 +01:00
Loic d'Anterroches
3c7ad4581f Added the display of the number of people following a ticket. 2009-01-31 22:23:49 +01:00
Loic d'Anterroches
01b68978ff Fixed a crash when submitting a new code review and no full rights. 2009-01-30 23:28:18 +01:00
Loic d'Anterroches
8f7ec57213 Fixed crash when looking at a wiki page with missing link and no create rights. 2009-01-30 23:20:52 +01:00
Loic d'Anterroches
3b830581d5 Fixed to show the SSH access URL to perform the clone for a private project. 2009-01-30 14:11:37 +01:00
Loic d'Anterroches
dc6e3e2cda Added a little note about the need to restart Apache in some cases. 2009-01-30 12:13:56 +01:00
Loic d'Anterroches
6153cd0b13 Fixed issue 119, no environment for the shell_exec and exec calls.
For each call exec or shell_exec, I have added the ability to prepend a
string. For example '/usr/bin/env -i '.
2009-01-29 20:29:45 +01:00
Loic d'Anterroches
6724238616 Update the French translation. 2009-01-29 18:59:26 +01:00
Loic d'Anterroches
766acd01f4 Added the ability for a user to change his email address.
The change is not performed immediately. First a confirmation email is
sent to the user and if validated, the email address is changed.
2009-01-29 18:44:39 +01:00
Loic d'Anterroches
1307c97ff3 Updated the French translations and page title. 2009-01-28 15:40:37 +01:00
Loic d'Anterroches
fb1a47b323 Added the notification for the commits. 2009-01-28 15:20:41 +01:00
Loic d'Anterroches
be7cbabe8d Added the notification of changes for the documentation pages. 2009-01-28 14:59:51 +01:00
Loic d'Anterroches
ee5044fb1c Added the notifications for the code review. 2009-01-28 14:03:34 +01:00
Loic d'Anterroches
2ec653ad43 Merge branch 'notifications' 2009-01-26 21:52:13 +01:00
Loic d'Anterroches
b6922a0296 Added the notifications on the new uploaded files. 2009-01-26 21:51:51 +01:00
Loic d'Anterroches
d47530e128 Fixed stupid mistake in copy/paste. 2009-01-26 21:22:59 +01:00
Loic d'Anterroches
f203f7d78f Fixed bad help message for the git access.
Git has 2 methods to access a repository, one with gitdaemon to do a
checkout and one with SSH to perform the push. We need to provide those
2 methods.
2009-01-26 17:58:58 +01:00
Loic d'Anterroches
c11cd7bc62 Fixed issue 110, SyncMercurial error and undocumented requirement.
Added the documentation.
2009-01-26 09:44:32 +01:00
Loic d'Anterroches
fccdcaa878 Fully fix issue 112, creating Mercurial also gives Subversion repos. 2009-01-25 21:22:38 +01:00
Loic d'Anterroches
dd6517c709 Fixed Issue 112, creating Mercurial also gives Subversion repos. 2009-01-25 21:09:31 +01:00
Loic d'Anterroches
96a82ba0ae Fixed issue 113, login name case problem. 2009-01-25 21:00:21 +01:00
Loic d'Anterroches
743a3f312c Added a control to avoid redirecting to an inexisting branch. 2009-01-25 20:48:02 +01:00
Loic d'Anterroches
e513d95dbe Added the notification for the tickets. 2009-01-25 10:58:24 +01:00
Loic d'Anterroches
ec2565e244 Fixed French translations. 2009-01-25 10:56:08 +01:00
Loic d'Anterroches
4c45a4a2ac Added the management of the notification email addresses. 2009-01-25 10:32:34 +01:00
Loic d'Anterroches
121c94dc8e Added more help for the Git access. 2009-01-24 12:56:59 +01:00
Loic d'Anterroches
bcbcee281f Added the IDF_Conf relation. 2009-01-24 10:07:33 +01:00
Loic d'Anterroches
9d667643e7 Changed to use the "origin" convention for the git repository. 2009-01-23 16:28:55 +01:00
Loic d'Anterroches
f4133a1a1d Fixed French translations. 2009-01-23 16:27:57 +01:00
Loic d'Anterroches
38ac214d55 Fixed bug in the cache resulting in errors in the tree view.
The cache was not per project but one cache for all the projects.
2009-01-23 15:06:57 +01:00
Loic d'Anterroches
657dd339a1 Updated the French translations. 2009-01-23 14:25:28 +01:00
Loic d'Anterroches
cadbc040a1 Fixed issue 101, better information for the Subversion login/password.
A nice little help icon is providing more information for the checkout.
This for all the backends.
2009-01-22 11:12:41 +01:00
Loic d'Anterroches
6b32413e69 Automatically create the git repository during the cron run. 2009-01-21 20:05:03 +01:00
Loic d'Anterroches
3fbac71f04 Fixed to redirect the user to the home if no available branch.
This is a short term fix. We need to create a "howto" page for each kind
of repository to display it when the user want to access a repository
with nothing in it.
2009-01-21 19:56:47 +01:00
Loic d'Anterroches
49c8510d31 Fixed the rights in the sync file. 2009-01-21 19:29:03 +01:00
Loic d'Anterroches
eb90d46eb8 Improved the documentation of the git backend. 2009-01-21 19:27:56 +01:00
Loic d'Anterroches
848ec329da Added a note about the distribution packaged PEAR packages. 2009-01-21 19:27:16 +01:00
Loic d'Anterroches
037b9b78ae Fixed issue 106, crash with the cache and subversion. 2009-01-21 19:25:49 +01:00
Loic d'Anterroches
388a98defd Improved the default description to guide the administrator. 2009-01-20 17:39:53 +01:00
Loic d'Anterroches
28432296bf Fixed the bad static call. 2009-01-20 17:37:37 +01:00
Loic d'Anterroches
5d3ce34c4b Fixed to correctly update the git daemon export flag as needed. 2009-01-20 17:31:55 +01:00
Loic d'Anterroches
8cfc5ec026 Fixed the download of a commit diff file.
Note, we need to stream this commit diff like for the archive for
performance reasons.
2009-01-20 16:33:36 +01:00
Loic d'Anterroches
e159185465 Restructured the control of the large commits. 2009-01-20 16:26:36 +01:00
Loic d'Anterroches
df086f7a61 Improved Subversion and Mercurial backend performance. 2009-01-20 13:12:17 +01:00
Loic d'Anterroches
bab9ec661d Updated the French translations. 2009-01-20 11:45:26 +01:00
Loic d'Anterroches
1327e5f1e3 Added the documentation for the git synchronization. 2009-01-20 11:24:38 +01:00
Loic d'Anterroches
ef197fc6ab Added a note about the plugins in the default conf file. 2009-01-20 10:36:41 +01:00
Loic d'Anterroches
61f53c518f Added the latest elements of the git repositories control.
Now, need to write the documentation.
2009-01-20 10:33:56 +01:00
Loic d'Anterroches
e21b4d87b9 Removed the need to set a configuration variable. 2009-01-19 20:54:49 +01:00
Loic d'Anterroches
941a495144 Added the bulk of the access control to the git repositories. 2009-01-19 20:44:03 +01:00
Loic d'Anterroches
b2ec9bb9e8 Added cache at the view level when displaying the source tree. 2009-01-18 10:17:39 +01:00
Loic d'Anterroches
835eab9c24 Fixed issue 93 by preventing the display of a large commit diff.
Still needs to implement the equivalent for subversion and mercurial,
maybe with a simple command $scm->isCommitLarge($commit).
2009-01-17 18:46:26 +01:00
Loic d'Anterroches
61bc7a70b6 Solved most of issue 93 by not requesting diff content most of the time.
Still need to handle the display of a large commit in the individual
commit page.
2009-01-17 18:10:55 +01:00
Loic d'Anterroches
48355417d7 Remove cariage returns in the SSH keys. 2009-01-17 16:37:14 +01:00
Benjamin Jorand
7e7b5a4409 Fixed issue in the display of filename with spaces with Mercurial.
The Mercurial backend was not displaying correctly the filenames/folders
having spaces in them. It was troncating at the space.
2009-01-17 10:03:10 +01:00
Loic d'Anterroches
419601bb92 Added the upload of the SSH key for the end user. 2009-01-14 23:05:52 +01:00
Loic d'Anterroches
7f32c6f377 Added details on how to use a SMTP server with SSL. 2009-01-14 21:52:59 +01:00
Loic d'Anterroches
00f3b08ec6 Started the work on issue 3, git synchronization. 2009-01-14 21:23:52 +01:00
Loic d'Anterroches
509d6b6aa9 Added the option to follow symlinks in the default .htacces. 2009-01-13 10:05:01 +01:00
Loic d'Anterroches
11ddf00062 Updated the default configuration to match the INSTALL file. 2009-01-12 22:37:36 +01:00
Loic d'Anterroches
fd183d60e8 Fixed the path in the bootstrap file. 2009-01-12 21:36:05 +01:00
Loic d'Anterroches
3f31d37955 Correctly use the url_upload configuration variable. 2009-01-12 15:31:04 +01:00
Loic d'Anterroches
c8e523d8c7 Added some extra signals useful to customize the project creation. 2009-01-12 14:57:09 +01:00
Loic d'Anterroches
da524146e9 Added the upgrade-all command before installing the other PEAR packages. 2009-01-11 16:30:47 +01:00
Loic d'Anterroches
24632818e0 Fixed issue 98, better upgrade procedure. 2009-01-09 15:51:25 +01:00
Loic d'Anterroches
a6c42120d8 Improved issue 93, memory usage.
I have been doing tracing of the memory usage using xdebug, now I am
normally below 15MB for the linux kernel instead of 35MB. Please test.
2009-01-06 22:56:02 +01:00
Loic d'Anterroches
a30a62d48f Fixed issue 97, project created as private is not private.
It was not a security issue, just a display issue.
2009-01-06 22:04:48 +01:00
Loic d'Anterroches
6e5c8b5bb3 Better font definition. 2009-01-02 22:56:15 +01:00
Loic d'Anterroches
1cf84c3829 Added the sans-serif font family as default font. 2009-01-02 22:53:46 +01:00
Loic d'Anterroches
a24b1f798d Added Julien Issler in the contributors. 2009-01-02 22:53:15 +01:00
Loic d'Anterroches
52abaf0461 Updated the French translations. 2009-01-02 22:25:28 +01:00
Loic d'Anterroches
c949672e53 Fixed to allow the dash (-) in the shortname of a project.
The dash is not allowed as first or last character of the name.
2009-01-02 17:26:52 +01:00
Loic d'Anterroches
c8d1b66c91 Better alignment in the table columns. 2009-01-02 14:06:50 +01:00
Loic d'Anterroches
60bc06f5c3 Added ticket 90, propose to create a new documentation page when it doesn't exists. 2009-01-02 13:58:55 +01:00
Loic d'Anterroches
a36e89ac2e Added a little note that accounts with bad emails can be deactivated. 2009-01-02 13:39:43 +01:00
Loic d'Anterroches
36f29dfbfd Removed the dead screen of the administration as not used for the moment.
The first tab of the administration was not used, so I removed it to
remove the clutter, it will be reactivated when it will be possible to
perform forge configuration from there.
2009-01-02 13:34:09 +01:00
Loic d'Anterroches
dc5cdd74af Added the ability to set the staff flag of a user. 2009-01-02 12:07:41 +01:00
Loic d'Anterroches
dd6b6c9ce6 Fixed little glitch in the listing of the users. 2009-01-02 11:58:38 +01:00
Loic d'Anterroches
76cf64369d Inforce the rule to have only letters and digits in the login. 2009-01-02 11:43:15 +01:00
Loic d'Anterroches
404e2cd150 Added the filtering of the non validated users by default. 2009-01-02 11:38:35 +01:00
Loic d'Anterroches
3aaf24d3bb Added the base administration of the users.
Still need to not show the non activated accounts by default.
2009-01-02 11:20:10 +01:00
Loic d'Anterroches
2bfa4478e1 Added the ability to mark a project as private at creation time. 2009-01-01 22:11:23 +01:00
Loic d'Anterroches
99f82216dd Added ticket 95, ability to get a new password when forgotten. 2009-01-01 21:50:16 +01:00
Julien Issler
c33b271519 Fixed issue 96, errors when parsing a Mercurial diff. 2008-12-30 18:21:04 +01:00
Loic d'Anterroches
2078d69a83 Added ticket 86, preview attached files to issues. 2008-12-30 18:00:59 +01:00
Loic d'Anterroches
e6b19a695b Fixed issue 94, commands have changed in git 1.6. 2008-12-23 22:43:09 +01:00
Loic d'Anterroches
0a02916e81 Added a partial fix of issue 93 to limit memory exhaustion.
This is not perfect because it means that we cannot get the
corresponding commit message and author for each file in the tree view.
This is just a work around that will not affect most of the repositories
but the biggest ones with files not changes for a long time.

To fully fix this problem, one needs to build at each commit time a
cache table with the data of each commit (including the hash of each
file at each commit).
2008-12-23 22:29:11 +01:00
Loic d'Anterroches
4f682c2e93 Fixed to prevent entering empty bug reports. 2008-12-23 11:20:08 +01:00
Loic d'Anterroches
d292678759 Added the initialisation of the user language at registration time.
When the user register, we grab the language from the browser and use it
as first value for the corresponding user object. The user can then
later on modify it from his account area.
2008-12-19 11:46:46 +01:00
Loic d'Anterroches
f6fb6c5ccc Fixed inconsistency in the link. 2008-12-19 11:32:21 +01:00
Loic d'Anterroches
d2323c6d97 Added the language selection in the user account. 2008-12-19 11:30:50 +01:00
Loic d'Anterroches
d11b107ce1 Added more text extensions and give the ability to add in the config.
Using the 'idf_extra_text_ext' configuration variable you can add more
text extensions.
2008-12-17 15:08:51 +01:00
Loic d'Anterroches
e282c65bb0 Added more text extensions. 2008-12-17 14:34:55 +01:00
Benjamin Jorand
dbd513b24e Fixed to be consistent in the display of errors. 2008-12-17 09:31:22 +01:00
Julien Issler
e535fbf5e1 Fixed issue 88, Mercurial support doesn't show all directories. 2008-12-17 09:02:45 +01:00
Loic d'Anterroches
31bd7e1d19 Fixed grammar in French translations. 2008-12-10 11:33:40 +01:00
Benjamin Jorand
2418645604 Fixed issue 83, small issue in SyncSvn. 2008-12-08 10:00:46 +01:00
Loic d'Anterroches
68310e3ac5 Fixed to match the new path. 2008-12-08 09:58:01 +01:00
Benjamin Jorand
0eeef34908 Added the Mercurial repository serving synchronization.
This fixes ticket 79.
2008-12-08 09:56:31 +01:00
Loic d'Anterroches
023cd73ebf Added link to documentation for the Subversion synchronization. 2008-12-07 20:44:26 +01:00
Loic d'Anterroches
5886aef3ad Added the terms and conditions.
These are simple terms, just to allow people to adapt them later.
2008-12-07 14:56:06 +01:00
Loic d'Anterroches
ef3adafa02 Added the CSRF control by default in the configuration. 2008-12-07 12:58:44 +01:00
Loic d'Anterroches
7d999107b2 Added automatic case lowering of the login.
This is just a little usability improvement based on user feedback.
2008-12-07 11:42:02 +01:00
Loic d'Anterroches
4add76dbc8 Fixed a little consistency issue in the timeline subtab link. 2008-12-07 10:01:17 +01:00
Loic d'Anterroches
8378da5353 Fixed to not display the hashed password in the password field. 2008-12-07 09:46:08 +01:00
Loic d'Anterroches
e8d55a5267 Fixed include the File_Passwd file only if the SyncSvn plugin is activated. 2008-12-07 09:34:52 +01:00
Loic d'Anterroches
67a38fd547 Updated the French translations. 2008-12-06 14:03:35 +01:00
Loic d'Anterroches
f1f7d635b7 Updated to send to the dashboard when clicking on the "me" links. 2008-12-05 20:41:11 +01:00
Loic d'Anterroches
05c8d321c1 Added developer/user dashboard for all the projects.
This fixes issue 60.
2008-12-05 20:37:24 +01:00
Loic d'Anterroches
119aa4505e Added emphasis on the latest update link. 2008-12-05 15:04:46 +01:00
Loic d'Anterroches
a727285ce4 Added links to the author profile. 2008-12-05 14:58:32 +01:00
Loic d'Anterroches
e208a3e1eb Fixed to be usable outside of a request context. 2008-12-05 14:55:11 +01:00
Loic d'Anterroches
6c5da01319 Added the link to the author profile when possible. 2008-12-05 14:51:51 +01:00
Loic d'Anterroches
b660c6782f Updated the timeline to link to the author profile. 2008-12-05 12:28:58 +01:00
Loic d'Anterroches
5bd4fed8f4 Updated to simplify the configuration.
We do not need to have the extra tags and modifiers in the configuration
as a signal is taking care of that. That way we can add new tags,
modifiers without the need to update the configuration settings.
2008-12-05 11:53:22 +01:00
Loic d'Anterroches
6ae032df3e Fixed to correctly get a reference to the parameters. 2008-12-05 11:52:14 +01:00
Loic d'Anterroches
a1eeb12516 Added ticket 80, scm login integration with database login.
Based on the login for Subversion and the email address for git and
Mercurial.
2008-12-05 11:34:02 +01:00
Loic d'Anterroches
d6e3b8dca9 Cleaned the atom feed to validate against the feedvalidator. 2008-12-04 14:37:46 +01:00
Loic d'Anterroches
2e3330a8ad Fixed to avoid creation of unvalid HTML. 2008-12-04 14:29:45 +01:00
Loic d'Anterroches
a2ea924448 Added the File_Passwd dependency in the documentation. 2008-12-03 23:33:00 +01:00
Loic d'Anterroches
b60aeb0ca1 Updated the plugin to match InDefero's coding standards. 2008-12-03 23:02:59 +01:00
Baptiste Michaud
39cf9f985a Added a plugin to synchronize subversion repositories. 2008-12-03 21:04:32 +01:00
Loic d'Anterroches
7cc244ec06 Fixed to avoid double slashes in the path to folders. 2008-12-03 20:58:06 +01:00
Loic d'Anterroches
9c44bc5fe5 Fixed issue 71, add atom feeds.
The basic building block is here with the feed of the timeline, it is
rather easy to add the feed other elements based on this work. This will
require iterations and polishing.
2008-12-03 15:00:47 +01:00
Loic d'Anterroches
a79d13ee3f Fixed issues 64 and 65, add a short description for the projects.
Bulk of the changes were made in commit 209715a.
2008-12-02 13:23:17 +01:00
Loic d'Anterroches
209715a6b3 Increased the size of the edition area of the documentation pages. 2008-12-02 13:22:01 +01:00
Loic d'Anterroches
2bd74621ce Added a link to more documentation in the installation readme. 2008-12-02 12:53:23 +01:00
Loic d'Anterroches
ec22b2c8c2 Grouped the items in the timeline by day. 2008-12-01 22:16:28 +01:00
Loic d'Anterroches
da23f2b915 Added review update notification. 2008-12-01 21:39:17 +01:00
Loic d'Anterroches
fefff591b6 Tried a little improvement of the layout. 2008-12-01 18:23:18 +01:00
Loic d'Anterroches
67361d7df6 Fixed ticket 46, PHP 5.2.0 compatibility. 2008-12-01 14:48:09 +01:00
Loic d'Anterroches
7d0ecd8f8a Added a link to Gravatar in the FAQ. 2008-12-01 14:44:35 +01:00
Loic d'Anterroches
6e4aa82dad Added the support of the gravatars. 2008-12-01 14:30:40 +01:00
Loic d'Anterroches
0ae266cfaf Added to have a nicer look in the account area. 2008-12-01 14:30:00 +01:00
Loic d'Anterroches
cc49fd1dc9 Updated the documentation to take into account the new admin area. 2008-12-01 13:48:39 +01:00
Loic d'Anterroches
d1911339d7 Added signals when the user updates his password. 2008-12-01 13:35:17 +01:00
Loic d'Anterroches
9c5156e6ef Added the first work on the administration area. 2008-12-01 00:36:27 +01:00
Loic d'Anterroches
3cacf44605 Fixed to add a little margin after the logo. 2008-11-30 19:00:17 +01:00
Loic d'Anterroches
b45d9854af Fixed issue 70, markdown syntax in the Wiki partially broken.
We will need to iterate with feedback from the users for the autorized
tags in the filtering process.
2008-11-30 11:33:46 +01:00
Loic d'Anterroches
2732da9dbb Fixed possible fatal error in the Wiki.
The error could be triggered only when playing with the URL manually.
2008-11-30 11:18:58 +01:00
Loic d'Anterroches
57a4ea7cb0 Fixed ticket 76, add the attached files in the issue notification emails.
Beware that you need to update your configuration file to add the new
template filter.
2008-11-30 10:52:06 +01:00
Loic d'Anterroches
81cb427f7f Fixed to be consistent with the rest. 2008-11-30 10:37:40 +01:00
Loic d'Anterroches
8941889a17 Merge branch 'codereview'
Conflicts:

	src/IDF/templates/idf/issues/create.html
2008-11-30 10:29:24 +01:00
Loic d'Anterroches
f690968b11 Started ticket 39, add code review.
We now have a limited support of the code review. Still some work to be
done to allow the submission of new patches on a given review and update
the status. For the moment, only pre-commit review is supported.
2008-11-30 10:26:05 +01:00
Loic d'Anterroches
65b0899d4a Fixed bad block in the template. 2008-11-27 21:47:40 +01:00
Loic d'Anterroches
fbe364462d Fixed issue 67, conf and Conf folders conflict. 2008-11-27 11:29:40 +01:00
Loic d'Anterroches
45d1422ab0 Added upgrade procedure. 2008-11-27 11:23:38 +01:00
Loic d'Anterroches
0273e535e0 Added multiple upload file in issue.
This fixes issue 54.
2008-11-27 11:10:23 +01:00
Loic d'Anterroches
566c90cf6a Added full debug of Scm call only when debug flag set.
This is because git will return an error code when testing if a commit
exists and in fact does not exist. So it means that we get an exception
on all the string "commit foo" when foo is not a commit in the issue
text.
2008-11-27 10:03:30 +01:00
Loic d'Anterroches
eb60e99d5c Fixed to have correct post failure cleaning of the uploaded file.
Note: Latest Pluf release needed.
2008-11-27 09:48:01 +01:00
Loic d'Anterroches
93c44feb05 Fixed issue 61, better sync of the source changelog with the latest updates.
The sync is made automatically on all the branches of the project when
looking at the timeline after a given time.
2008-11-27 09:30:12 +01:00
Loic d'Anterroches
08f6c5fc79 Improved a little bit the keyboard shortcuts. 2008-11-27 09:18:33 +01:00
Loic d'Anterroches
83fd312c81 Added an exception when the scm command fails.
This will help the debugging for people.
2008-11-27 09:03:21 +01:00
Loic d'Anterroches
f9b6a022c8 Fixed the link colors. 2008-11-26 22:35:36 +01:00
Loic d'Anterroches
71461299bc Fixed the listing of the projects in the public profile view. 2008-11-26 22:29:10 +01:00
Loic d'Anterroches
ed40edf594 Updated the French translations. 2008-11-26 22:14:51 +01:00
Loic d'Anterroches
c61b5c2cbe Improved a little bit more the installation documentation. 2008-11-26 22:08:12 +01:00
Loic d'Anterroches
22fec9f732 Forced the default link colors.
This is needed for people using custome style in their browser. This
allows correct display in that case.
2008-11-26 21:51:16 +01:00
Loic d'Anterroches
1332ba7eda Improved the documentation based on user feedback. 2008-11-26 21:34:47 +01:00
Loic d'Anterroches
28b650f841 Added the ability to configure the path to the mime types db. 2008-11-26 11:12:25 +01:00
Loic d'Anterroches
628b01faf4 Fixed the indexing for the issues.
It was not indexing the content correctly when creating the issue as the
content of the issue was not yet stored in the database.
2008-11-26 10:08:51 +01:00
Benjamin Jorand
4bff745890 Fixed to finish the code cleaning. 2008-11-26 08:36:01 +01:00
Loic d'Anterroches
82ed55f1a0 Fixed issue 55, improvements for the wiki. 2008-11-25 21:59:57 +01:00
Loic d'Anterroches
b528bf356c Merge branch 'master' into betterwiki 2008-11-25 21:21:40 +01:00
Loic d'Anterroches
c02c52b14a Fixed some call-time pass-by-reference warnings with PHP 5.2.6. 2008-11-25 21:18:12 +01:00
Loic d'Anterroches
86ab5221a7 Added view of pages by label. 2008-11-25 21:07:51 +01:00
Loic d'Anterroches
7a0a0b523b Fixed the right action when listing by tag the downloads. 2008-11-25 20:45:27 +01:00
Loic d'Anterroches
c0918de3dc Improved the phrasing. 2008-11-25 20:36:09 +01:00
Loic d'Anterroches
fd9cb62946 Added documentation wiki search. 2008-11-25 20:32:33 +01:00
Loic d'Anterroches
4dc0747769 Added the download of a commit diff.
This fixes issue 50.
2008-11-25 20:11:09 +01:00
Loic d'Anterroches
6bee793704 Added permission at the installation/upgrade for cleaner code.
The authorized user permission is added at the installation/upgrade step
to remove the code to remove the unncessary logic in the code.
2008-11-25 09:38:10 +01:00
Loic d'Anterroches
70616a0c95 Improved the translations based on user feedback. 2008-11-24 22:10:36 +01:00
Loic d'Anterroches
8519303494 Fix to have a more logical order in the HTML. 2008-11-24 21:56:04 +01:00
Loic d'Anterroches
ad159a5463 Fixed the title to be correctly titlecased. 2008-11-24 21:52:24 +01:00
Loic d'Anterroches
39d015a586 Added a simpler way to configure the repositories. 2008-11-24 20:27:03 +01:00
Loic d'Anterroches
9d1a4cad01 Updated the documentation based on user feedback. 2008-11-24 10:50:37 +01:00
Loic d'Anterroches
bebce1812d Fixed to handle exception nicely instead of crashing the view. 2008-11-23 21:09:44 +01:00
Loic d'Anterroches
4260a083be Updated the French translations. 2008-11-23 18:51:24 +01:00
Loic d'Anterroches
335abc3a41 Updated the authors and the contributions. 2008-11-23 17:47:35 +01:00
Benjamin Jorand
08145b7b1c Added the support of Mercurial. 2008-11-23 17:45:00 +01:00
Loic d'Anterroches
ee8d56075d Fixed to get consistent listing order. 2008-11-23 17:14:23 +01:00
Loic d'Anterroches
7f88056bd9 Fixed autolinking breaking MarkDown links. 2008-11-23 14:58:43 +01:00
Loic d'Anterroches
39916d46f0 Added the featured documentation pages on the homepage. 2008-11-23 14:51:47 +01:00
Loic d'Anterroches
b7b7e7aff2 Fixed to use the right initialized labels. 2008-11-23 14:44:44 +01:00
Loic d'Anterroches
380df7380b Fixed the regex to match the commit and the path. 2008-11-23 14:41:41 +01:00
Loic d'Anterroches
a643cbbc0c Fixed to use the new class name. 2008-11-23 14:35:39 +01:00
Loic d'Anterroches
649e85e551 Fixed bad relate name conflicting with the IDF_Issue. 2008-11-23 14:34:45 +01:00
Loic d'Anterroches
7c1ad62cdb Added the deletion of old revisions of the wiki pages. 2008-11-23 12:33:11 +01:00
Loic d'Anterroches
048e2ba783 Fixed to be translated. 2008-11-23 12:13:04 +01:00
Loic d'Anterroches
b3eb42817c Fixed to correctly clean the index and timeline when needed. 2008-11-23 12:00:15 +01:00
Loic d'Anterroches
78bc9a5466 Remove from the index on delete. 2008-11-23 11:26:17 +01:00
Loic d'Anterroches
b03422fc18 Fixed function name and add the timeline cleaning for wikirev/page deletion. 2008-11-23 11:23:20 +01:00
Loic d'Anterroches
99dd3aa1d6 Remove the corresponding information from the timeline on deletion. 2008-11-23 11:20:08 +01:00
Loic d'Anterroches
d9ffac099f Added the visualisation of the old revisions of a page. 2008-11-23 11:12:16 +01:00
Loic d'Anterroches
8eb5715656 Partial fix of issue 55, addition of a simple Wiki.
Added a base wiki, it is now possible to create wiki pages and update
them. Revisions are kept also not used/displayed at the moment.
2008-11-22 23:51:23 +01:00
Loic d'Anterroches
b13633fed2 Fixed to work correctly with MySQL. 2008-11-22 22:06:02 +01:00
Loic d'Anterroches
c86cf6e5f1 Fixed issue 62, crash when searching in the issues.
The issue search is now correctly limiting the search to the issues.
2008-11-22 14:17:26 +01:00
Loic d'Anterroches
d8870e6df0 Added the installation of the IDF_Commit table. 2008-11-22 10:59:15 +01:00
Loic d'Anterroches
b94ca8215f Fixed to not cache the getBlob call with git. 2008-11-21 21:47:43 +01:00
Loic d'Anterroches
f674992fc8 Updated the French translations. 2008-11-21 21:14:37 +01:00
Loic d'Anterroches
6072c2f9cb Fixed a little cosmetic glitch. 2008-11-21 20:41:32 +01:00
Loic d'Anterroches
9814a75f82 Added the first work on an API. 2008-11-21 20:33:39 +01:00
Loic d'Anterroches
0e725bea26 Added private projects.
It is now possible to create private projects. To mark a project as
private, you simply go in the Administer > Tabs Access menu and select
"Private project". Only project members and owners together with the
extra authorized users will be able to access the project. The project
will not appear in the list of projects for not authorized users.
2008-11-21 13:19:02 +01:00
Loic d'Anterroches
80b9e2ff78 Fixed issue 59, field idf_idf_conf.vdesc is a varchar(250) instead of text (MySQL). 2008-11-20 10:05:40 +01:00
bohwaz
c807c18d21 Fixed issue 58, bug avec les raccourcis clavier. 2008-11-18 11:29:53 +01:00
bohwaz
e01235caea Fixed issue 57, bug dans la gestion SVN. 2008-11-18 11:23:09 +01:00
Loic d'Anterroches
79312f7242 Fixed some bad path to templates. 2008-11-18 10:58:54 +01:00
Loic d'Anterroches
7e1f5bb029 Fixed path to the login form. 2008-11-18 09:59:11 +01:00
Loic d'Anterroches
18da9ed72f Renamed to match Pluf conventions and fix issue 56. 2008-11-18 09:21:49 +01:00
Loic d'Anterroches
725ece26cd Fixed in part issue 56, the templates are in the idf subfolder. 2008-11-18 09:15:02 +01:00
Loic d'Anterroches
8e093bc7ee Added link to issues and commit in the source tree. 2008-11-17 14:09:00 +01:00
Loic d'Anterroches
809967f5d7 Updated the hotkeys to add the latest updates into them. 2008-11-17 14:01:24 +01:00
Loic d'Anterroches
3ae666a781 Fixed the layout for long names in the attachments. 2008-11-16 12:19:30 +01:00
Loic d'Anterroches
97425529b1 Fixed to have the short name of the subject instead of InDefero. 2008-11-16 12:11:13 +01:00
Loic d'Anterroches
7ca43e0ec2 Updated the pretty print not to need extra <pre></pre>. 2008-11-15 21:23:29 +01:00
Nicolas Lassalle
5c32145e13 Added diff pretty print in the commit. 2008-11-15 21:10:37 +01:00
Nicolas Lassalle
99a2e2b83f Fixed issue 37, syntax highlighting of source. 2008-11-15 15:44:47 +01:00
Loic d'Anterroches
316a7d1a54 Fixed wrong display of the comment author. 2008-11-15 15:09:07 +01:00
Loic d'Anterroches
17734a7482 Fixed the class of some issue links. 2008-11-15 15:01:12 +01:00
Loic d'Anterroches
619334db67 Fixed the date to be the one of the comment and not the issue. 2008-11-15 12:26:21 +01:00
Loic d'Anterroches
42df3f5a74 Using normal storage instead of compressed. 2008-11-15 12:16:39 +01:00
Loic d'Anterroches
5370939229 Fixed the indenting. 2008-11-15 11:57:17 +01:00
Loic d'Anterroches
bcaf6efb6a Added the downloads in the timeline. 2008-11-15 11:56:44 +01:00
Loic d'Anterroches
8336dd6549 Updated to make the timeline follow the access rules. 2008-11-15 11:47:09 +01:00
Loic d'Anterroches
c044e99960 Renamed timeline to updates as more intuitive.
Fixed other little consistency stuff.
2008-11-15 11:35:30 +01:00
Loic d'Anterroches
4760c4b563 Improved the style of the timeline to add consistency.
The timeline is now a table with a more consistent look and feel with
respect to the other tables in the application.
2008-11-15 11:31:43 +01:00
Loic d'Anterroches
b85da85dfe Added ticket 45, base implementation of a timeline.
Still some cleaning of the code to have a nicer display of the timeline
especially for the issue updates.
2008-11-14 15:41:51 +01:00
Loic d'Anterroches
386ff894fc Cleaned the view of a commit.
Removed the unnecessary "tree" information.
Added direct link to the files at the given commit.
2008-11-13 14:19:18 +01:00
Loic d'Anterroches
a5acd5d6ca Improved the documentation based on a LinuxFr comment. 2008-11-13 08:52:56 +01:00
Loic d'Anterroches
431654592d Updated the French translation. 2008-11-12 21:02:57 +01:00
Loic d'Anterroches
fa5fe0d610 Fixed ticket 36, attach a file to a ticket.
It is now possible to attach a file to a ticket.
2008-11-12 20:40:58 +01:00
Loic d'Anterroches
9404309a0a Fixed issue 43 by removing the logo.
We do not need to do some advertizing on all the pages. Logo removed, the interface is
cleaner. Yeah!
2008-11-12 13:57:47 +01:00
Loic d'Anterroches
de8b5aa74c Improved the visualisation of text files in the source.
If the visualized version is not at the head of a branch, the details of
the corresponding commit are given.
2008-11-11 22:55:35 +01:00
Loic d'Anterroches
9ac6e38e81 Added inline visualization of text files from the repository. 2008-11-11 22:32:01 +01:00
Loic d'Anterroches
297d7290df Fixed partially issue 37, text files are displayed as text.
The display is not done with a nice layout, the file is pushed directly
with the text/plain mime type. This is working better than expected.
2008-11-11 21:21:21 +01:00
Loic d'Anterroches
cf01b5f3df Added .htaccess example for Apache. 2008-11-11 11:40:29 +01:00
Loic d'Anterroches
9e91215205 Fixed to avoid a line break between size numbers and units. 2008-11-10 22:47:59 +01:00
Loic d'Anterroches
36623eaf94 Added the default cache configuration. 2008-11-10 15:32:59 +01:00
Loic d'Anterroches
adb5de13e2 Added a cache layer to cache the execution of the scm commands. 2008-11-07 23:54:40 +01:00
Loic d'Anterroches
c113c11da5 Added the display of the author of a commit in the source tree. 2008-11-07 14:32:22 +01:00
Loic d'Anterroches
c797e6c7c3 Fixed minor compatibility issue with PHP 5.2. 2008-11-07 14:31:45 +01:00
Loic d'Anterroches
ad5348e7f5 Fixed issue 42, add projects list link in project page.
At the top, a link to the project list is added when within a project
page.
2008-09-12 16:31:49 +02:00
Loic d'Anterroches
cecae05bda Fixed ticket 40 View Projects: implode functions seems not support ArrayObject
Forced the arrayObject as array with casting.
2008-09-12 16:23:43 +02:00
Nicolas Lassalle
8ae2ae2b06 [PATCH] Fixed the source url in js-hotkeys 2008-09-12 12:38:13 +02:00
Loic d'Anterroches
2b5f97b362 Cleaned the code to have consistent style. 2008-09-12 12:36:41 +02:00
Nicolas Lassalle
b881ad1c8f [PATCH] Restructured the "source views" file hierarchy.
Svn specifics Views are now in a Source subfolder.
2008-09-12 12:32:27 +02:00
Nicolas Lassalle
bd15328758 [PATCH] Fixed issue 41 - View Source : Missing argument 2 for IDF_Scm_Svn::getBlob()
Created an unique method declaration in the tow scm backend.
Passed a more generic argument so that the scm backend could get the
correct data.
2008-09-12 12:26:51 +02:00
Loic d'Anterroches
e4f7dd8975 Continued work to fix the support of PHP 5.2.0. 2008-09-05 15:53:13 +02:00
Loic d'Anterroches
c7fc30bab0 Continued work to fix for PHP 5.2.0. 2008-09-05 15:35:27 +02:00
Loic d'Anterroches
f4cbf84559 Added more fixes for PHP 5.2.0. 2008-09-05 14:38:50 +02:00
Loic d'Anterroches
c8e91d80f2 Forced as ContextVars for PHP 5.2. 2008-09-05 10:45:01 +02:00
Loic d'Anterroches
57084c6a25 Removed dead code. 2008-09-05 10:14:47 +02:00
Loic d'Anterroches
eeac44303b Added PEAR prerequisites. 2008-09-04 17:54:52 +02:00
Loic d'Anterroches
61b109d75e Really fixed the project name with hyphen (-). 2008-09-04 17:38:29 +02:00
Loic d'Anterroches
7dabb32e25 Fixed bug with project names containing an hyphen (-). 2008-09-04 17:19:53 +02:00
Loic d'Anterroches
bef8ac82b7 Updated to be clearer to for the first installation. 2008-09-04 15:30:39 +02:00
Loic d'Anterroches
7e3ca3cff8 Fixed a path in the proposed bootstrap file. 2008-09-04 15:11:23 +02:00
Loic d'Anterroches
6521680719 Added the French translations.
Fixed at the same time some bad English strings.
2008-09-03 09:33:29 +02:00
Loic d'Anterroches
28817af471 Removed dead code. 2008-09-03 09:13:50 +02:00
Loic d'Anterroches
5b9dbd5c3b Fixed bug preventing the download. 2008-09-03 00:12:25 +02:00
Loic d'Anterroches
2acf2fb054 Removed the sort by id on the closed issues. 2008-09-03 00:06:24 +02:00
Loic d'Anterroches
14811beeba Fixed issue 9, do not show the deprecated files by default. 2008-09-02 23:31:43 +02:00
Loic d'Anterroches
ee88dbed86 Added an author file.
I am always bad at tracking contributors, so this time I am doing it
correctly right from the start.
2008-09-02 17:55:54 +02:00
Loic d'Anterroches
0ff5eb0f82 Fixed issue 34 in the case of bad commit in the changelog. 2008-09-02 17:41:54 +02:00
Loic d'Anterroches
3a3aa9c730 Added the administration of the repository.
It is now possible to select the repository type and a possible remote
repository for subversion in the administration area.
2008-09-02 16:45:49 +02:00
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
283 changed files with 40864 additions and 1197 deletions

4
.gitignore vendored
View File

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

29
AUTHORS Normal file
View File

@@ -0,0 +1,29 @@
InDefero was originally created during summer 2008
by Loïc d'Anterroches with the support of Céondo Ltd.
Much appreciated contributors:
Nicolas Lassalle <http://www.beroot.org/> - Subversion support
bohwaz <http://bohwaz.net/>
Benjamin Jorand <benjamin.jorand@gmail.com> - Mercurial support
Baptiste Michaud <bactisme@gmail.com> - Subversion synchronization
Julien Issler
Manuel Eidenberger <eidenberger@gmail.com>
Ciaran Gultnieks
Mehdi Kabab <http://pioupioum.fr/>
Sindre R. Myren
Patrick Georgi <patrick.georgi@coresystems.de>
Adrien Bustany
Charles Melbye
Baptiste Durand-Bret
Andrew Nguyen
David Feeney
Denis Kot <denis.kot@gmail.com>
Samuel Suther
Ludovic Bellière
Brian Armstrong
Raphaël Emourgeon
And all the nice users who spent time reporting issues and promoting
the project. The project could not live without them.

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

215
INSTALL.mdtext Normal file
View File

@@ -0,0 +1,215 @@
# Quick installation instruction
The installation of InDefero is composed of 2 parts, first the
installation of the [Pluf framework](http://www.pluf.org) and second,
the installation of InDefero by itself.
## Recommended Layout of the Files
If your server document root is in `/var/www` a good thing is to keep
the number of files under the `/var/www` folder to its minimum. So,
you should create a `/home/www` folder in which we are going to
install all but the files which need to be available under the
document root.
/home/www/pluf/src/
/home/www/pluf/src/Pluf.php
/home/www/pluf/src/migrate.php
/home/www/indefero/src
/home/www/indefero/www
/home/www/indefero/www/index.php
/home/www/indefero/www/media
The you need to link the `media` and `index.php` files into your
docroot.
$ cd /var/www
$ ln -s /home/www/indefero/www/index.php
$ ln -s /home/www/indefero/www/media
## Installation of Pluf
* Checkout the trunk of [Pluf](http://www.pluf.org).
* Install the `Mail` and `Mail_mime` classes from [PEAR](http://pear.php.net). You must use the `--alldeps` flag when installing these modules.
**Pear install/upgrade:**
$ sudo pear upgrade-all
$ sudo pear install --alldeps Mail
$ sudo pear install --alldeps Mail_mime
If you already have some of the PEAR packages installed with your
distribution, the `Mail` package is often not up-to-date,
[read more here](http://projects.ceondo.com/p/indefero/issues/104/#ic347).
The Pluf installation folder is the folder containing the file `Pluf.php`.
## Installation of InDefero
The installation is composed of the following steps:
* Get the InDefero archive.
* Configure it correctly.
* Installation the database with the `migrate.php` script.
* Bootstrap the application with a `bootstrap.php` script.
Here is the step-by-step installation procedure:
* Extract the InDefero archive somewhere.
* The InDefero installation folder is the folder containing this file INSTALL.mdtext.
* 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.
* Make a copy of `src/IDF/conf/path.php-dist` as `src/IDF/conf/path.php`.
* Update the path.php file to match your installation paths. It should work out of the box if you followed the recommended file layout.
* Open a terminal/shell and go into the `src` folder in the InDefero installation folder.
**Command:**
$ cd /home/www/indefero/src
* Run `php /home/www/pluf/src/migrate.php --conf=IDF/conf/idf.php -a -i -d -u` to test the installation of the tables.
* Run `php /home/www/pluf/src/migrate.php --conf=IDF/conf/idf.php -a -i -d` to really install the tables.
* More details about the migration is available in the [migration documentation](http://pluf.org/doc/migrations.html) of the Pluf framework.
* Create a bootstrap file to create the admin user for example `www/bootstrap.php`. Do not forget to update the second line with your path to Pluf.
**Bootstrap script:**
<?php
require '/home/www/indefero/src/IDF/conf/path.php';
require 'Pluf.php';
Pluf::start('/home/www/indefero/src/IDF/conf/idf.php');
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
$user = new Pluf_User();
$user->first_name = 'John';
$user->last_name = 'Doe'; // Required!
$user->login = 'doe'; // must be lowercase!
$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 www/bootstrap.php`.
* Remove the `www/bootstrap.php` file.
* Open the `www/index.php` file and ensure that the path to Pluf and
Indefero are correctly set for your configuration.
* Now you can login with this user into the interface.
* Click on the Forge Management link on top and create your first project.
## Upgrade InDefero
To upgrade:
* Make a backup of your data, including the database.
* Extract the new archive on top of the current one.
* Update your version of Pluf.
* Check that the path in the `index.php` are still good.
* Remove all the `*.phps` files in your temp folder.
* Upgrade the database with the upgrade commands:
**Upgrade commands:**
$ cd /home/www/indefero/src
$ php /home/www/pluf/src/migrate.php --conf=IDF/conf/idf.php -a -d -u
$ php /home/www/pluf/src/migrate.php --conf=IDF/conf/idf.php -a -d
## Repository Synchronization
The documentation is available in the `doc` folder.
* Subversion: `doc/syncsvn.mdtext`.
* Mercurial: `doc/syncmercurial.mdtext`.
* Git: `doc/syncgit.mdtext`.
## For the Apache Webserver Users
If you are using [Apache](http://httpd.apache.org/) for your webserver
and want to have nice URLs like `http://yourdomain.com/p/yourproject/`
and not `http://yourdomain.com/index.php/p/yourproject/` you can use
the following `.htaccess` file to be put in the same folder of the
`www/index.php` file.
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) /index.php/$1
`Options +FollowSymLinks` is only needed if you are using symlinks.
## For the Gentoo users
If you get the error:
T_CHARACTER Use of undefined constant T_CHARACTER - assumed 'T_CHARACTER'"
you need to compile PHP with the "tokenizer" flag.
## For People with open_basedir restriction error
If you get an error like:
file_get_contents(): open_basedir restriction in effect.
File(/etc/mime.types) is not within the
allowed path(s): (/srv/http/:/home/:/tmp/:/usr/share/pear/)
Just copy the file `/etc/mime.types` into the folder `/home` and put
this in your configuration file:
$cfg['idf_mimetypes_db'] = '/home/mime.types';
## FreeBSD Installation
You need to install `/usr/ports/lang/php5-extensions` which contains
the Standard PHP Library (SPL).
## Using a SMTP server with authentication
If your SMTP server requires authentication, for example,
*smtp.gmail.com*, you can use the following email configuration:
$cfg['send_emails'] = true;
$cfg['mail_backend'] = 'smtp';
$cfg['mail_auth'] = true;
$cfg['mail_host'] = 'ssl://smtp.gmail.com';
$cfg['mail_port'] = 465;
$cfg['mail_username'] = 'YOURGMAILADDRESS';
$cfg['mail_password'] = 'YOURPASSWORD';
Check with your provider to get the right settings.
## Git Daemon on Ubuntu Karmic
If you have problems getting it to run, you can follow this procedure
proposed by Mathias in ticket 369.
1. Install git-daemon-run in addition to git-core
2. Edit /etc/sv/git-daemon/run to look as follows:
#!/bin/sh
exec 2>&1
echo 'git-daemon starting.'
exec chpst -ugit:git \
/usr/lib/git-core/git-daemon \
--reuseaddr \
--syslog \
--verbose \
--base-path=/home/git/repositories \
/home/git/repositories
3. Restart git-daemon-run
sv restart git-daemon
## If Subversion is not working
If you access a Subversion server with a self-signed certificate, you
may have problems as your certificate is not trusted, check the
[procedure provided here][svnfix] to solve the problem.
[svnfix]: http://projects.ceondo.com/p/indefero/issues/319/#ic1358

113
doc/syncgit.mdtext Normal file
View File

@@ -0,0 +1,113 @@
# Plugin SyncGit by Céondo Ltd
The SyncGit plugin allow the direct creation and synchronisation of
git repositories with the InDefero database. This requires giving
access to the repositories using a dedicated SSH account, usually the
`git` account.
## Prerequisites
A good understanding of:
* the security issues related to using a SSH account on a server;
* the principle of public/private SSH keys;
* the rights/ownership of files on a Linux/BSD/nix system;
Yes, what you are going to do has security implications.
## Git user configuration
On your system, you will need to create a new `git` account. This
account will only be used to access the git repositories and at the
moment cannot be shared for other use.
First create a new git account:
$ sudo adduser \
--system \
--shell /bin/sh \
--gecos 'git version control' \
--group \
--disabled-password \
--home /home/git \
git
Then, we need to create the base SSH files with the right permissions:
$ sudo su git
$ mkdir /home/git/.ssh
$ touch /home/git/.ssh/authorized_keys
$ chmod 0700 /home/git/.ssh
$ chmod 0600 /home/git/.ssh/authorized_keys
$ exit
We add the `www-data` user to the `git` group so it can access the
repositories to read the content:
$ sudo usermod -a -G git www-data
Do not forget to restart Apache or your fastcgi process to take the
group addition into account.
## Creation of the repositories base
For each project using git in InDefero a corresponding bare repository
will be created in `/home/git/repositories`. For example, if the
shortname of your project is `wonder`, it will be created in
`/home/git/repositories/wonder.git`
$ sudo -H -u git mkdir /home/git/repositories
## InDefero Configuration
First, you need to have python installed on your system to be able to
run the very small python script `gitserve.py` in the `scripts`
folder. Here is a configuration example:
$cfg['git_repositories'] = '/home/git/repositories/%s.git';
$cfg['git_remote_url'] = 'git://yourdomain.com/%s.git';
$cfg['idf_plugin_syncgit_path_gitserve'] = '/home/www/indefero/scripts/gitserve.py'; # yes .py
$cfg['idf_plugin_syncgit_path_authorized_keys'] = '/home/git/.ssh/authorized_keys';
$cfg['idf_plugin_syncgit_sync_file'] = '/tmp/SYNC-GIT';
# Remove the git repositories which do not have a corresponding project
# This is run at cron time
$cfg['idf_plugin_syncgit_remove_orphans'] = false;
# git account home dir
$cfg['idf_plugin_syncgit_git_home_dir'] = '/home/git';
# where are going to be the git repositories
$cfg['idf_plugin_syncgit_base_repositories'] = '/home/git/repositories';
When someone will change his SSH key or add a new one, the
`/tmp/SYNC-GIT` file will be created. The cron job
`/home/www/indefero/scripts/gitcron.php` will see the file and update
the content of the `authorized_keys` file.
## Cron Job Configuration
You need to run a cron job every now and then to synchronize the SSH
keys. The command to run in the cron job is:
php /home/www/indefero/scripts/gitcron.php
The user of the cron job must be `git`.
## Git daemon configuration
Put in `/etc/event.d/local-git-daemon` the following:
start on startup
stop on shutdown
exec /usr/bin/git-daemon \
--user=git --group=git \
--verbose \
--reuseaddr \
--base-path=/home/git/repositories/ \
/home/git/repositories/
respawn
Then run:
$ sudo start local-git-daemon

96
doc/syncmercurial.mdtext Normal file
View File

@@ -0,0 +1,96 @@
# Plugin SyncMercurial by Benjamin Jorand
The SyncMercurial plugin allows the direct creation and synchronisation of
mercurial repositories with the InDefero database. The repositories will be
published by hgwebdir.cgi using HTTP. It also handles private repositories.
SyncMercurial is adapted from SyncSvn by Baptiste Michaud.
## To Contact the Author
Benjamin Jorand <benjamin.jorand@gmail.com>
## Apache configuration
The simple way to share Mercurial repositories is to publish them
using HTTP and `hgwebdir.cgi`.
It first requires a config file called hgweb.config in the same
directory where you put hgwebdir.cgi (for example,
`/home/indefero/scripts`):
[collections]
/home/indefero/repositories/mercurial/ = /home/indefero/repositories/mercurial/
Then configure a vhost this way :
ScriptAliasMatch ^/hg(.*) /home/indefero/scripts/hgwebdir.cgi$1
<Directory /home/indefero/scripts>
Options +ExecCGI
AuthName "Restricted"
AuthType Basic
AuthUserFile /home/indefero/auth/.htpasswd
<Limit PUT POST>
Require valid-user
</Limit>
</Directory>
Enable the authentification for private repositories :
Include /home/indefero/scripts/private_indefero.conf
## InDefero configuration
First, you need to install the File_Passwd PEAR package:
$ sudo pear install File_Passwd
Then, based on the paths provided in the Apache configuration, you
need to put the following lines in your configuration file:
$cfg['idf_plugin_syncmercurial_passwd_file'] = '/home/indefero/auth/.htpasswd';
$cfg['idf_plugin_syncmercurial_path'] = '/home/indefero/repositories/mercurial';
$cfg['idf_plugin_syncmercurial_private_include'] = '/home/indefero/scripts/private_indefero.conf';
$cfg['idf_plugin_syncmercurial_private_notify'] = '/home/indefero/tmp/notify.tmp';
$cfg['idf_plugin_syncmercurial_private_url'] = '/hg/%s';
You also need to provide the base definition of the hgrc file. For example:
$cfg['idf_plugin_syncmercurial_hgrc'] = array(
'web' => array('push_ssl' => 'false',
'allow_push' => '',
'description' => '',
'allow_archive' => 'bz2, zip, gz',
'style' => 'gitweb',
'contact' => ''),
'hooks' => array(),
'extensions' => array(),
);
If you are note using Apache but Nginx, you may need to create the
passwords as plain text passwords (see ticket 391). You can configure
the password storage with the format you want. The default is `sha`
you can set it to `plain` for nginx.
$cfg['idf_plugin_syncmercurial_passwd_mode'] = 'sha';
See the [`FILE_PASSWD_*` constants](http://euk1.php.net/package/File_Passwd/docs/latest/File_Passwd/_File_Passwd-1.1.7---Passwd.php.html) for more choices.
## Cron configuration
As InDefero modifies the private_indefero.conf, apache needs to be reloaded.
Each time this file is modified, a temporary file is created.
*/5 * * * * /bin/sh /home/indefero/src/scripts/SyncMercurial.sh
Edit this script and add correct values to `private_notify` and `reload_cmd`.
## Hook configuratin
To get notifications sent directly when pushing in your repositories,
you need to add the following in your `.hgrc` file. The script will be
called onec per push and will automatically send the notifications and
sync the timeline.
[hooks]
changegroup = /home/indefero/src/scripts/hgchangegroup.php

81
doc/syncsvn.mdtext Normal file
View File

@@ -0,0 +1,81 @@
# Plugin SyncSvn by Baptiste Michaud
The SyncSvn plugin allow the direct creation and synchronisation of
subversion repositories with the InDefero database. This requires
giving access to the repositories using the DAV_SVN module of Apache2.
## To Contact the Author
Baptiste Michaud
bactisme@gmail.com
webplay.fr - frandroid.com - lost-in-translation.fr
## Apache configuration
You will first need to install the DAV_SVN module for Apache. On
Debian/Ubuntu based systems just run:
$ sudo apt-get install libapache2-svn
$ sudo a2enmod dav_svn
Then, you need to configure dav_svn, this is an example of
configuration:
<Location /svn>
DAV svn
SVNParentPath /home/svn/repositories
AuthzSVNAccessFile /home/svn/dav_svn.authz
Satisfy Any
Require valid-user
AuthType Basic
AuthName "Subversion Repository"
AuthUserFile /home/svn/dav_svn.passwd
</Location>
Be sure to [read the documentation before](http://svnbook.red-bean.com/en/1.5/svn.serverconfig.httpd.html).
The files `/home/svn/dav_svn.authz`, `/home/svn/dav_svn.passwd` and
the directory `/home/svn/repositories` must be writable by your
webserver process. To ensure that, do:
$ sudo mkdir --parents /home/svn/repositories
$ sudo touch /home/svn/dav_svn.authz
$ sudo touch /home/svn/dav_svn.passwd
$ sudo chown -R www-data:www-data /home/svn
Now, you need to restart apache:
$ sudo /etc/init.d/apache2 force-reload
## InDefero Configuration
First, you need to install the File_Passwd PEAR package:
$ sudo pear install File_Passwd
Then, based on the paths provided in the Apache configuration and if
your Apache server is serving the domain `www.mydomain.com`, the you
need to put the following in your configuration file:
$cfg['svn_repositories'] = 'file:///home/svn/repositories/%s';
// We add "trunk" to invite people to checkout the trunk of the
// project.
$cfg['svn_remote_url'] = 'http://www.mydomain.com/svn/%s/trunk';
// Synchronisation specific configuration variables
$cfg['idf_plugin_syncsvn_authz_file'] = '/home/svn/dav_svn.authz';
$cfg['idf_plugin_syncsvn_passwd_file'] = '/home/svn/dav_svn.passwd';
$cfg['idf_plugin_syncsvn_svn_path'] = '/home/svn/repositories';
// Delete the corresponding repository when deleting the project
$cfg['idf_plugin_syncsvn_remove_orphans'] = false;
You can have more control over the permissions given to the owners,
members, extra authorized users and anonymous users if you want with
the following configuration variables:
* **idf_plugin_syncsvn_access_owners ('rw')**: Access for the project owners.
* **idf_plugin_syncsvn_access_members ('rw')**: Access for the project members.
* **idf_plugin_syncsvn_access_extra ('r')**: Access for the extra authorized people in case of a private project.
* **idf_plugin_syncsvn_access_public ('r')**: Anonymous access.
* **idf_plugin_syncsvn_access_private ('')**: Anonymous access in the case of a private project.

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

248
logo/indefero-logo5.svg Normal file
View File

@@ -0,0 +1,248 @@
<?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"
width="2500.0901"
height="1052.3622"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docname="indefero-logo5.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.0">
<defs
id="defs4">
<linearGradient
inkscape:collect="always"
id="linearGradient3535">
<stop
style="stop-color:#8ae234;stop-opacity:1;"
offset="0"
id="stop3537" />
<stop
style="stop-color:#8ae234;stop-opacity:0;"
offset="1"
id="stop3539" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient3527">
<stop
style="stop-color:#9f5e21;stop-opacity:1;"
offset="0"
id="stop3529" />
<stop
style="stop-color:#9f5e21;stop-opacity:0;"
offset="1"
id="stop3531" />
</linearGradient>
<linearGradient
id="linearGradient3309">
<stop
style="stop-color:#73d213;stop-opacity:1;"
offset="0"
id="stop3311" />
<stop
style="stop-color:#4e9a06;stop-opacity:1;"
offset="1"
id="stop3313" />
</linearGradient>
<linearGradient
id="linearGradient3300">
<stop
style="stop-color:#a56223;stop-opacity:1;"
offset="0"
id="stop3302" />
<stop
style="stop-color:#492902;stop-opacity:1;"
offset="1"
id="stop3304" />
</linearGradient>
<linearGradient
id="linearGradient3214">
<stop
style="stop-color:#c17d11;stop-opacity:1;"
offset="0"
id="stop3216" />
<stop
style="stop-color:#c17d11;stop-opacity:0;"
offset="1"
id="stop3218" />
</linearGradient>
<linearGradient
id="linearGradient3200">
<stop
id="stop3204"
offset="1"
style="stop-color:#feaf36;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient3178">
<stop
style="stop-color:#8f5902;stop-opacity:1;"
offset="0"
id="stop3180" />
<stop
style="stop-color:#8f5902;stop-opacity:0;"
offset="1"
id="stop3182" />
</linearGradient>
<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" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3300"
id="linearGradient3502"
gradientUnits="userSpaceOnUse"
x1="255.2104"
y1="743.95398"
x2="517.18134"
y2="334.32626" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3309"
id="linearGradient3505"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.90055,0,0,0.9256685,8.702072,50.919096)"
x1="314.81146"
y1="549.55981"
x2="603.79364"
y2="130.80347" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3527"
id="linearGradient3533"
x1="344.48004"
y1="526.93384"
x2="279.66428"
y2="343.77176"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3535"
id="linearGradient3541"
x1="189.38306"
y1="542.79291"
x2="213.16689"
y2="515.03833"
gradientUnits="userSpaceOnUse" />
</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="0.26121807"
inkscape:cx="54.404882"
inkscape:cy="468.48648"
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">
<rect
style="opacity:1;fill:#2e1c09;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect3479"
width="2327.0989"
height="738.08856"
x="28.861307"
y="47.247032"
ry="0"
inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
inkscape:export-xdpi="10.974293"
inkscape:export-ydpi="10.974293" />
<path
style="fill:url(#linearGradient3505);fill-opacity:1;fill-rule:nonzero;stroke:#4e9a06;stroke-width:8.6737175;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="M 380.09452,80.164434 C 325.61878,80.164434 277.73831,109.63179 250.44346,154.01542 C 173.08362,161.38346 112.57489,224.77008 112.57489,301.77525 C 112.57489,328.86745 120.06848,354.27485 133.14683,376.14693 C 107.0966,405.29318 91.243109,443.82739 91.243109,486.07006 C 91.243109,576.90189 164.55915,650.60763 254.88993,650.60763 C 271.09473,650.60763 286.73713,648.22781 301.52154,643.80975 C 339.34139,666.58702 385.02955,679.88189 434.18381,679.88189 C 564.58392,679.88189 670.40931,586.17696 670.40933,470.70975 C 670.40933,424.81133 653.66603,382.36023 625.32555,347.85618 C 628.73221,333.4146 630.53185,318.20844 630.53185,302.49843 C 630.53185,212.12462 570.29216,138.17535 494.43623,133.1011 C 466.29611,100.61828 425.47491,80.164434 380.09452,80.164434 z"
id="path2397"
inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
inkscape:export-xdpi="10.974293"
inkscape:export-ydpi="10.974293" />
<path
style="fill:url(#linearGradient3502);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.40000010000000019;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
d="M 480.78826,286.70465 C 471.74217,303.14801 466.59919,316.91871 462.21007,324.32034 C 458.4305,332.41806 437.64848,381.27668 422.04792,410.85567 C 411.43569,432.35724 400.69212,454.31445 384.28125,472.09375 C 378.04049,477.37818 367.78727,475.23418 363.26933,468.71428 C 353.17919,458.89555 343.45836,448.70244 333.24758,439.0075 C 317.77144,422.84753 305.56098,403.5827 297.62915,382.65768 C 289.88676,365.17614 282.06423,347.03016 268.29833,333.33399 C 266.87079,332.00892 257.87723,342.72971 251.17151,350.13535 C 257.91944,385.02379 261.45712,393.83832 272.16171,424.59112 C 277.97988,444.06591 287.52802,462.10601 295.01596,480.9416 C 301.95377,497.73482 307.73336,515.11749 310.44798,533.13227 C 316.97458,571.06863 313.78852,610.29702 303.54294,647.28446 C 295.53018,678.50408 283.87873,708.83248 268.40625,737.125 C 267.48586,741.82958 273.28765,745.0451 277.32903,743.60868 C 316.10441,740.6132 354.85515,736.72668 393.76368,736.01019 C 432.08809,735.62752 470.30781,739.07963 508.44819,742.41755 C 512.60098,742.7876 516.75356,743.16004 520.90625,743.53125 C 522.73666,738.32781 518.93119,733.37885 515.5,729.84375 C 505.37922,718.85052 495.85001,707.33025 486.04792,696.05659 C 472.12065,679.28224 459.49275,660.74518 453.48949,639.55179 C 445.74909,613.54378 446.01072,585.89039 449.3871,559.18722 C 454.13621,520.65521 457.70434,476.90793 468.12716,439.5747 C 475.03595,414.82825 499.26944,325.16045 505.28557,302.9463 C 506.6387,297.45482 507.70151,291.88947 508.4375,286.28125 C 508.3006,286.02979 501.31023,285.50092 480.78826,286.70465 z"
id="rect2383"
sodipodi:nodetypes="ccscscscccccccccccccccsccc"
inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
inkscape:export-xdpi="10.974293"
inkscape:export-ydpi="10.974293" />
<path
style="fill:url(#linearGradient3533);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25000000000000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 261.61445,343.77177 C 258.29796,347.16111 256.78029,348.5143 253.80525,350.94005 C 257.34812,367.72635 271.24491,410.33512 274.92704,420.91638 C 283.98781,446.95407 298.27703,470.44926 305.96388,496.81755 C 312.6285,519.6793 316.95796,541.99153 318.18734,565.63445 C 319.43684,589.66412 317.35056,617.16231 311.24725,640.25675 C 304.22544,666.82669 289.67219,709.81256 278.71119,735.38821 C 291.85032,736.74081 303.9092,734.30172 317.58611,733.28738 C 330.8032,732.91089 346.51711,728.41602 360.28427,728.58961 C 361.70248,692.98386 362.28914,639.87401 361.91883,606.08799 C 361.53577,571.13943 356.10739,550.49134 343.0934,518.4162 C 332.76842,492.96857 313.62977,457.39972 298.73254,430.34797 C 283.83531,403.29622 270.0195,371.02282 261.61445,343.77177 z"
id="path3450"
sodipodi:nodetypes="ccsssscccsssc"
inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
inkscape:export-xdpi="10.974293"
inkscape:export-ydpi="10.974293" />
<path
style="fill:url(#linearGradient3541);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25000000000000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 113.16529,429.39362 C 113.16529,429.39362 104.64326,454.1652 103.03794,468.69107 C 101.44344,483.11903 102.46825,506.23771 105.4804,520.43755 C 108.98801,536.97308 121.29761,558.93795 130.281,573.25668 C 138.61935,586.54728 158.09648,604.51753 171.10612,613.28778 C 185.25725,622.82754 205.57955,633.4095 222.19789,637.29436 C 239.00231,641.22271 255.43702,641.52676 272.63495,640.09436 C 278.21565,639.62955 302.26007,633.26385 302.26007,633.26385 L 308.87233,606.09973 L 113.16529,429.39362 z"
id="path3461"
sodipodi:nodetypes="cssssssccc"
inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
inkscape:export-xdpi="10.974293"
inkscape:export-ydpi="10.974293" />
<text
xml:space="preserve"
style="font-size:500px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;writing-mode:lr-tb;text-anchor:start;fill:#eeeeec;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="739.95435"
y="534.80164"
id="text3481"
inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
inkscape:export-xdpi="10.974293"
inkscape:export-ydpi="10.974293"><tspan
sodipodi:role="line"
id="tspan3483"
x="739.95435"
y="534.80164">indefero</tspan></text>
<text
xml:space="preserve"
style="font-size:150px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#eeeeec;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="744.05432"
y="711.55402"
id="text3485"
sodipodi:linespacing="125%"
inkscape:export-filename="/home/loa/Projects/indefero.net/media/idfn/img/indefero-logo.png"
inkscape:export-xdpi="10.974293"
inkscape:export-ydpi="10.974293"><tspan
sodipodi:role="line"
id="tspan3487"
x="744.05432"
y="711.55402"
style="letter-spacing:5.01085806">Better code management</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 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"

10
scripts/SyncMercurial.sh Normal file
View File

@@ -0,0 +1,10 @@
#!/bin/sh
private_notify="/home/indefero/tmp/notify.tmp"
reload_cmd="/usr/sbin/apachectl -k graceful"
if [ -e $private_notify ]; then
rm -f $private_notify
$reload_cmd
fi

24
scripts/git-post-update Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# This hook does only one thing:
#
# 1. It calls the gitpostupdate.php script with the current $GIT_DIR
# as argument. The gitpostupdate.php script will then trigger
# the 'gitpostupdate.php::run' event with the $GIT_DIR as argument
# together with merged $_ENV and $_SERVER array.
#
# This hook is normally installed automatically at the creation of your
# repository if you have everything configured correctly. If you want
# to enable it later, you need to symlink it as "post-update" in your
# $GIT_DIR/hooks folder.
#
# www$ chmod +x /home/www/indefero/scripts/git-post-update
# git$ cd /home/git/repositories/project.git/hooks
# git$ ln -s /home/www/indefero/scripts/git-post-update post-update
#
SCRIPTDIR=$(dirname $(readlink -f $0))
FULL_GIT_DIR=$(readlink -f $GIT_DIR)
PHP_POST_UPDATE=$SCRIPTDIR/gitpostupdate.php
echo php $PHP_POST_UPDATE $FULL_GIT_DIR | at now > /dev/null 2>&1

35
scripts/gitcron.php Normal file
View File

@@ -0,0 +1,35 @@
<?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 ***** */
/**
* This script is used to sync the SSH keys, mark the repositories for
* export and prune the deleted repositories.
*
*/
require dirname(__FILE__).'/../src/IDF/conf/path.php';
require 'Pluf.php';
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
IDF_Plugin_SyncGit_Cron::main();

58
scripts/gitpostupdate.php Normal file
View File

@@ -0,0 +1,58 @@
<?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-2010 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 ***** */
/**
* This script will send the notifications after a push in your
* repository.
*/
require dirname(__FILE__).'/../src/IDF/conf/path.php';
require 'Pluf.php';
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
/**
* [signal]
*
* gitpostupdate.php::run
*
* [sender]
*
* gitpostupdate.php
*
* [description]
*
* This signal allows an application to perform a set of tasks on a
* post update of a git repository.
*
* [parameters]
*
* array('git_dir' => '/path/to/git/repository.git',
* 'env' => array_merge($_ENV, $_SERVER));
*
*/
$params = array('git_dir' => $argv[1],
'env' => array_merge($_ENV, $_SERVER));
Pluf_Signal::send('gitpostupdate.php::run', 'gitpostupdate.php', $params);

36
scripts/gitserve.php Normal file
View File

@@ -0,0 +1,36 @@
<?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 ***** */
/**
* This script is used to control the access to the git repositories
* using a restricted shell access.
*
* The only argument must be the login of the user.
*/
require dirname(__FILE__).'/../src/IDF/conf/path.php';
require 'Pluf.php';
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
IDF_Plugin_SyncGit_Serve::main($argv, array_merge($_SERVER, $_ENV));

36
scripts/gitserve.py Normal file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# ***** 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 ***** */
import os
import sys
import commands
import traceback
n = len("/gitserve.py")
GITSERVEPHP = '%s/gitserve.php' % traceback.extract_stack(limit=1)[0][0][0:-n]
status, output = commands.getstatusoutput('php %s %s' % (GITSERVEPHP, sys.argv[1]))
if status == 0:
os.execvp('git', ['git', 'shell', '-c', output.strip()])
else:
sys.stderr.write("%s\n" % output)
sys.exit(1)

61
scripts/hgchangegroup.php Executable file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/env php
<?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-2010 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 ***** */
/**
* This script will send the notifications after a push in your
* repository.
*/
require dirname(__FILE__).'/../src/IDF/conf/path.php';
require 'Pluf.php';
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
/**
* [signal]
*
* hgchangegroup.php::run
*
* [sender]
*
* hgchangegroup.php
*
* [description]
*
* This signal allows an application to perform a set of tasks on a
* group change hook of a Mercurial repository. The rel_dir is a
* relative path to the root of your hg repositories but starting with
* a slash.
*
* [parameters]
*
* array('rel_dir' => '/relative/path/to/hg/repository',
* 'env' => array_merge($_ENV, $_SERVER));
*
*/
$params = array('rel_dir' => $_ENV['PATH_INFO'],
'env' => array_merge($_ENV, $_SERVER));
Pluf_Signal::send('hgchangegroup.php::run', 'hgchangegroup.php', $params);

24
scripts/svn-post-commit Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# This hook does only one thing:
#
# 1. It calls the svnpostcommit.php script with the current repository
# and revision as argument. The svnpostcommit.php script will then
# trigger the 'svnpostcommit.php::run' event with the repository path
# and revision as arguments together with merged $_ENV and $_SERVER
# array.
#
# This hook is normally installed automatically at the creation of your
# repository if you have everything configured correctly. If you want
# to enable it later, you need to symlink it as "post-commit" in your
# $REPOSITORY/hooks folder.
#
# www$ chmod +x /home/www/indefero/scripts/svn-post-commit
# www$ cd /home/svn/repositories/project/hooks
# www$ ln -s /home/www/indefero/scripts/svn-post-commit post-commit
#
SCRIPTDIR=$(dirname $(readlink -f $0))
PHP_POST_COMMIT=$SCRIPTDIR/svnpostcommit.php
echo php $PHP_POST_COMMIT "$1" "$2" | at now > /dev/null 2>&1

61
scripts/svnpostcommit.php Normal file
View File

@@ -0,0 +1,61 @@
<?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-2010 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 ***** */
/**
* This script will send the notifications after a push in your
* repository.
*/
require dirname(__FILE__).'/../src/IDF/conf/path.php';
require 'Pluf.php';
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
/**
* [signal]
*
* svnpostcommit.php::run
*
* [sender]
*
* svnpostcommit.php
*
* [description]
*
* This signal allows an application to perform a set of tasks on a
* post commit of a subversion repository.
*
* [parameters]
*
* array('repo_dir' => '/path/to/subversion/repository',
* 'revision' => 1234,
* 'env' => array_merge($_ENV, $_SERVER));
*
*/
$params = array('repo_dir' => $argv[1],
'revision' => $argv[2],
'env' => array_merge($_ENV, $_SERVER));
Pluf_Signal::send('svnpostcommit.php::run', 'svnpostcommit.php', $params);

292
src/IDF/Commit.php Normal file
View File

@@ -0,0 +1,292 @@
<?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
n# 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_Template_dateAgo');
/**
* Base definition of a commit.
*
* By having a reference in the database for each commit, one can
* easily generate a timeline or use the search engine. Commit details
* are normally always taken from the underlining SCM.
*/
class IDF_Commit extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_commits';
$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' => 'commits',
),
'author' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User',
'is_null' => true,
'verbose' => __('submitter'),
'relate_name' => 'submitted_commit',
'help_text' => 'This will allow us to list the latest commits of a user in its profile.',
),
'origauthor' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 150,
'help_text' => 'As we do not necessary have the mapping between the author in the database and the scm, we store the scm author commit information here. That way we can update the author info later in the process.',
),
'scm_id' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 50,
'index' => true,
'help_text' => 'The id of the commit. For git, it will be the SHA1 hash, for subversion it will be the revision id.',
),
'summary' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 250,
'verbose' => __('summary'),
),
'fullmessage' =>
array(
'type' => 'Pluf_DB_Field_Text',
'blank' => true,
'verbose' => __('changelog'),
'help_text' => 'This is the full message of the commit.',
),
'creation_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('creation date'),
'index' => true,
'help_text' => 'Date of creation by the scm',
),
);
}
function __toString()
{
return $this->summary.' - ('.$this->scm_id.')';
}
function _toIndex()
{
$str = str_repeat($this->summary.' ', 4).' '.$this->fullmessage;
return Pluf_Text::cleanString(html_entity_decode($str, ENT_QUOTES, 'UTF-8'));
}
function postSave($create=false)
{
IDF_Search::index($this);
if ($create) {
IDF_Timeline::insert($this, $this->get_project(),
$this->get_author(), $this->creation_dtime);
}
}
function preDelete()
{
IDF_Timeline::remove($this);
IDF_Search::remove($this);
}
/**
* Create a commit from a simple class commit info of a changelog.
*
* @param stdClass Commit info
* @param IDF_Project Current project
* @return IDF_Commit
*/
public static function getOrAdd($change, $project)
{
$sql = new Pluf_SQL('project=%s AND scm_id=%s',
array($project->id, $change->commit));
$r = Pluf::factory('IDF_Commit')->getList(array('filter'=>$sql->gen()));
if ($r->count() > 0) {
return $r[0];
}
if (!isset($change->full_message)) {
$change->full_message = '';
}
$scm = IDF_Scm::get($project);
$commit = new IDF_Commit();
$commit->project = $project;
$commit->scm_id = $change->commit;
$commit->summary = self::toUTF8($change->title);
$commit->fullmessage = self::toUTF8($change->full_message);
$commit->author = $scm->findAuthor($change->author);
$commit->origauthor = $change->author;
$commit->creation_dtime = $change->date;
$commit->create();
$commit->notify($project->getConf());
return $commit;
}
/**
* Convert encoding to UTF8.
*
* If an array is given, the encoding is detected only on the
* first value and then used to convert all the strings.
*
* @param mixed String or array of string to be converted
* @param bool Returns the encoding together with the converted text (false)
* @return mixed String or array of string or array of res + encoding
*/
public static function toUTF8($text, $get_encoding=False)
{
$enc = 'ASCII, UTF-8, ISO-8859-1, JIS, EUC-JP, SJIS';
$ref = $text;
if (is_array($text)) {
$ref = $text[0];
}
if (Pluf_Text_UTF8::check($ref)) {
return (!$get_encoding) ? $text : array($text, 'UTF-8');
}
$encoding = mb_detect_encoding($ref, $enc, true);
if ($encoding == false) {
$encoding = Pluf_Text_UTF8::detect_cyr_charset($ref);
}
if (is_array($text)) {
foreach ($text as $t) {
$res[] = mb_convert_encoding($t, 'UTF-8', $encoding);
}
return (!$get_encoding) ? $res : array($res, $encoding);
} else {
$res = mb_convert_encoding($text, 'UTF-8', $encoding);
return (!$get_encoding) ? $res : array($res, $encoding);
}
}
/**
* Returns the timeline fragment for the commit.
*
*
* @param Pluf_HTTP_Request
* @return Pluf_Template_SafeString
*/
public function timelineFragment($request)
{
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit',
array($request->project->shortname,
$this->scm_id));
$out = '<tr class="log"><td><a href="'.$url.'">'.
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
'</a></td><td>';
$stag = new IDF_Template_ShowUser();
$user = $stag->start($this->get_author(), $request, $this->origauthor, false);
$tag = new IDF_Template_IssueComment();
$out .= $tag->start($this->summary, $request, false);
if (0 && $this->fullmessage) {
$out .= '<br /><br />'.$tag->start($this->fullmessage, $request, false);
}
$out .= '</td>
</tr>
<tr class="extra">
<td colspan="2">
<div class="helptext right">'.sprintf(__('Commit&nbsp;%s, by %s'), '<a href="'.$url.'" class="mono">'.$this->scm_id.'</a>', $user).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
/**
* Returns the feed fragment for the commit.
*
* @param Pluf_HTTP_Request
* @return Pluf_Template_SafeString
*/
public function feedFragment($request)
{
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit',
array($request->project->shortname,
$this->scm_id));
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
$author = ($this->get_author()) ?
$this->get_author() : $this->origauthor;
$cproject = $this->get_project();
$context = new Pluf_Template_Context_Request(
$request,
array(
'c' => $this,
'cproject' => $cproject,
'url' => $url,
'date' => $date,
'author' => $author,
)
);
$tmpl = new Pluf_Template('idf/source/feedfragment.xml');
return $tmpl->render($context);
}
/**
* Notification of change of the object.
*
* @param IDF_Conf Current configuration
* @param bool Creation (true)
*/
public function notify($conf, $create=true)
{
if ('' == $conf->getVal('source_notification_email', '')) {
return;
}
$current_locale = Pluf_Translation::getLocale();
$langs = Pluf::f('languages', array('en'));
Pluf_Translation::loadSetLocale($langs[0]);
$context = new Pluf_Template_Context(
array(
'c' => $this,
'project' => $this->get_project(),
'url_base' => Pluf::f('url_base'),
)
);
$tmpl = new Pluf_Template('idf/source/commit-created-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail(Pluf::f('from_email'),
$conf->getVal('source_notification_email'),
sprintf(__('New Commit %s - %s (%s)'),
$this->scm_id, $this->summary,
$this->get_project()->shortname));
$email->addTextMessage($text_email);
$email->sendMail();
Pluf_Translation::loadSetLocale($current_locale);
}
}

View File

@@ -62,9 +62,8 @@ class IDF_Conf extends Pluf_Model
),
'vdesc' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'type' => 'Pluf_DB_Field_Text',
'blank' => false,
'size' => 250,
'verbose' => __('value'),
),
);
@@ -74,7 +73,7 @@ class IDF_Conf extends Pluf_Model
'type' => 'unique',
),
);
$this->f = new IDF_Conf_DataProxy($this);
$this->f = new IDF_Config_DataProxy($this);
}
function setProject($project)
@@ -86,7 +85,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 +121,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

@@ -26,7 +26,7 @@
* {$conf.f.fieldname} in a template.
*/
class IDF_Conf_DataProxy
class IDF_Config_DataProxy
{
protected $obj = null;

View File

@@ -46,13 +46,41 @@ class IDF_Diff
$current_chunk = 0;
$lline = 0;
$rline = 0;
$files = array();
$indiff = false; // Used to skip the headers in the git patches
$i = 0; // Used to skip the end of a git patch with --\nversion number
foreach ($this->lines as $line) {
$i++;
if (0 === strpos($line, '--') and isset($this->lines[$i])
and preg_match('/^\d+\.\d+\.\d+\.\d+$/', $this->lines[$i])) {
break;
}
if (0 === strpos($line, 'diff --git a')) {
$current_file = self::getFile($line);
$files[$current_file] = array();
$files[$current_file]['chunks'] = array();
$files[$current_file]['chunks_def'] = array();
$current_chunk = 0;
$indiff = true;
continue;
} else if (preg_match('#^diff -r [^\s]+ -r [^\s]+ (.+)$#', $line, $matches)) {
$current_file = $matches[1];
$files[$current_file] = array();
$files[$current_file]['chunks'] = array();
$files[$current_file]['chunks_def'] = array();
$current_chunk = 0;
$indiff = true;
continue;
} else 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;
$indiff = true;
continue;
}
if (!$indiff) {
continue;
}
if (0 === strpos($line, '@@ ')) {
@@ -82,6 +110,12 @@ class IDF_Diff
$lline++;
continue;
}
if ($line == '') {
$files[$current_file]['chunks'][$current_chunk-1][] = array($lline, $rline, $line);
$rline++;
$lline++;
continue;
}
}
$this->files = $files;
return $files;
@@ -94,6 +128,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.
*/
@@ -101,6 +140,11 @@ class IDF_Diff
{
$out = '';
foreach ($this->files as $filename=>$file) {
$pretty = '';
$fileinfo = IDF_Views_Source::getMimeType($filename);
if (IDF_Views_Source::isSupportedExtension($fileinfo[2])) {
$pretty = ' prettyprint';
}
$out .= "\n".'<table class="diff" summary="">'."\n";
$out .= '<tr id="diff-'.md5($filename).'"><th colspan="3">'.Pluf_esc($filename).'</th></tr>'."\n";
$cc = 1;
@@ -113,8 +157,8 @@ class IDF_Diff
} else {
$class = 'diff-a';
}
$line_content = $this->padLine(Pluf_esc($line[2]));
$out .= sprintf('<tr class="diff-line"><td class="diff-lc">%s</td><td class="diff-lc">%s</td><td class="%s mono">%s</td></tr>'."\n", $line[0], $line[1], $class, $line_content);
$line_content = self::padLine(Pluf_esc($line[2]));
$out .= sprintf('<tr class="diff-line"><td class="diff-lc">%s</td><td class="diff-lc">%s</td><td class="%s%s mono">%s</td></tr>'."\n", $line[0], $line[1], $class, $pretty, $line_content);
}
if (count($file['chunks']) > $cc)
$out .= '<tr class="diff-next"><td>...</td><td>...</td><td>&nbsp;</td></tr>'."\n";
@@ -122,12 +166,13 @@ class IDF_Diff
}
$out .= '</table>';
}
return $out;
return Pluf_Template::markSafe($out);
}
public function padLine($line)
public static function padLine($line)
{
$line = str_replace("\t", ' ', $line);
$n = strlen($line);
for ($i=0;$i<$n;$i++) {
if (substr($line, $i, 1) != ' ') {
@@ -142,12 +187,172 @@ class IDF_Diff
*/
public static function getChunk($line)
{
$elts = split(' ', $line);
$elts = explode(' ', $line);
$res = array();
for ($i=1;$i<3;$i++) {
$res[] = split(',', trim(substr($elts[$i], 1)));
$res[] = explode(',', trim(substr($elts[$i], 1)));
}
return $res;
}
}
/**
* Review patch.
*
* Given the original file as a string and the parsed
* corresponding diff chunks, generate a side by side view of the
* original file and new file with added/removed lines.
*
* Example of use:
*
* $diff = new IDF_Diff(file_get_contents($diff_file));
* $orig = file_get_contents($orig_file);
* $diff->parse();
* echo $diff->fileCompare($orig, $diff->files[$orig_file], $diff_file);
*
* @param string Original file
* @param array Chunk description of the diff corresponding to the file
* @param string Original file name
* @param int Number of lines before/after the chunk to be displayed (10)
* @return Pluf_Template_SafeString The table body
*/
public function fileCompare($orig, $chunks, $filename, $context=10)
{
$orig_lines = preg_split("/\015\012|\015|\012/", $orig);
$new_chunks = $this->mergeChunks($orig_lines, $chunks, $context);
return $this->renderCompared($new_chunks, $filename);
}
public function mergeChunks($orig_lines, $chunks, $context=10)
{
$spans = array();
$new_chunks = array();
$min_line = 0;
$max_line = 0;
//if (count($chunks['chunks_def']) == 0) return '';
foreach ($chunks['chunks_def'] as $chunk) {
$start = ($chunk[0][0] > $context) ? $chunk[0][0]-$context : 0;
$end = (($chunk[0][0]+$chunk[0][1]+$context-1) < count($orig_lines)) ? $chunk[0][0]+$chunk[0][1]+$context-1 : count($orig_lines);
$spans[] = array($start, $end);
}
// merge chunks/get the chunk lines
// these are reference lines
$chunk_lines = array();
foreach ($chunks['chunks'] as $chunk) {
foreach ($chunk as $line) {
$chunk_lines[] = $line;
}
}
$i = 0;
foreach ($chunks['chunks'] as $chunk) {
$n_chunk = array();
// add lines before
if ($chunk[0][0] > $spans[$i][0]) {
for ($lc=$spans[$i][0];$lc<$chunk[0][0];$lc++) {
$exists = false;
foreach ($chunk_lines as $line) {
if ($lc == $line[0]
or ($chunk[0][1]-$chunk[0][0]+$lc) == $line[1]) {
$exists = true;
break;
}
}
if (!$exists) {
$orig = isset($orig_lines[$lc-1]) ? $orig_lines[$lc-1] : '';
$n_chunk[] = array(
$lc,
$chunk[0][1]-$chunk[0][0]+$lc,
$orig
);
}
}
}
// add chunk lines
foreach ($chunk as $line) {
$n_chunk[] = $line;
}
// add lines after
$lline = $line;
if (!empty($lline[0]) and $lline[0] < $spans[$i][1]) {
for ($lc=$lline[0];$lc<=$spans[$i][1];$lc++) {
$exists = false;
foreach ($chunk_lines as $line) {
if ($lc == $line[0] or ($lline[1]-$lline[0]+$lc) == $line[1]) {
$exists = true;
break;
}
}
if (!$exists) {
$n_chunk[] = array(
$lc,
$lline[1]-$lline[0]+$lc,
$orig_lines[$lc-1]
);
}
}
}
$new_chunks[] = $n_chunk;
$i++;
}
// Now, each chunk has the right length, we need to merge them
// when needed
$nnew_chunks = array();
$i = 0;
foreach ($new_chunks as $chunk) {
if ($i>0) {
$lline = end($nnew_chunks[$i-1]);
if ($chunk[0][0] <= $lline[0]+1) {
// need merging
foreach ($chunk as $line) {
if ($line[0] > $lline[0] or empty($line[0])) {
$nnew_chunks[$i-1][] = $line;
}
}
} else {
$nnew_chunks[] = $chunk;
$i++;
}
} else {
$nnew_chunks[] = $chunk;
$i++;
}
}
return $nnew_chunks;
}
public function renderCompared($chunks, $filename)
{
$fileinfo = IDF_Views_Source::getMimeType($filename);
$pretty = '';
if (IDF_Views_Source::isSupportedExtension($fileinfo[2])) {
$pretty = ' prettyprint';
}
$out = '';
$cc = 1;
$i = 0;
foreach ($chunks as $chunk) {
foreach ($chunk as $line) {
$line1 = '&nbsp;';
$line2 = '&nbsp;';
$line[2] = (strlen($line[2])) ? self::padLine(Pluf_esc($line[2])) : '&nbsp;';
if ($line[0] and $line[1]) {
$class = 'diff-c';
$line1 = $line2 = $line[2];
} elseif ($line[0]) {
$class = 'diff-r';
$line1 = $line[2];
} else {
$class = 'diff-a';
$line2 = $line[2];
}
$out .= sprintf('<tr class="diff-line"><td class="diff-lc">%s</td><td class="%s mono%s"><code>%s</code></td><td class="diff-lc">%s</td><td class="%s mono%s"><code>%s</code></td></tr>'."\n", $line[0], $class, $pretty, $line1, $line[1], $class, $pretty, $line2);
}
if (count($chunks) > $cc)
$out .= '<tr class="diff-next"><td>...</td><td>&nbsp;</td><td>...</td><td>&nbsp;</td></tr>'."\n";
$cc++;
$i++;
}
return Pluf_Template::markSafe($out);
}
}

View File

@@ -0,0 +1,240 @@
<?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 ***** */
/**
* Create a project.
*
* A kind of merge of the member configuration, overview and the
* former source tab.
*
*/
class IDF_Form_Admin_ProjectCreate extends Pluf_Form
{
public function initFields($extra=array())
{
$choices = array();
$options = array(
'git' => __('git'),
'svn' => __('Subversion'),
'mercurial' => __('mercurial'),
);
foreach (Pluf::f('allowed_scm', array()) as $key => $class) {
$choices[$options[$key]] = $key;
}
$this->fields['name'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Name'),
'initial' => '',
));
$this->fields['private_project'] = new Pluf_Form_Field_Boolean(
array('required' => false,
'label' => __('Private project'),
'initial' => false,
'widget' => 'Pluf_Form_Widget_CheckboxInput',
));
$this->fields['shortname'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Shortname'),
'initial' => '',
'help_text' => __('It must be unique for each project and composed only of letters, digits and dash (-) like "my-project".'),
));
$this->fields['scm'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Repository type'),
'initial' => 'git',
'widget_attrs' => array('choices' => $choices),
'widget' => 'Pluf_Form_Widget_SelectInput',
));
$this->fields['svn_remote_url'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Remote Subversion repository'),
'initial' => '',
'widget_attrs' => array('size' => '30'),
));
$this->fields['svn_username'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Repository username'),
'initial' => '',
'widget_attrs' => array('size' => '15'),
));
$this->fields['svn_password'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Repository password'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_PasswordInput',
));
$this->fields['owners'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Project owners'),
'initial' => $extra['user']->login,
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array('rows' => 5,
'cols' => 40),
));
$this->fields['members'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Project members'),
'initial' => '',
'widget_attrs' => array('rows' => 7,
'cols' => 40),
'widget' => 'Pluf_Form_Widget_TextareaInput',
));
/**
* [signal]
*
* IDF_Form_Admin_ProjectCreate::initFields
*
* [sender]
*
* IDF_Form_Admin_ProjectCreate
*
* [description]
*
* This signal allows an application to modify the form
* for the creation of a project.
*
* [parameters]
*
* array('form' => $form)
*
*/
$params = array('form' => $this);
Pluf_Signal::send('IDF_Form_Admin_ProjectCreate::initFields',
'IDF_Form_Admin_ProjectCreate', $params);
}
public function clean_owners()
{
return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['owners']);
}
public function clean_members()
{
return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['members']);
}
public function clean_svn_remote_url()
{
$this->cleaned_data['svn_remote_url'] = (!empty($this->cleaned_data['svn_remote_url'])) ? $this->cleaned_data['svn_remote_url'] : '';
$url = trim($this->cleaned_data['svn_remote_url']);
if (strlen($url) == 0) return $url;
// we accept only starting with http(s):// to avoid people
// trying to access the local filesystem.
if (!preg_match('#^(http|https)://#', $url)) {
throw new Pluf_Form_Invalid(__('Only a remote repository available throught http or https are allowed. For example "http://somewhere.com/svn/trunk".'));
}
return $url;
}
public function clean_shortname()
{
$shortname = mb_strtolower($this->cleaned_data['shortname']);
if (preg_match('/[^\-A-Za-z0-9]/', $shortname)) {
throw new Pluf_Form_Invalid(__('This shortname contains illegal characters, please use only letters, digits and dash (-).'));
}
if (mb_substr($shortname, 0, 1) == '-') {
throw new Pluf_Form_Invalid(__('The shortname cannot start with the dash (-) character.'));
}
if (mb_substr($shortname, -1) == '-') {
throw new Pluf_Form_Invalid(__('The shortname cannot end with the dash (-) character.'));
}
$sql = new Pluf_SQL('shortname=%s', array($shortname));
$l = Pluf::factory('IDF_Project')->getList(array('filter'=>$sql->gen()));
if ($l->count() > 0) {
throw new Pluf_Form_Invalid(__('This shortname is already used. Please select another one.'));
}
return $shortname;
}
public function clean()
{
if ($this->cleaned_data['scm'] != 'svn') {
foreach (array('svn_remote_url', 'svn_username', 'svn_password')
as $key) {
$this->cleaned_data[$key] = '';
}
}
/**
* [signal]
*
* IDF_Form_Admin_ProjectCreate::clean
*
* [sender]
*
* IDF_Form_Admin_ProjectCreate
*
* [description]
*
* This signal allows an application to clean the form
* for the creation of a project.
*
* [parameters]
*
* array('cleaned_data' => $cleaned_data)
*
*/
$params = array('cleaned_data' => $this->cleaned_data);
Pluf_Signal::send('IDF_Form_Admin_ProjectCreate::clean',
'IDF_Form_Admin_ProjectCreate', $params);
return $this->cleaned_data;
}
public function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
$project = new IDF_Project();
$project->name = $this->cleaned_data['name'];
$project->shortname = $this->cleaned_data['shortname'];
$project->private = $this->cleaned_data['private_project'];
$project->description = __('Click on the Project Management tab to set the description of your project.');
$project->create();
$conf = new IDF_Conf();
$conf->setProject($project);
$keys = array('scm', 'svn_remote_url',
'svn_username', 'svn_password');
foreach ($keys as $key) {
$this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ?
$this->cleaned_data[$key] : '';
$conf->setVal($key, $this->cleaned_data[$key]);
}
$project->created();
IDF_Form_MembersConf::updateMemberships($project,
$this->cleaned_data);
$project->membershipsUpdated();
return $project;
}
}

View File

@@ -0,0 +1,88 @@
<?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 ***** */
/**
* Delete a project.
*
* It is also removing the SCM files, so handle with care.
*
*/
class IDF_Form_Admin_ProjectDelete extends Pluf_Form
{
public $project = null;
public function initFields($extra=array())
{
$this->project = $extra['project'];
$this->user = $extra['user'];
$this->fields['code'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Confirmation code'),
'initial' => '',
));
$this->fields['agree'] = new Pluf_Form_Field_Boolean(
array('required' => true,
'label' => __('I have made a backup of all the important data of this project.'),
'initial' => '',
));
}
public function clean_code()
{
$code = $this->cleaned_data['code'];
if ($code != $this->getCode()) {
throw new Pluf_Form_Invalid(__('The confirmation code does not match. Please provide a valid confirmation code to delete the project.'));
}
return $code;
}
public function clean_agree()
{
if (!$this->cleaned_data['agree']) {
throw new Pluf_Form_Invalid(__('Sorry, you really need to backup your data before deletion.'));
}
return $this->cleaned_data['agree'];
}
public function getCode()
{
return substr(md5(Pluf::f('secret_key').$this->user->id.'.'.$this->project->id),
0, 8);
}
public function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
// So, we drop the project, it will cascade and delete all the
// elements of the project. For large projects, this may use
// quite some memory.
$this->project->delete();
return true;
}
}

View File

@@ -0,0 +1,87 @@
<?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 project.
*
* A kind of merge of the member configuration and overview in the
* project administration area.
*
*/
class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
{
public $project = null;
public function initFields($extra=array())
{
$this->project = $extra['project'];
$members = $this->project->getMembershipData('string');
$this->fields['name'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Name'),
'initial' => $this->project->name,
));
$this->fields['owners'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Project owners'),
'initial' => $members['owners'],
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array('rows' => 5,
'cols' => 40),
));
$this->fields['members'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Project members'),
'initial' => $members['members'],
'widget_attrs' => array('rows' => 7,
'cols' => 40),
'widget' => 'Pluf_Form_Widget_TextareaInput',
));
}
public function clean_owners()
{
return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['owners']);
}
public function clean_members()
{
return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['members']);
}
public function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
IDF_Form_MembersConf::updateMemberships($this->project,
$this->cleaned_data);
$this->project->membershipsUpdated();
$this->project->name = $this->cleaned_data['name'];
$this->project->update();
}
}

View File

@@ -0,0 +1,214 @@
<?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 an admin to create a user.
*/
class IDF_Form_Admin_UserCreate extends Pluf_Form
{
public $request = null;
public function initFields($extra=array())
{
$this->request = $extra['request'];
$this->fields['first_name'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('First name'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 50,
'size' => 15,
),
));
$this->fields['last_name'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Last name'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 50,
'size' => 20,
),
));
$this->fields['login'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Login'),
'max_length' => 15,
'min_length' => 3,
'initial' => '',
'help_text' => __('The login must be between 3 and 15 characters long and contains only letters and digits.'),
'widget_attrs' => array(
'maxlength' => 15,
'size' => 10,
),
));
$this->fields['email'] = new Pluf_Form_Field_Email(
array('required' => true,
'label' => __('Email'),
'initial' => '',
'help_text' => __('Double check the email address as the password is directly sent to the user.'),
));
$this->fields['language'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Language'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_SelectInput',
'widget_attrs' => array(
'choices' =>
Pluf_L10n::getInstalledLanguages()
),
));
$this->fields['ssh_key'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Add a public SSH key'),
'initial' => '',
'widget_attrs' => array('rows' => 3,
'cols' => 40),
'widget' => 'Pluf_Form_Widget_TextareaInput',
'help_text' => __('Be careful to provide the public key and not the private key!')
));
}
/**
* 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.'));
}
$password = Pluf_Utils::getPassword();
$user = new Pluf_User();
$user->setFromFormData($this->cleaned_data);
$user->active = true;
$user->staff = false;
$user->administrator = false;
$user->setPassword($password);
$user->create();
/**
* [signal]
*
* Pluf_User::passwordUpdated
*
* [sender]
*
* IDF_Form_Admin_UserCreate
*
* [description]
*
* This signal is sent when a user is created
* by the staff.
*
* [parameters]
*
* array('user' => $user)
*
*/
$params = array('user' => $user);
Pluf_Signal::send('Pluf_User::passwordUpdated',
'IDF_Form_Admin_UserCreate', $params);
// Create the ssh key as needed
if ('' !== $this->cleaned_data['ssh_key']) {
$key = new IDF_Key();
$key->user = $user;
$key->content = $this->cleaned_data['ssh_key'];
$key->create();
}
// Send an email to the user with the password
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
$url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views::login', array(), array(), false);
$context = new Pluf_Template_Context(
array('password' => Pluf_Template::markSafe($password),
'user' => $user,
'url' => Pluf_Template::markSafe($url),
'admin' => $this->request->user,
));
$tmpl = new Pluf_Template('idf/gadmin/users/createuser-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail(Pluf::f('from_email'), $user->email,
__('Your details to access your forge.'));
$email->addTextMessage($text_email);
$email->sendMail();
return $user;
}
function clean_ssh_key()
{
return IDF_Form_UserAccount::checkSshKey($this->cleaned_data['ssh_key']);
}
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;
}
function clean_email()
{
$this->cleaned_data['email'] = mb_strtolower(trim($this->cleaned_data['email']));
$guser = new Pluf_User();
$sql = new Pluf_SQL('email=%s', array($this->cleaned_data['email']));
if ($guser->getCount(array('filter' => $sql->gen())) > 0) {
throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used.'), $this->cleaned_data['email']));
}
return $this->cleaned_data['email'];
}
public function clean_login()
{
$this->cleaned_data['login'] = mb_strtolower(trim($this->cleaned_data['login']));
if (preg_match('/[^a-z0-9]/', $this->cleaned_data['login'])) {
throw new Pluf_Form_Invalid(sprintf(__('The login "%s" can only contain letters and digits.'), $this->cleaned_data['login']));
}
$guser = new Pluf_User();
$sql = new Pluf_SQL('login=%s', $this->cleaned_data['login']);
if ($guser->getCount(array('filter' => $sql->gen())) > 0) {
throw new Pluf_Form_Invalid(sprintf(__('The login "%s" is already used, please find another one.'), $this->cleaned_data['login']));
}
return $this->cleaned_data['login'];
}
}

View File

@@ -0,0 +1,219 @@
<?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 user's details.
*/
class IDF_Form_Admin_UserUpdate 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['email'] = new Pluf_Form_Field_Email(
array('required' => true,
'label' => __('Email'),
'initial' => $this->user->email,
'widget_attrs' => array(
'maxlength' => 50,
'size' => 20,
),
));
$this->fields['language'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Language'),
'initial' => $this->user->language,
'widget' => 'Pluf_Form_Widget_SelectInput',
'widget_attrs' => array(
'choices' =>
Pluf_L10n::getInstalledLanguages()
),
));
$this->fields['password'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Password'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_PasswordInput',
'help_text' => Pluf_Template::markSafe(__('Leave blank if you do not want to change the password.').'<br />'.__('The password must be hard for other people to find it, but easy for the user to remember.')),
'widget_attrs' => array(
'maxlength' => 50,
'size' => 15,
),
));
$this->fields['password2'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Confirm password'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_PasswordInput',
'widget_attrs' => array(
'maxlength' => 50,
'size' => 15,
),
));
if ($extra['request']->user->administrator) {
$this->fields['staff'] = new Pluf_Form_Field_Boolean(
array('required' => false,
'label' => __('Staff'),
'initial' => $this->user->staff,
'widget' => 'Pluf_Form_Widget_CheckboxInput',
'help_text' => __('If you give staff rights to a user, you really need to trust him.'),
));
}
$attrs = ($extra['request']->user->id == $this->user->id) ?
array('readonly' => 'readonly') : array();
$this->fields['active'] = new Pluf_Form_Field_Boolean(
array('required' => false,
'label' => __('Active'),
'initial' => $this->user->active,
'widget' => 'Pluf_Form_Widget_CheckboxInput',
'widget_attrs' => $attrs,
'help_text' => __('If the user is not getting the confirmation email or is abusing the system, you can directly enable or disable his account here.'),
));
}
/**
* 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']);
$update_pass = false;
if (strlen($this->cleaned_data['password']) == 0) {
unset($this->cleaned_data['password']);
} else {
$update_pass = true;
}
$this->user->setFromFormData($this->cleaned_data);
if ($commit) {
$this->user->update();
if ($update_pass) {
/**
* [signal]
*
* Pluf_User::passwordUpdated
*
* [sender]
*
* IDF_Form_UserAccount
*
* [description]
*
* This signal is sent when the user updated his
* password from his account page.
*
* [parameters]
*
* array('user' => $user)
*
*/
$params = array('user' => $this->user);
Pluf_Signal::send('Pluf_User::passwordUpdated',
'IDF_Form_Admin_UserUpdate', $params);
}
}
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 == '---') {
throw new Pluf_Form_Invalid(__('--- is not a valid first name.'));
}
if ($first_name == mb_strtoupper($first_name)) {
$first_name = mb_convert_case(mb_strtolower($first_name),
MB_CASE_TITLE, 'UTF-8');
}
return $first_name;
}
function clean_email()
{
$email = mb_strtolower(trim($this->cleaned_data['email']));
$sql = new Pluf_SQL('email=%s AND id!=%s',
array($email, $this->user->id));
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
if ($users->count() > 0) {
throw new Pluf_Form_Invalid(__('A user with this email already exists, please provide another email address.'));
}
return $email;
}
/**
* 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

@@ -64,6 +64,28 @@ class IDF_Form_IssueCreate extends Pluf_Form
'rows' => 13,
),
));
$upload_path = Pluf::f('upload_issue_path', false);
if (false === $upload_path) {
throw new Pluf_Exception_SettingError(__('The "upload_issue_path" configuration variable was not set.'));
}
$md5 = md5(rand().microtime().Pluf_Utils::getRandomString());
// We add .dummy to try to mitigate security issues in the
// case of someone allowing the upload path to be accessible
// to everybody.
for ($i=1;$i<4;$i++) {
$filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';
$this->fields['attachment'.$i] = new Pluf_Form_Field_File(
array('required' => false,
'label' => __('Attach a file'),
'move_function_params' =>
array('upload_path' => $upload_path,
'upload_path_create' => true,
'file_name' => $filename,
)
)
);
}
if ($this->show_full) {
$this->fields['status'] = new Pluf_Form_Field_Varchar(
array('required' => true,
@@ -119,7 +141,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (split(',', $conf->getVal('labels_issue_one_max', IDF_Form_IssueTrackingConf::init_one_max)) as $class) {
foreach (explode(',', $conf->getVal('labels_issue_one_max', IDF_Form_IssueTrackingConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
@@ -151,6 +173,15 @@ class IDF_Form_IssueCreate extends Pluf_Form
return $this->cleaned_data;
}
function clean_content()
{
$content = trim($this->cleaned_data['content']);
if (strlen($content) == 0) {
throw new Pluf_Form_Invalid(__('You need to provide a description of the issue.'));
}
return $content;
}
function clean_status()
{
// Check that the status is in the list of official status
@@ -174,6 +205,21 @@ class IDF_Form_IssueCreate extends Pluf_Form
return $this->cleaned_data['status'];
}
/**
* Clean the attachments post failure.
*/
function failed()
{
$upload_path = Pluf::f('upload_issue_path', false);
if ($upload_path == false) return;
for ($i=1;$i<4;$i++) {
if (!empty($this->cleaned_data['attachment'.$i]) and
file_exists($upload_path.'/'.$this->cleaned_data['attachment'.$i])) {
@unlink($upload_path.'/'.$this->cleaned_data['attachment'.$i]);
}
}
}
/**
* Save the model in the database.
*
@@ -183,55 +229,63 @@ class IDF_Form_IssueCreate extends Pluf_Form
*/
function save($commit=true)
{
if ($this->isValid()) {
// Add a tag for each label
$tags = array();
if ($this->show_full) {
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);
}
}
} else {
$tags[] = IDF_Tag::add('Medium', $this->project, 'Priority');
$tags[] = IDF_Tag::add('Defect', $this->project, 'Type');
}
// Create the issue
$issue = new IDF_Issue();
$issue->project = $this->project;
$issue->submitter = $this->user;
if ($this->show_full) {
$issue->status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
$issue->owner = self::findUser($this->cleaned_data['owner']);
} else {
$_t = $this->project->getTagIdsByStatus('open');
$issue->status = new IDF_Tag($_t[0]); // first one is the default
$issue->owner = null;
}
$issue->summary = trim($this->cleaned_data['summary']);
$issue->create();
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;
$comment->content = $this->cleaned_data['content'];
$comment->submitter = $this->user;
$comment->create();
return $issue;
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
throw new Exception(__('Cannot save the model from an invalid form.'));
// Add a tag for each label
$tags = array();
if ($this->show_full) {
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);
}
}
} else {
$tags[] = IDF_Tag::add('Medium', $this->project, 'Priority');
$tags[] = IDF_Tag::add('Defect', $this->project, 'Type');
}
// Create the issue
$issue = new IDF_Issue();
$issue->project = $this->project;
$issue->submitter = $this->user;
if ($this->show_full) {
$issue->status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
$issue->owner = self::findUser($this->cleaned_data['owner']);
} else {
$_t = $this->project->getTagIdsByStatus('open');
$issue->status = new IDF_Tag($_t[0]); // first one is the default
$issue->owner = null;
}
$issue->summary = trim($this->cleaned_data['summary']);
$issue->create();
foreach ($tags as $tag) {
$issue->setAssoc($tag);
}
// add the first comment
$comment = new IDF_IssueComment();
$comment->issue = $issue;
$comment->content = $this->cleaned_data['content'];
$comment->submitter = $this->user;
$comment->create();
// If we have a file, create the IDF_IssueFile and attach
// it to the comment.
for ($i=1;$i<4;$i++) {
if ($this->cleaned_data['attachment'.$i]) {
$file = new IDF_IssueFile();
$file->attachment = $this->cleaned_data['attachment'.$i];
$file->submitter = $this->user;
$file->comment = $comment;
$file->create();
}
}
return $issue;
}
/**

View File

@@ -60,6 +60,28 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
'rows' => 9,
),
));
$upload_path = Pluf::f('upload_issue_path', false);
if (false === $upload_path) {
throw new Pluf_Exception_SettingError(__('The "upload_issue_path" configuration variable was not set.'));
}
$md5 = md5(rand().microtime().Pluf_Utils::getRandomString());
// We add .dummy to try to mitigate security issues in the
// case of someone allowing the upload path to be accessible
// to everybody.
for ($i=1;$i<4;$i++) {
$filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';
$this->fields['attachment'.$i] = new Pluf_Form_Field_File(
array('required' => false,
'label' => __('Attach a file'),
'move_function_params' =>
array('upload_path' => $upload_path,
'upload_path_create' => true,
'file_name' => $filename,
)
)
);
}
if ($this->show_full) {
$this->fields['status'] = new Pluf_Form_Field_Varchar(
array('required' => true,
@@ -103,6 +125,30 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
}
}
/**
* Clean the attachments post failure.
*/
function failed()
{
$upload_path = Pluf::f('upload_issue_path', false);
if ($upload_path == false) return;
for ($i=1;$i<4;$i++) {
if (!empty($this->cleaned_data['attachment'.$i]) and
file_exists($upload_path.'/'.$this->cleaned_data['attachment'.$i])) {
@unlink($upload_path.'/'.$this->cleaned_data['attachment'.$i]);
}
}
}
function clean_content()
{
$content = trim($this->cleaned_data['content']);
if (!$this->show_full and strlen($content) == 0) {
throw new Pluf_Form_Invalid(__('You need to provide a description of the issue.'));
}
return $content;
}
/**
* We check that something is really changed.
*/
@@ -182,79 +228,92 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
*/
function save($commit=true)
{
if ($this->isValid()) {
if ($this->show_full) {
// Add a tag for each label
$tags = array();
$tagids = 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;
$tagids[] = $tag->id;
}
}
// Compare between the old and the new data
$changes = array();
$oldtags = $this->issue->get_tags_list();
foreach ($tags as $tag) {
if (!Pluf_Model_InArray($tag, $oldtags)) {
if (!isset($changes['lb'])) $changes['lb'] = array();
if ($tag->class != 'Other') {
$changes['lb'][] = (string) $tag; //new tag
} else {
$changes['lb'][] = (string) $tag->name;
}
}
}
foreach ($oldtags as $tag) {
if (!Pluf_Model_InArray($tag, $tags)) {
if (!isset($changes['lb'])) $changes['lb'] = array();
if ($tag->class != 'Other') {
$changes['lb'][] = '-'.(string) $tag; //new tag
} else {
$changes['lb'][] = '-'.(string) $tag->name;
}
}
}
// Status, summary and owner
$status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
if ($status->id != $this->issue->status) {
$changes['st'] = $status->name;
}
if (trim($this->issue->summary) != trim($this->cleaned_data['summary'])) {
$changes['su'] = trim($this->cleaned_data['summary']);
}
$owner = self::findUser($this->cleaned_data['owner']);
if ((is_null($owner) and !is_null($this->issue->get_owner()))
or (!is_null($owner) and is_null($this->issue->get_owner()))
or ((!is_null($owner) and !is_null($this->issue->get_owner())) and $owner->id != $this->issue->get_owner()->id)) {
$changes['ow'] = (is_null($owner)) ? '---' : $owner->login;
}
// Update the issue
$this->issue->batchAssoc('IDF_Tag', $tagids);
$this->issue->summary = trim($this->cleaned_data['summary']);
$this->issue->status = $status;
$this->issue->owner = $owner;
}
// Create the comment
$comment = new IDF_IssueComment();
$comment->issue = $this->issue;
$comment->content = $this->cleaned_data['content'];
$comment->submitter = $this->user;
if (!$this->show_full) $changes = array();
$comment->changes = $changes;
$comment->create();
$this->issue->update();
return $this->issue;
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
throw new Exception(__('Cannot save the model from an invalid form.'));
if ($this->show_full) {
// Add a tag for each label
$tags = array();
$tagids = 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;
$tagids[] = $tag->id;
}
}
// Compare between the old and the new data
$changes = array();
$oldtags = $this->issue->get_tags_list();
foreach ($tags as $tag) {
if (!Pluf_Model_InArray($tag, $oldtags)) {
if (!isset($changes['lb'])) $changes['lb'] = array();
if ($tag->class != 'Other') {
$changes['lb'][] = (string) $tag; //new tag
} else {
$changes['lb'][] = (string) $tag->name;
}
}
}
foreach ($oldtags as $tag) {
if (!Pluf_Model_InArray($tag, $tags)) {
if (!isset($changes['lb'])) $changes['lb'] = array();
if ($tag->class != 'Other') {
$changes['lb'][] = '-'.(string) $tag; //new tag
} else {
$changes['lb'][] = '-'.(string) $tag->name;
}
}
}
// Status, summary and owner
$status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
if ($status->id != $this->issue->status) {
$changes['st'] = $status->name;
}
if (trim($this->issue->summary) != trim($this->cleaned_data['summary'])) {
$changes['su'] = trim($this->cleaned_data['summary']);
}
$owner = self::findUser($this->cleaned_data['owner']);
if ((is_null($owner) and !is_null($this->issue->get_owner()))
or (!is_null($owner) and is_null($this->issue->get_owner()))
or ((!is_null($owner) and !is_null($this->issue->get_owner())) and $owner->id != $this->issue->get_owner()->id)) {
$changes['ow'] = (is_null($owner)) ? '---' : $owner->login;
}
// Update the issue
$this->issue->batchAssoc('IDF_Tag', $tagids);
$this->issue->summary = trim($this->cleaned_data['summary']);
$this->issue->status = $status;
$this->issue->owner = $owner;
}
// Create the comment
$comment = new IDF_IssueComment();
$comment->issue = $this->issue;
$comment->content = $this->cleaned_data['content'];
$comment->submitter = $this->user;
if (!$this->show_full) $changes = array();
$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.
}
for ($i=1;$i<4;$i++) {
if ($this->cleaned_data['attachment'.$i]) {
$file = new IDF_IssueFile();
$file->attachment = $this->cleaned_data['attachment'.$i];
$file->submitter = $this->user;
$file->comment = $comment;
$file->create();
}
}
return $this->issue;
}
}

View File

@@ -63,20 +63,72 @@ class IDF_Form_MembersConf extends Pluf_Form
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
self::updateMemberships($this->project, $this->cleaned_data);
$this->project->membershipsUpdated();
}
public function clean_owners()
{
return self::checkBadLogins($this->cleaned_data['owners']);
}
public function clean_members()
{
return self::checkBadLogins($this->cleaned_data['members']);
}
/**
* From the input, find the bad logins.
*
* @throws Pluf_Form_Invalid exception when bad logins are found
* @param string Comma, new line delimited list of logins
* @return string Comma, new line delimited list of logins
*/
public static function checkBadLogins($logins)
{
$bad = array();
foreach (preg_split("/\015\012|\015|\012|\,/", $logins, -1, PREG_SPLIT_NO_EMPTY) as $login) {
$sql = new Pluf_SQL('login=%s', array(trim($login)));
try {
$user = Pluf::factory('Pluf_User')->getOne(array('filter'=>$sql->gen()));
if (null == $user) {
$bad[] = $login;
}
} catch (Exception $e) {
$bad[] = $login;
}
}
$n = count($bad);
if ($n) {
$badlogins = Pluf_esc(implode(', ', $bad));
throw new Pluf_Form_Invalid(sprintf(_n('The following login is invalid: %s.', 'The following login are invalids: %s.', $n), $badlogins));
}
return $logins;
}
/**
* The update of the memberships is done in different places. This
* avoids duplicating code.
*
* @param IDF_Project The project
* @param array The new memberships data in 'owners' and 'members' keys
*/
public static function updateMemberships($project, $cleaned_data)
{
// remove all the permissions
$cm = $this->project->getMembershipData();
$cm = $project->getMembershipData();
$def = array('owners' => Pluf_Permission::getFromString('IDF.project-owner'),
'members' => Pluf_Permission::getFromString('IDF.project-member'));
$guser = new Pluf_User();
foreach ($def as $key=>$perm) {
foreach ($cm[$key] as $user) {
Pluf_RowPermission::remove($user, $this->project, $perm);
Pluf_RowPermission::remove($user, $project, $perm);
}
foreach (preg_split("/\015\012|\015|\012|\,/", $this->cleaned_data[$key], -1, PREG_SPLIT_NO_EMPTY) as $login) {
foreach (preg_split("/\015\012|\015|\012|\,/", $cleaned_data[$key], -1, PREG_SPLIT_NO_EMPTY) as $login) {
$sql = new Pluf_SQL('login=%s', array(trim($login)));
$users = $guser->getList(array('filter'=>$sql->gen()));
if ($users->count() == 1) {
Pluf_RowPermission::add($users[0], $this->project, $perm);
Pluf_RowPermission::add($users[0], $project, $perm);
}
}
}

112
src/IDF/Form/Password.php Normal file
View File

@@ -0,0 +1,112 @@
<?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 ***** */
/**
* Ask a password recovery.
*
*/
class IDF_Form_Password extends Pluf_Form
{
public function initFields($extra=array())
{
$this->fields['account'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Your login or email'),
'help_text' => __('Provide either your login or your email to recover your password.'),
));
}
/**
* Validate that a user with this login or email exists.
*/
public function clean_account()
{
$account = mb_strtolower(trim($this->cleaned_data['account']));
$sql = new Pluf_SQL('email=%s OR login=%s',
array($account, $account));
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
if ($users->count() == 0) {
throw new Pluf_Form_Invalid(__('Sorry, we cannot find a user with this email address or login. Feel free to try again.'));
}
$ok = false;
foreach ($users as $user) {
if ($user->active) {
$ok = true;
continue;
}
if (!$user->active and $user->first_name == '---') {
$ok = true;
continue;
}
$ok = false; // This ensures an all or nothing ok.
}
if (!$ok) {
throw new Pluf_Form_Invalid(__('Sorry, we cannot find a user with this email address or login. Feel free to try again.'));
}
return $account;
}
/**
* Send the reminder email.
*
*/
function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
$account = $this->cleaned_data['account'];
$sql = new Pluf_SQL('email=%s OR login=%s',
array($account, $account));
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
$return_url = '';
foreach ($users as $user) {
if ($user->active) {
$return_url = Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecoveryInputCode');
$tmpl = new Pluf_Template('idf/user/passrecovery-email.txt');
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
$code = trim($cr->encrypt($user->email.':'.$user->id.':'.time()),
'~');
$code = substr(md5(Pluf::f('secret_key').$code), 0, 2).$code;
$url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecovery', array($code), array(), false);
$urlic = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecoveryInputCode', array(), array(), false);
$context = new Pluf_Template_Context(
array('url' => Pluf_Template::markSafe($url),
'urlik' => Pluf_Template::markSafe($urlic),
'user' => Pluf_Template::markSafe($user),
'key' => Pluf_Template::markSafe($code)));
$email = new Pluf_Mail(Pluf::f('from_email'), $user->email,
__('Password Recovery - InDefero'));
$email->setReturnPath(Pluf::f('bounce_email', Pluf::f('from_email')));
$email->addTextMessage($tmpl->render($context));
$email->sendMail();
}
if (!$user->active and $user->first_name == '---') {
$return_url = Pluf_HTTP_URL_urlForView('IDF_Views::registerInputKey');
IDF_Form_Register::sendVerificationEmail($user);
}
}
return $return_url;
}
}

View File

@@ -0,0 +1,104 @@
<?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');
/**
* Check the validity of a confirmation key to reset a password.
*
*/
class IDF_Form_PasswordInputKey extends Pluf_Form
{
public function initFields($extra=array())
{
$this->fields['key'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Your verification key'),
'initial' => '',
'widget_attrs' => array(
'size' => 50,
),
));
}
/**
* Validate the key.
*/
public function clean_key()
{
$this->cleaned_data['key'] = trim($this->cleaned_data['key']);
$error = __('We are sorry but this validation key is not valid. Maybe you should directly copy/paste it from your validation email.');
if (false === ($cres=self::checkKeyHash($this->cleaned_data['key']))) {
throw new Pluf_Form_Invalid($error);
}
$guser = new Pluf_User();
$sql = new Pluf_SQL('email=%s AND id=%s',
array($cres[0], $cres[1]));
if ($guser->getCount(array('filter' => $sql->gen())) != 1) {
throw new Pluf_Form_Invalid($error);
}
if ((time() - $cres[2]) > 86400) {
throw new Pluf_Form_Invalid(__('Sorry, but this verification key has expired, please restart the password recovery sequence. For security reasons, the verification key is only valid 24h.'));
}
return $this->cleaned_data['key'];
}
/**
* 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 string Url to redirect to the form.
*/
function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save an invalid form.'));
}
return Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecovery',
array($this->cleaned_data['key']));
}
/**
* Return false or an array with the email, id and timestamp.
*
* This is a static function to be reused by other forms.
*
* @param string Confirmation key
* @return mixed Either false or array(email, id, timestamp)
*/
public static function checkKeyHash($key)
{
$hash = substr($key, 0, 2);
$encrypted = substr($key, 2);
if ($hash != substr(md5(Pluf::f('secret_key').$encrypted), 0, 2)) {
return false;
}
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
$f = explode(':', $cr->decrypt($encrypted), 3);
if (count($f) != 3) {
return false;
}
return $f;
}
}

View File

@@ -0,0 +1,138 @@
<?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');
/**
* Reset the password of a user.
*
*/
class IDF_Form_PasswordReset extends Pluf_Form
{
protected $user = null;
public function initFields($extra=array())
{
$this->user = $extra['user'];
$this->fields['key'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Your verification key'),
'initial' => $extra['key'],
'widget' => 'Pluf_Form_Widget_HiddenInput',
));
$this->fields['password'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Your password'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_PasswordInput',
'help_text' => __('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' => true,
'label' => __('Confirm your password'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_PasswordInput',
'widget_attrs' => array(
'maxlength' => 50,
'size' => 15,
),
));
}
/**
* Check the passwords.
*/
public function clean()
{
if ($this->cleaned_data['password'] != $this->cleaned_data['password2']) {
throw new Pluf_Form_Invalid(__('The two passwords must be the same.'));
}
if (!$this->user->active) {
throw new Pluf_Form_Invalid(__('This account is not active. Please contact the forge administrator to activate it.'));
}
return $this->cleaned_data;
}
/**
* Validate the key.
*/
public function clean_key()
{
$this->cleaned_data['key'] = trim($this->cleaned_data['key']);
$error = __('We are sorry but this validation key is not valid. Maybe you should directly copy/paste it from your validation email.');
if (false === ($cres=IDF_Form_PasswordInputKey::checkKeyHash($this->cleaned_data['key']))) {
throw new Pluf_Form_Invalid($error);
}
$guser = new Pluf_User();
$sql = new Pluf_SQL('email=%s AND id=%s',
array($cres[0], $cres[1]));
if ($guser->getCount(array('filter' => $sql->gen())) != 1) {
throw new Pluf_Form_Invalid($error);
}
if ((time() - $cres[2]) > 86400) {
throw new Pluf_Form_Invalid(__('Sorry, but this verification key has expired, please restart the password recovery sequence. For security reasons, the verification key is only valid 24h.'));
}
return $this->cleaned_data['key'];
}
function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save an invalid form.'));
}
$this->user->setFromFormData($this->cleaned_data);
if ($commit) {
$this->user->update();
/**
* [signal]
*
* Pluf_User::passwordUpdated
*
* [sender]
*
* IDF_Form_PasswordReset
*
* [description]
*
* This signal is sent when the user reset his
* password from the password recovery page.
*
* [parameters]
*
* array('user' => $user)
*
*/
$params = array('user' => $this->user);
Pluf_Signal::send('Pluf_User::passwordUpdated',
'IDF_Form_PasswordReset', $params);
}
return $this->user;
}
}

View File

@@ -27,8 +27,11 @@
*/
class IDF_Form_Register extends Pluf_Form
{
protected $request;
public function initFields($extra=array())
{
$this->request = $extra['request'];
$login = '';
if (isset($extra['initial']['login'])) {
$login = $extra['initial']['login'];
@@ -36,12 +39,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,
),
));
@@ -65,6 +68,9 @@ class IDF_Form_Register extends Pluf_Form
public function clean_login()
{
$this->cleaned_data['login'] = mb_strtolower(trim($this->cleaned_data['login']));
if (preg_match('/[^A-Za-z0-9]/', $this->cleaned_data['login'])) {
throw new Pluf_Form_Invalid(sprintf(__('The login "%s" can only contain letters and digits.'), $this->cleaned_data['login']));
}
$guser = new Pluf_User();
$sql = new Pluf_SQL('login=%s', $this->cleaned_data['login']);
if ($guser->getCount(array('filter' => $sql->gen())) > 0) {
@@ -115,10 +121,17 @@ class IDF_Form_Register extends Pluf_Form
$user->last_name = $this->cleaned_data['login'];
$user->login = $this->cleaned_data['login'];
$user->email = $this->cleaned_data['email'];
$user->language = $this->request->language_code;
$user->active = false;
$user->create();
$from_email = Pluf::f('from_email');
self::sendVerificationEmail($user);
return $user;
}
public static function sendVerificationEmail($user)
{
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
$from_email = Pluf::f('from_email');
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
$encrypted = trim($cr->encrypt($user->email.':'.$user->id), '~');
$key = substr(md5(Pluf::f('secret_key').$encrypted), 0, 2).$encrypted;
@@ -131,12 +144,11 @@ class IDF_Form_Register extends Pluf_Form
'user'=> $user,
)
);
$tmpl = new Pluf_Template('register/confirmation-email.txt');
$tmpl = new Pluf_Template('idf/register/confirmation-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail($from_email, $user->email,
__('Confirm the creation of your account.'));
$email->addTextMessage($text_email);
$email->sendMail();
return $user;
}
}

View File

@@ -63,6 +63,7 @@ class IDF_Form_RegisterConfirmation extends Pluf_Form
'size' => 15,
),
));
$this->fields['password'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Your password'),
@@ -141,6 +142,28 @@ class IDF_Form_RegisterConfirmation extends Pluf_Form
$this->_user->staff = false;
if ($commit) {
$this->_user->update();
/**
* [signal]
*
* Pluf_User::passwordUpdated
*
* [sender]
*
* IDF_Form_RegisterConfirmation
*
* [description]
*
* This signal is sent when the user updated his
* password from his account page.
*
* [parameters]
*
* array('user' => $user)
*
*/
$params = array('user' => $this->_user);
Pluf_Signal::send('Pluf_User::passwordUpdated',
'IDF_Form_RegisterConfirmation', $params);
}
return $this->_user;
}

View File

@@ -91,6 +91,6 @@ class IDF_Form_RegisterInputKey extends Pluf_Form
return false;
}
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
return split(':', $cr->decrypt($encrypted), 2);
return explode(':', $cr->decrypt($encrypted), 2);
}
}

View File

@@ -0,0 +1,231 @@
<?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 ***** */
/**
* Create a new code review.
*
* This creates an IDF_Review and the corresponding IDF_Review_Patch.
*/
class IDF_Form_ReviewCreate extends Pluf_Form
{
public $user = null;
public $project = null;
public $show_full = false;
public function initFields($extra=array())
{
$this->user = $extra['user'];
$this->project = $extra['project'];
if ($this->user->hasPerm('IDF.project-owner', $this->project)
or $this->user->hasPerm('IDF.project-member', $this->project)) {
$this->show_full = true;
}
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Summary'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
$this->fields['description'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Description'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array(
'cols' => 58,
'rows' => 7,
),
));
$sql = new Pluf_SQL('project=%s', array($this->project->id));
$commits = Pluf::factory('IDF_Commit')->getList(array('order' => 'creation_dtime DESC',
'nb' => 10,
'filter' => $sql->gen()));
$choices = array();
foreach ($commits as $c) {
$id = (strlen($c->scm_id) > 10) ? substr($c->scm_id, 0, 10) : $c->scm_id;
$ext = (mb_strlen($c->summary) > 50) ? mb_substr($c->summary, 0, 47).'...' : $c->summary;
$choices[$id.' - '.$ext] = $c->scm_id;
}
$this->fields['commit'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Commit'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_SelectInput',
'widget_attrs' => array(
'choices' => $choices,
),
));
$upload_path = Pluf::f('upload_issue_path', false);
if (false === $upload_path) {
throw new Pluf_Exception_SettingError(__('The "upload_issue_path" configuration variable was not set.'));
}
$md5 = md5(rand().microtime().Pluf_Utils::getRandomString());
// We add .dummy to try to mitigate security issues in the
// case of someone allowing the upload path to be accessible
// to everybody.
$filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';
$this->fields['patch'] = new Pluf_Form_Field_File(
array('required' => true,
'label' => __('Patch'),
'move_function_params' =>
array('upload_path' => $upload_path,
'upload_path_create' => true,
'file_name' => $filename,
)
)
);
if ($this->show_full) {
$this->fields['status'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Status'),
'initial' => 'New',
'widget_attrs' => array(
'maxlength' => 20,
'size' => 15,
),
));
}
}
public function clean_patch()
{
$diff = new IDF_Diff(file_get_contents(Pluf::f('upload_issue_path').'/'
.$this->cleaned_data['patch']));
$diff->parse();
if (count($diff->files) == 0) {
throw new Pluf_Form_Invalid(__('We were not able to parse your patch. Please provide a valid patch.'));
}
return $this->cleaned_data['patch'];
}
public function clean_commit()
{
$commit = self::findCommit($this->cleaned_data['commit']);
if (null == $commit) {
throw new Pluf_Form_Invalid(__('You provided an invalid commit.'));
}
return $this->cleaned_data['commit'];
}
/**
* Validate the interconnection in the form.
*/
public function clean()
{
return $this->cleaned_data;
}
function clean_status()
{
// Check that the status is in the list of official status
$tags = $this->project->getTagsFromConfig('labels_issue_open',
IDF_Form_IssueTrackingConf::init_open,
'Status');
$tags = array_merge($this->project->getTagsFromConfig('labels_issue_closed',
IDF_Form_IssueTrackingConf::init_closed,
'Status')
, $tags);
$found = false;
foreach ($tags as $tag) {
if ($tag->name == trim($this->cleaned_data['status'])) {
$found = true;
break;
}
}
if (!$found) {
throw new Pluf_Form_Invalid(__('You provided an invalid status.'));
}
return $this->cleaned_data['status'];
}
/**
* Clean the attachments post failure.
*/
function failed()
{
$upload_path = Pluf::f('upload_issue_path', false);
if ($upload_path == false) return;
if (!empty($this->cleaned_data['patch']) and
file_exists($upload_path.'/'.$this->cleaned_data['patch'])) {
@unlink($upload_path.'/'.$this->cleaned_data['patch']);
}
}
/**
* 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.'));
}
// Create the review
$review = new IDF_Review();
$review->project = $this->project;
$review->summary = $this->cleaned_data['summary'];
$review->submitter = $this->user;
if (!isset($this->cleaned_data['status'])) {
$this->cleaned_data['status'] = 'New';
}
$review->status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
$review->create();
// add the first patch
$patch = new IDF_Review_Patch();
$patch->review = $review;
$patch->summary = __('Initial patch to be reviewed.');
$patch->description = $this->cleaned_data['description'];
$patch->commit = self::findCommit($this->cleaned_data['commit']);
$patch->patch = $this->cleaned_data['patch'];
$patch->create();
$patch->notify($this->project->getConf());
return $review;
}
/**
* Based on the given string, try to find the matching commit.
*
* If no user found, simply returns null.
*
* @param string Commit
* @return IDF_Commit or null
*/
public static function findCommit($string)
{
$string = trim($string);
if (strlen($string) == 0) return null;
$gc = new IDF_Commit();
$sql = new Pluf_SQL('scm_id=%s', array($string));
$gcs = $gc->getList(array('filter' => $sql->gen()));
if ($gcs->count() > 0) {
return $gcs[0];
}
return null;
}
}

View File

@@ -0,0 +1,163 @@
<?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 comments to files in a review.
*
*/
class IDF_Form_ReviewFileComment extends Pluf_Form
{
public $files = null;
public $patch = null;
public $user = null;
public $project = null;
public function initFields($extra=array())
{
$this->files = $extra['files'];
$this->patch = $extra['patch'];
$this->user = $extra['user'];
$this->project = $extra['project'];
foreach ($this->files as $filename => $def) {
$this->fields[md5($filename)] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Comment'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array(
'cols' => 58,
'rows' => 9,
),
));
}
$this->fields['content'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('General comment'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array(
'cols' => 58,
'rows' => 9,
),
));
if ($this->user->hasPerm('IDF.project-owner', $this->project)
or $this->user->hasPerm('IDF.project-member', $this->project)) {
$this->show_full = true;
}
if ($this->show_full) {
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Summary'),
'initial' => $this->patch->get_review()->summary,
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
$this->fields['status'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Status'),
'initial' => $this->patch->get_review()->get_status()->name,
'widget_attrs' => array(
'maxlength' => 20,
'size' => 15,
),
));
}
}
/**
* Validate the interconnection in the form.
*/
public function clean()
{
foreach ($this->files as $filename => $def) {
if (!empty($this->cleaned_data[md5($filename)])) {
return $this->cleaned_data;
}
}
throw new Pluf_Form_Invalid(__('You need to provide comments on at least one file.'));
}
function clean_content()
{
$content = trim($this->cleaned_data['content']);
if (!$this->show_full and strlen($content) == 0) {
throw new Pluf_Form_Invalid(__('You need to provide your general comment about the proposal.'));
}
return $content;
}
/**
* 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.'));
}
// create a base comment
$bc = new IDF_Review_Comment();
$bc->patch = $this->patch;
$bc->submitter = $this->user;
$bc->content = $this->cleaned_data['content'];
$review = $this->patch->get_review();
if ($this->show_full) {
// Compare between the old and the new data
// Status, summary
$changes = array();
$status = IDF_Tag::add(trim($this->cleaned_data['status']), $this->project, 'Status');
if ($status->id != $this->patch->get_review()->status) {
$changes['st'] = $status->name;
}
if (trim($this->patch->get_review()->summary) != trim($this->cleaned_data['summary'])) {
$changes['su'] = trim($this->cleaned_data['summary']);
}
// Update the review
$review->summary = trim($this->cleaned_data['summary']);
$review->status = $status;
$bc->changes = $changes;
}
$bc->create();
foreach ($this->files as $filename => $def) {
if (!empty($this->cleaned_data[md5($filename)])) {
// Add a comment.
$c = new IDF_Review_FileComment();
$c->comment = $bc;
$c->cfile = $filename;
$c->content = $this->cleaned_data[md5($filename)];
$c->create();
}
}
$review->update(); // reindex and put up in the list.
return $bc;
}
}

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 ***** */
/**
* Configuration of the source.
*
* Only the modification of the login/password for subversion is
* authorized.
*/
class IDF_Form_SourceConf extends Pluf_Form
{
public $conf = null;
public function initFields($extra=array())
{
$this->conf = $extra['conf'];
if ($this->conf->getVal('scm', 'git') == 'svn') {
$this->fields['svn_username'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Repository username'),
'initial' => $this->conf->getVal('svn_username', ''),
'widget_attrs' => array('size' => '15'),
));
$this->fields['svn_password'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Repository password'),
'initial' => $this->conf->getVal('svn_password', ''),
'widget' => 'Pluf_Form_Widget_PasswordInput',
));
}
}
}

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

@@ -0,0 +1,124 @@
<?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 $project = null;
public function initFields($extra=array())
{
$this->conf = $extra['conf'];
$this->project = $extra['project'];
$ak = array('downloads_access_rights' => __('Downloads'),
'review_access_rights' => __('Code Review'),
'wiki_access_rights' => __('Documentation'),
'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',
));
}
$ak = array('downloads_notification_email',
'review_notification_email',
'wiki_notification_email',
'source_notification_email',
'issues_notification_email',);
foreach ($ak as $key) {
$this->fields[$key] = new Pluf_Form_Field_Email(
array('required' => false,
'label' => $key,
'initial' => $this->conf->getVal($key, ''),
));
}
$this->fields['private_project'] = new Pluf_Form_Field_Boolean(
array('required' => false,
'label' => __('Private project'),
'initial' => $this->project->private,
'widget' => 'Pluf_Form_Widget_CheckboxInput',
));
$this->fields['authorized_users'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Extra authorized users'),
'widget_attrs' => array('rows' => 7,
'cols' => 40),
'widget' => 'Pluf_Form_Widget_TextareaInput',
));
}
public function clean_authorized_users()
{
return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['authorized_users']);
}
public function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
// remove all the permissions
$perm = Pluf_Permission::getFromString('IDF.project-authorized-user');
$cm = $this->project->getMembershipData();
$guser = new Pluf_User();
foreach ($cm['authorized'] as $user) {
Pluf_RowPermission::remove($user, $this->project, $perm);
}
if ($this->cleaned_data['private_project']) {
foreach (preg_split("/\015\012|\015|\012|\,/", $this->cleaned_data['authorized_users'], -1, PREG_SPLIT_NO_EMPTY) as $login) {
$sql = new Pluf_SQL('login=%s', array(trim($login)));
$users = $guser->getList(array('filter'=>$sql->gen()));
if ($users->count() == 1) {
Pluf_RowPermission::add($users[0], $this->project, $perm);
}
}
$this->project->private = 1;
} else {
$this->project->private = 0;
}
$this->project->update();
$this->project->membershipsUpdated();
}
}

View File

@@ -0,0 +1,152 @@
<?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,
),
));
$this->fields['changelog'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Description'),
'initial' => $this->upload->changelog,
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array(
'cols' => 58,
'rows' => 13,
),
));
$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 (explode(',', $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->changelog = trim($this->cleaned_data['changelog']);
$this->upload->modif_dtime = gmdate('Y-m-d H:i:s');
$this->upload->update();
$this->upload->batchAssoc('IDF_Tag', $tags);
return $this->upload;
}
}

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

@@ -0,0 +1,182 @@
<?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['changelog'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Description'),
'initial' => '',
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array(
'cols' => 58,
'rows' => 13,
),
));
$this->fields['file'] = new Pluf_Form_Field_File(
array('required' => true,
'label' => __('File'),
'initial' => '',
'max_size' => Pluf::f('max_upload_size', 2097152),
'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()
{
$extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext'))));
if (strlen($extra)) $extra .= '|';
if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|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|war|wmv|zip)$/i', $this->cleaned_data['file'])) {
@unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$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 (explode(',', $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;
}
/**
* If we have uploaded a file, but the form failed remove it.
*
*/
function failed()
{
if (!empty($this->cleaned_data['file'])
and file_exists(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->cleaned_data['file'])) {
@unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->cleaned_data['file']);
}
}
/**
* 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->changelog = trim($this->cleaned_data['changelog']);
$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);
}
// Send the notification
$upload->notify($this->project->getConf());
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,290 @@
<?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');
/**
* 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['email'] = new Pluf_Form_Field_Email(
array('required' => true,
'label' => __('Your mail'),
'initial' => $this->user->email,
'help_text' => __('If you change your email address, an email will be sent to the new address to confirm it.'),
));
$this->fields['language'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Language'),
'initial' => $this->user->language,
'widget' => 'Pluf_Form_Widget_SelectInput',
'widget_attrs' => array(
'choices' =>
Pluf_L10n::getInstalledLanguages()
),
));
$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,
),
));
$this->fields['ssh_key'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Add a public SSH key'),
'initial' => '',
'widget_attrs' => array('rows' => 3,
'cols' => 40),
'widget' => 'Pluf_Form_Widget_TextareaInput',
'help_text' => __('Be careful to provide your public key and not your private key!')
));
}
/**
* 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']);
$update_pass = false;
if (strlen($this->cleaned_data['password']) == 0) {
unset($this->cleaned_data['password']);
} else {
$update_pass = true;
}
$old_email = $this->user->email;
$new_email = $this->cleaned_data['email'];
unset($this->cleaned_data['email']);
if ($old_email != $new_email) {
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
$encrypted = trim($cr->encrypt($new_email.':'.$this->user->id.':'.time()), '~');
$key = substr(md5(Pluf::f('secret_key').$encrypted), 0, 2).$encrypted;
$url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailDo', array($key), array(), false);
$urlik = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailInputKey', array(), array(), false);
$context = new Pluf_Template_Context(
array('key' => Pluf_Template::markSafe($key),
'url' => Pluf_Template::markSafe($url),
'urlik' => Pluf_Template::markSafe($urlik),
'email' => $new_email,
'user'=> $this->user,
)
);
$tmpl = new Pluf_Template('idf/user/changeemail-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail(Pluf::f('from_email'), $new_email,
__('Confirm your new email address.'));
$email->addTextMessage($text_email);
$email->sendMail();
$this->user->setMessage(sprintf(__('A validation email has been sent to "%s" to validate the email address change.'), Pluf_esc($new_email)));
}
$this->user->setFromFormData($this->cleaned_data);
// Add key as needed.
if ('' !== $this->cleaned_data['ssh_key']) {
$key = new IDF_Key();
$key->user = $this->user;
$key->content = $this->cleaned_data['ssh_key'];
if ($commit) {
$key->create();
}
}
if ($commit) {
$this->user->update();
if ($update_pass) {
/**
* [signal]
*
* Pluf_User::passwordUpdated
*
* [sender]
*
* IDF_Form_UserAccount
*
* [description]
*
* This signal is sent when the user updated his
* password from his account page.
*
* [parameters]
*
* array('user' => $user)
*
*/
$params = array('user' => $this->user);
Pluf_Signal::send('Pluf_User::passwordUpdated',
'IDF_Form_UserAccount', $params);
}
}
return $this->user;
}
/**
* Check an ssh key.
*
* It will throw a Pluf_Form_Invalid exception if it cannot
* validate the key.
*
* @param $key string The key
* @param $user int The user id of the user of the key (0)
* @return string The clean key
*/
public static function checkSshKey($key, $user=0)
{
$key = trim($key);
if (strlen($key) == 0) {
return '';
}
$key = str_replace(array("\n", "\r"), '', $key);
if (!preg_match('#^ssh\-[a-z]{3}\s(\S+)\s\S+$#', $key, $matches)) {
throw new Pluf_Form_Invalid(__('The format of the key is not valid. It must start with ssh-dss or ssh-rsa, a long string on a single line and at the end a comment.'));
}
if (Pluf::f('idf_strong_key_check', false)) {
$tmpfile = Pluf::f('tmp_folder', '/tmp').'/'.$user.'-key';
file_put_contents($tmpfile, $key, LOCK_EX);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').
'ssh-keygen -l -f '.escapeshellarg($tmpfile).' > /dev/null 2>&1';
exec($cmd, $out, $return);
unlink($tmpfile);
if ($return != 0) {
throw new Pluf_Form_Invalid(__('Please check the key as it does not appears to be a valid key.'));
}
}
// If $user, then check if not the same key stored
if ($user) {
$ruser = Pluf::factory('Pluf_User', $user);
if ($ruser->id > 0) {
$sql = new Pluf_SQL('content=%s', array($key));
$keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen()));
if (count($keys) > 0) {
throw new Pluf_Form_Invalid(__('You already have uploaded this SSH key.'));
}
}
}
return $key;
}
function clean_ssh_key()
{
return self::checkSshKey($this->cleaned_data['ssh_key'],
$this->user->id);
}
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;
}
function clean_email()
{
$this->cleaned_data['email'] = mb_strtolower(trim($this->cleaned_data['email']));
$guser = new Pluf_User();
$sql = new Pluf_SQL('email=%s AND id!=%s',
array($this->cleaned_data['email'], $this->user->id));
if ($guser->getCount(array('filter' => $sql->gen())) > 0) {
throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used.'), $this->cleaned_data['email']));
}
return $this->cleaned_data['email'];
}
/**
* 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

@@ -0,0 +1,84 @@
<?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 ***** */
/**
* Change the email address of a user.
*
*/
class IDF_Form_UserChangeEmail extends Pluf_Form
{
protected $user;
public function initFields($extra=array())
{
$this->fields['key'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Your verification key'),
'initial' => '',
'widget_attrs' => array(
'size' => 50,
),
));
}
function clean_key()
{
self::validateKey($this->cleaned_data['key']);
return $this->cleaned_data['key'];
}
/**
* Validate the key.
*
* Throw a Pluf_Form_Invalid exception if the key is not valid.
*
* @param string Key
* @return array array($new_email, $user_id, time())
*/
public static function validateKey($key)
{
$hash = substr($key, 0, 2);
$encrypted = substr($key, 2);
if ($hash != substr(md5(Pluf::f('secret_key').$encrypted), 0, 2)) {
throw new Pluf_Form_Invalid(__('The validation key is not valid. Please copy/paste it from your confirmation email.'));
}
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
return explode(':', $cr->decrypt($encrypted), 3);
}
/**
* 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.'));
}
return Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailDo', array($this->cleaned_data['key']));
}
}

66
src/IDF/Form/WikiConf.php Normal file
View File

@@ -0,0 +1,66 @@
<?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 wiki pages.
*/
class IDF_Form_WikiConf 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
Phase:Requirements = Project vision and requirements
Phase:Design = Project design and key concerns
Phase:Implementation = Developers\' guide
Phase:QA = Testing plans and QA strategies
Phase:Deploy = How to install and configure the program
Phase:Support = Plans for user support and advocacy
Deprecated = Most users should NOT reference this';
const init_one_max = '';
public function initFields($extra=array())
{
$this->fields['labels_wiki_predefined'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Predefined documentation page labels'),
'initial' => self::init_predefined,
'widget_attrs' => array('rows' => 13,
'cols' => 75),
'widget' => 'Pluf_Form_Widget_TextareaInput',
));
$this->fields['labels_wiki_one_max'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Each documentation page may have at most one label with each of these classes'),
'initial' => self::init_one_max,
'widget_attrs' => array('size' => 60),
));
}
}

205
src/IDF/Form/WikiCreate.php Normal file
View File

@@ -0,0 +1,205 @@
<?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 ***** */
/**
* Create a new documentation page.
*
* This create a new page and the corresponding revision.
*
*/
class IDF_Form_WikiCreate extends Pluf_Form
{
public $user = null;
public $project = null;
public $show_full = false;
public function initFields($extra=array())
{
$initial = __('# Introduction
Add your content here.
# Details
Add your content here. Format your content with:
* Text in **bold** or *italic*.
* Headings, paragraphs, and lists.
* Links to other [[WikiPage]].
');
$this->user = $extra['user'];
$this->project = $extra['project'];
if ($this->user->hasPerm('IDF.project-owner', $this->project)
or $this->user->hasPerm('IDF.project-member', $this->project)) {
$this->show_full = true;
}
$initname = (!empty($extra['name'])) ? $extra['name'] : __('PageName');
$this->fields['title'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Page title'),
'initial' => $initname,
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
'help_text' => __('The page name must contains only letters, digits and the dash (-) character.'),
));
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Description'),
'help_text' => __('This one line description is displayed in the list of pages.'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
$this->fields['content'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Content'),
'initial' => $initial,
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array(
'cols' => 68,
'rows' => 26,
),
));
if ($this->show_full) {
for ($i=1;$i<4;$i++) {
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Labels'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 50,
'size' => 20,
),
));
}
}
}
public function clean_title()
{
$title = $this->cleaned_data['title'];
if (preg_match('/[^a-zA-Z0-9\-]/', $title)) {
throw new Pluf_Form_Invalid(__('The title contains invalid characters.'));
}
$sql = new Pluf_SQL('project=%s AND title=%s',
array($this->project->id, $title));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
if ($pages->count() > 0) {
throw new Pluf_Form_Invalid(__('A page with this title already exists.'));
}
return $title;
}
/**
* Validate the interconnection in the form.
*/
public function clean()
{
if (!$this->show_full) {
return $this->cleaned_data;
}
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (explode(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
}
$count = array();
for ($i=1;$i<4;$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 a page.'), $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();
if ($this->show_full) {
for ($i=1;$i<4;$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 page
$page = new IDF_WikiPage();
$page->project = $this->project;
$page->submitter = $this->user;
$page->summary = trim($this->cleaned_data['summary']);
$page->title = trim($this->cleaned_data['title']);
$page->create();
foreach ($tags as $tag) {
$page->setAssoc($tag);
}
// add the first revision
$rev = new IDF_WikiRevision();
$rev->wikipage = $page;
$rev->content = $this->cleaned_data['content'];
$rev->submitter = $this->user;
$rev->summary = __('Initial page creation');
$rev->create();
$rev->notify($this->project->getConf());
return $page;
}
}

View File

@@ -0,0 +1,64 @@
<?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 ***** */
/**
* Delete a documentation page.
*
* This is a hard delete of the page and the revisions.
*
*/
class IDF_Form_WikiDelete extends Pluf_Form
{
protected $page = null;
public function initFields($extra=array())
{
$this->page = $extra['page'];
$this->fields['confirm'] = new Pluf_Form_Field_Boolean(
array('required' => true,
'label' => __('Yes, I understand that the page and all its revisions will be deleted.'),
'initial' => '',
));
}
/**
* Check the confirmation.
*/
public function clean_confirm()
{
if (!$this->cleaned_data['confirm']) {
throw new Pluf_Form_Invalid(__('You need to confirm the deletion.'));
}
return $this->cleaned_data['confirm'];
}
function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
$this->page->delete();
return true;
}
}

242
src/IDF/Form/WikiUpdate.php Normal file
View File

@@ -0,0 +1,242 @@
<?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 documentation page.
*
* This add a corresponding revision.
*
*/
class IDF_Form_WikiUpdate extends Pluf_Form
{
public $user = null;
public $project = null;
public $page = null;
public $show_full = false;
public function initFields($extra=array())
{
$this->page = $extra['page'];
$this->user = $extra['user'];
$this->project = $extra['project'];
if ($this->user->hasPerm('IDF.project-owner', $this->project)
or $this->user->hasPerm('IDF.project-member', $this->project)) {
$this->show_full = true;
}
if ($this->show_full) {
$this->fields['title'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Page title'),
'initial' => $this->page->title,
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
'help_text' => __('The page name must contains only letters, digits and the dash (-) character.'),
));
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Description'),
'help_text' => __('This one line description is displayed in the list of pages.'),
'initial' => $this->page->summary,
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
}
$rev = $this->page->get_current_revision();
$this->fields['content'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Content'),
'initial' => $rev->content,
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array(
'cols' => 68,
'rows' => 26,
),
));
$this->fields['comment'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Comment'),
'help_text' => __('One line to describe the changes you made.'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
if ($this->show_full) {
$tags = $this->page->get_tags_list();
for ($i=1;$i<4;$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,
),
));
}
}
}
public function clean_title()
{
$title = $this->cleaned_data['title'];
if (preg_match('/[^a-zA-Z0-9\-]/', $title)) {
throw new Pluf_Form_Invalid(__('The title contains invalid characters.'));
}
$sql = new Pluf_SQL('project=%s AND title=%s',
array($this->project->id, $title));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
if ($pages->count() > 0 and $pages[0]->id != $this->page->id) {
throw new Pluf_Form_Invalid(__('A page with this title already exists.'));
}
return $title;
}
/**
* Validate the interconnection in the form.
*/
public function clean()
{
if (!$this->show_full) {
return $this->cleaned_data;
}
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (explode(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
}
$count = array();
for ($i=1;$i<4;$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 a page.'), $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.'));
}
if ($this->show_full) {
$tagids = array();
$tags = array();
for ($i=1;$i<4;$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;
$tagids[] = $tag->id;
}
}
// Compare between the old and the new data
$changes = array();
$oldtags = $this->page->get_tags_list();
foreach ($tags as $tag) {
if (!Pluf_Model_InArray($tag, $oldtags)) {
if (!isset($changes['lb'])) $changes['lb'] = array();
if ($tag->class != 'Other') {
$changes['lb'][] = (string) $tag; //new tag
} else {
$changes['lb'][] = (string) $tag->name;
}
}
}
foreach ($oldtags as $tag) {
if (!Pluf_Model_InArray($tag, $tags)) {
if (!isset($changes['lb'])) $changes['lb'] = array();
if ($tag->class != 'Other') {
$changes['lb'][] = '-'.(string) $tag; //new tag
} else {
$changes['lb'][] = '-'.(string) $tag->name;
}
}
}
if (trim($this->page->summary) != trim($this->cleaned_data['summary'])) {
$changes['su'] = trim($this->cleaned_data['summary']);
}
// Update the page
$this->page->batchAssoc('IDF_Tag', $tagids);
$this->page->summary = trim($this->cleaned_data['summary']);
$this->page->title = trim($this->cleaned_data['title']);
} else {
$changes = array();
}
$this->page->update();
// add the new revision
$rev = new IDF_WikiRevision();
$rev->wikipage = $this->page;
$rev->content = $this->cleaned_data['content'];
$rev->submitter = $this->user;
$rev->summary = $this->cleaned_data['comment'];
$rev->changes = $changes;
$rev->create();
$rev->notify($this->project->getConf(), false);
return $this->page;
}
}

View File

@@ -1,304 +0,0 @@
<?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 ***** */
/**
* Git utils.
*
*/
class IDF_Git
{
public $repo = '';
public $mediumtree_fmt = 'commit %H%nAuthor: %an <%ae>%nTree: %T%nDate: %ai%n%n%s%n%n%b';
public function __construct($repo)
{
$this->repo = $repo;
}
/**
* Test a given object hash.
*
* @param string Object hash.
* @return mixed false if not valid or 'blob', 'tree', 'commit'
*/
public function testHash($hash)
{
$cmd = sprintf('GIT_DIR=%s git cat-file -t %s',
escapeshellarg($this->repo),
escapeshellarg($hash));
$ret = 0; $out = array();
exec($cmd, &$out, &$ret);
if ($ret != 0) return false;
return trim($out[0]);
}
/**
* 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($commit='HEAD', $folder='')
{
// now we grab the info about this commit including its tree.
$co = $this->getCommit($commit);
if ($folder) {
// As we are limiting to a given folder, we need to find
// the tree corresponding to this folder.
$found = false;
foreach ($this->getTreeInfo($co->tree) as $file) {
if ($file->type == 'tree' and $file->file == $folder) {
$found = true;
$tree = $file->hash;
break;
}
}
if (!$found) {
throw new Exception(sprintf(__('Folder %1$s not found in commit %2$s.'), $folder, $commit));
}
} else {
$tree = $co->tree;
}
$res = array();
// get the raw log corresponding to this commit to find the
// origin of each file.
$rawlog = array();
$cmd = sprintf('GIT_DIR=%s git log --raw --abbrev=40 --pretty=oneline %s',
escapeshellarg($this->repo), escapeshellarg($commit));
exec($cmd, &$rawlog);
// We reverse the log to be able to use a fixed efficient
// regex without back tracking.
$rawlog = implode("\n", array_reverse($rawlog));
foreach ($this->getTreeInfo($tree, false) as $file) {
// Now we grab the files in the current tree with as much
// information as possible.
$matches = array();
if ($file->type == 'blob' and preg_match('/^\:\d{6} \d{6} [0-9a-f]{40} '.$file->hash.' .*^([0-9a-f]{40})/msU',
$rawlog, &$matches)) {
$fc = $this->getCommit($matches[1]);
$file->date = $fc->date;
$file->log = $fc->title;
} else if ($file->type == 'blob') {
$file->date = $co->date;
$file->log = $co->title;
}
$file->fullpath = ($folder) ? $folder.'/'.$file->file : $file->file;
$res[] = $file;
}
return $res;
}
/**
* Get the tree info.
*
* @param string Tree hash
* @param bool Do we recurse in subtrees (true)
* @return array Array of file information.
*/
public function getTreeInfo($tree, $recurse=true)
{
if ('tree' != $this->testHash($tree)) {
throw new Exception(sprintf(__('Not a valid tree: %s.'), $tree));
}
$cmd_tmpl = 'GIT_DIR=%s git-ls-tree%s -t -l %s';
$cmd = sprintf($cmd_tmpl,
escapeshellarg($this->repo),
($recurse) ? ' -r' : '',
escapeshellarg($tree));
$out = array();
$res = array();
exec($cmd, &$out);
foreach ($out as $line) {
list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY);
$res[] = (object) array('perm' => $perm, 'type' => $type,
'size' => $size, 'hash' => $hash,
'file' => $file);
}
return $res;
}
/**
* Get the file info.
*
* @param string File
* @param string Commit ('HEAD')
* @return false Information
*/
public function getFileInfo($totest, $commit='HEAD')
{
$cmd_tmpl = 'GIT_DIR=%s git-ls-tree -r -t -l %s';
$cmd = sprintf($cmd_tmpl,
escapeshellarg($this->repo),
escapeshellarg($commit));
$out = array();
exec($cmd, &$out);
foreach ($out as $line) {
list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY);
if ($totest == $file) {
return (object) array('perm' => $perm, 'type' => $type,
'size' => $size, 'hash' => $hash,
'file' => $file);
}
}
return false;
}
/**
* Get a blob.
*
* @param string Blob hash
* @return string Raw blob
*/
public function getBlob($hash)
{
return shell_exec(sprintf('GIT_DIR=%s git-cat-file blob %s',
escapeshellarg($this->repo),
escapeshellarg($hash)));
}
/**
* Get the branches.
*
* @return array Branches.
*/
public function getBranches()
{
$out = array();
exec(sprintf('GIT_DIR=%s git branch',
escapeshellarg($this->repo)), &$out);
$res = array();
foreach ($out as $b) {
$res[] = substr($b, 2);
}
return $res;
}
/**
* Get commit details.
*
* @param string Commit ('HEAD').
* @return array Changes.
*/
public function getCommit($commit='HEAD')
{
$cmd = sprintf('GIT_DIR=%s git show --date=iso --pretty=format:%s %s',
escapeshellarg($this->repo),
"'".$this->mediumtree_fmt."'",
escapeshellarg($commit));
$out = array();
exec($cmd, &$out);
$log = array();
$change = array();
$inchange = false;
foreach ($out as $line) {
if (!$inchange and 0 === strpos($line, 'diff --git a')) {
$inchange = true;
}
if ($inchange) {
$change[] = $line;
} else {
$log[] = $line;
}
}
$out = self::parseLog($log, 4);
$out[0]->changes = implode("\n", $change);
return $out[0];
}
/**
* Get latest changes.
*
* @param string Commit ('HEAD').
* @param int Number of changes (10).
* @return array Changes.
*/
public function getChangeLog($commit='HEAD', $n=10)
{
if ($n === null) $n = '';
else $n = ' -'.$n;
$cmd = sprintf('GIT_DIR=%s git log%s --date=iso --pretty=format:\'%s\' %s',
escapeshellarg($this->repo), $n, $this->mediumtree_fmt,
escapeshellarg($commit));
$out = array();
exec($cmd, &$out);
return self::parseLog($out, 4);
}
/**
* Parse the log lines of a --pretty=medium log output.
*
* @param array Lines.
* @param int Number of lines in the headers (3)
* @return array Change log.
*/
public static function parseLog($lines, $hdrs=3)
{
$res = array();
$c = array();
$i = 0;
$hdrs += 2;
foreach ($lines as $line) {
$i++;
if (0 === strpos($line, 'commit')) {
if (count($c) > 0) {
$c['full_message'] = trim($c['full_message']);
$res[] = (object) $c;
}
$c = array();
$c['commit'] = trim(substr($line, 7));
$c['full_message'] = '';
$i=1;
continue;
}
if ($i == $hdrs) {
$c['title'] = trim($line);
continue;
}
$match = array();
if (preg_match('/(\S+)\s*:\s*(.*)/', $line, $match)) {
$match[1] = strtolower($match[1]);
$c[$match[1]] = trim($match[2]);
if ($match[1] == 'date') {
$c['date'] = gmdate('Y-m-d H:i:s', strtotime($match[2]));
}
continue;
}
if ($i > ($hdrs+1)) {
$c['full_message'] .= trim($line)."\n";
continue;
}
}
$c['full_message'] = trim($c['full_message']);
$res[] = (object) $c;
return $res;
}
}

View File

@@ -21,6 +21,9 @@
#
# ***** END LICENSE BLOCK ***** */
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
Pluf::loadFunction('Pluf_Template_dateAgo');
/**
* Base definition of an issue.
*
@@ -132,11 +135,21 @@ 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 preDelete()
{
IDF_Timeline::remove($this);
IDF_Search::remove($this);
}
function preSave()
function preSave($create=false)
{
if ($this->id == '') {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
@@ -144,15 +157,170 @@ 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();
// Note: No indexing is performed here. The indexing is
// triggered in the postSave step of the comment to ensure
// that the issue as at least one comment in the database when
// doing the indexing.
if ($create) {
IDF_Timeline::insert($this, $this->get_project(),
$this->get_submitter());
}
}
/**
* Returns an HTML fragment used to display this issue in the
* timeline.
*
* The request object is given to be able to check the rights and
* as such create links to other items etc. You can consider that
* if displayed, you can create a link to it.
*
* @param Pluf_HTTP_Request
* @return Pluf_Template_SafeString
*/
public function timelineFragment($request)
{
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($request->project->shortname,
$this->id));
$out = '<tr class="log"><td><a href="'.$url.'">'.
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
'</a></td><td>';
$stag = new IDF_Template_ShowUser();
$user = $stag->start($this->get_submitter(), $request, '', false);
$ic = (in_array($this->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
$out .= sprintf(__('<a href="%1$s" class="%2$s" title="View issue">Issue %3$d</a>, %4$s'), $url, $ic, $this->id, Pluf_esc($this->summary)).'</td>';
$out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">issue&nbsp;%d</a>, by %s'), $url, $ic, $this->id, $user).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
public function feedFragment($request)
{
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($request->project->shortname,
$this->id));
$title = sprintf(__('%s: Issue %d created - %s'),
$request->project->name,
$this->id, $this->summary);
$cts = $this->get_comments_list(array('order' => 'id ASC',
'nb' => 1));
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
$context = new Pluf_Template_Context_Request(
$request,
array('url' => $url,
'author' => $this->get_submitter(),
'title' => $title,
'c' => $cts[0],
'issue' => $this,
'date' => $date)
);
$tmpl = new Pluf_Template('idf/issues/feedfragment.xml');
return $tmpl->render($context);
}
/**
* Notification of change of the object.
*
* For the moment, only email, but one can add webhooks later.
*
* Usage:
* <pre>
* $this->notify($conf); // Notify the creation
* $this->notify($conf, false); // Notify the update of the object
* </pre>
*
* @param IDF_Conf Current configuration
* @param bool Creation (true)
*/
public function notify($conf, $create=true)
{
$prj = $this->get_project();
$to_email = array();
if ('' != $conf->getVal('issues_notification_email', '')) {
$langs = Pluf::f('languages', array('en'));
$to_email[] = array($conf->getVal('issues_notification_email'),
$langs[0]);
}
$current_locale = Pluf_Translation::getLocale();
$id = '<'.md5($this->id.md5(Pluf::f('secret_key'))).'@'.Pluf::f('mail_host', 'localhost').'>';
if ($create) {
if (null != $this->get_owner() and $this->owner != $this->submitter) {
$email_lang = array($this->get_owner()->email,
$this->get_owner()->language);
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang;
}
}
$comments = $this->get_comments_list(array('order' => 'id ASC'));
$context = new Pluf_Template_Context(
array(
'issue' => $this,
'comment' => $comments[0],
'project' => $prj,
'url_base' => Pluf::f('url_base'),
)
);
foreach ($to_email as $email_lang) {
Pluf_Translation::loadSetLocale($email_lang[1]);
$email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
sprintf(__('Issue %s - %s (%s)'),
$this->id, $this->summary, $prj->shortname));
$tmpl = new Pluf_Template('idf/issues/issue-created-email.txt');
$email->addTextMessage($tmpl->render($context));
$email->addHeaders(array('Message-ID'=>$id));
$email->sendMail();
}
} else {
$comments = $this->get_comments_list(array('order' => 'id DESC'));
$email_sender = '';
if (isset($comments[0])) {
$email_sender = $comments[0]->get_submitter()->email;
}
foreach ($this->get_interested_list() as $interested) {
$email_lang = array($interested->email,
$interested->language);
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang;
}
}
$email_lang = array($this->get_submitter()->email,
$this->get_submitter()->language);
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang;
}
if (null != $this->get_owner()) {
$email_lang = array($this->get_owner()->email,
$this->get_owner()->language);
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang;
}
}
$context = new Pluf_Template_Context(
array(
'issue' => $this,
'comments' => $comments,
'project' => $prj,
'url_base' => Pluf::f('url_base'),
));
foreach ($to_email as $email_lang) {
if ($email_lang[0] == $email_sender) {
continue; // Do not notify the one having created
// the comment
}
Pluf_Translation::loadSetLocale($email_lang[1]);
$email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
sprintf(__('Updated Issue %s - %s (%s)'),
$this->id, $this->summary, $prj->shortname));
$tmpl = new Pluf_Template('idf/issues/issue-updated-email.txt');
$email->addTextMessage($tmpl->render($context));
$email->addHeaders(array('References'=>$id));
$email->sendMail();
}
}
Pluf_Translation::loadSetLocale($current_locale);
}
}

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,102 @@ class IDF_IssueComment extends Pluf_Model
return $this->content;
}
function preSave()
function preDelete()
{
IDF_Timeline::remove($this);
}
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();
if ($create) {
// Check if more than one comment for this issue. We do
// not want to insert the first comment in the timeline as
// the issue itself is inserted.
$sql = new Pluf_SQL('issue=%s', array($this->issue));
$co = Pluf::factory('IDF_IssueComment')->getList(array('filter'=>$sql->gen()));
if ($co->count() > 1) {
IDF_Timeline::insert($this, $this->get_issue()->get_project(),
$this->get_submitter());
}
}
IDF_Search::index($this->get_issue());
}
public function timelineFragment($request)
{
$issue = $this->get_issue();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($request->project->shortname,
$issue->id));
$url .= '#ic'.$this->id;
$out = "\n".'<tr class="log"><td><a href="'.$url.'">'.
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
'</a></td><td>';
$stag = new IDF_Template_ShowUser();
$user = $stag->start($this->get_submitter(), $request, '', false);
$ic = (in_array($issue->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
$out .= sprintf(__('<a href="%1$s" class="%2$s" title="View issue">Issue %3$d</a>, %4$s'), $url, $ic, $issue->id, Pluf_esc($issue->summary));
if ($this->changedIssue()) {
$out .= '<div class="issue-changes-timeline">';
foreach ($this->changes as $w => $v) {
$out .= '<strong>';
switch ($w) {
case 'su':
$out .= __('Summary:'); break;
case 'st':
$out .= __('Status:'); break;
case 'ow':
$out .= __('Owner:'); break;
case 'lb':
$out .= __('Labels:'); break;
}
$out .= '</strong>&nbsp;';
if ($w == 'lb') {
$out .= Pluf_esc(implode(', ', $v));
} else {
$out .= Pluf_esc($v);
}
$out .= ' ';
}
$out .= '</div>';
}
$out .= '</td></tr>';
$out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Comment on <a href="%s" class="%s">issue&nbsp;%d</a>, by %s'), $url, $ic, $issue->id, $user).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
public function feedFragment($request)
{
$issue = $this->get_issue();
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($request->project->shortname,
$issue->id));
$title = sprintf(__('%s: Comment on issue %d - %s'),
Pluf_esc($request->project->name),
$issue->id, Pluf_esc($issue->summary));
$url .= '#ic'.$this->id;
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
$context = new Pluf_Template_Context_Request(
$request,
array('url' => $url,
'author' => $issue->get_submitter(),
'title' => $title,
'c' => $this,
'issue' => $issue,
'date' => $date)
);
$tmpl = new Pluf_Template('idf/issues/feedfragment.xml');
return $tmpl->render($context);
}
}
function IDF_IssueComment_Filter($text)
{
return wordwrap($text, 80, "\n", true);
}

131
src/IDF/IssueFile.php Normal file
View File

@@ -0,0 +1,131 @@
<?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 ***** */
/**
* A file uploaded with an issue or a comment to an issue.
*
*/
class IDF_IssueFile extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_issuefiles';
$this->_a['model'] = __CLASS__;
$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,
),
'comment' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_IssueComment',
'blank' => false,
'verbose' => __('comment'),
'relate_name' => 'attachment',
),
'submitter' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User',
'blank' => false,
'verbose' => __('submitter'),
),
'filename' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => true,
'size' => 100,
'verbose' => __('file name'),
),
'attachment' =>
array(
'type' => 'Pluf_DB_Field_File',
'blank' => false,
'verbose' => __('the file'),
),
'filesize' =>
array(
'type' => 'Pluf_DB_Field_Integer',
'blank' => true,
'verbose' => __('file size'),
'help_text' => 'Size in bytes.',
),
'type' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 10,
'verbose' => __('type'),
'choices' => array(
__('Image') => 'img',
__('Other') => 'other',
),
'default' => 'other',
'help_text' => 'The type is to display a thumbnail of the image.',
),
'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'),
),
);
}
function preSave($create=false)
{
if ($this->id == '') {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
$file = Pluf::f('upload_issue_path').'/'.$this->attachment;
$this->filesize = filesize($file);
// remove .dummy
$this->filename = substr(basename($file), 0, -6);
$img_extensions = array('jpeg', 'jpg', 'png', 'gif');
$info = pathinfo($this->filename);
if (!isset($info['extension'])) $info['extension'] = '';
if (in_array(strtolower($info['extension']), $img_extensions)) {
$this->type = 'img';
} else {
$this->type = 'other';
}
}
$this->modif_dtime = gmdate('Y-m-d H:i:s');
}
function preDelete()
{
@unlink(Pluf::f('upload_issue_path').'/'.$this->attachment);
}
}

131
src/IDF/Key.php Normal file
View File

@@ -0,0 +1,131 @@
<?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 SSH keys.
*
*/
class IDF_Key extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_keys';
$this->_a['model'] = __CLASS__;
$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,
),
'user' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User',
'blank' => false,
'verbose' => __('user'),
),
'content' =>
array(
'type' => 'Pluf_DB_Field_Text',
'blank' => false,
'verbose' => __('ssh key'),
),
);
// WARNING: Not using getSqlTable on the Pluf_User object to
// avoid recursion.
$t_users = $this->_con->pfx.'users';
$this->_a['views'] = array(
'join_user' =>
array(
'join' => 'LEFT JOIN '.$t_users
.' ON '.$t_users.'.id='.$this->_con->qn('user'),
'select' => $this->getSelect().', '
.$t_users.'.login AS login',
'props' => array('login' => 'login'),
)
);
}
function showCompact()
{
return Pluf_Template::markSafe(Pluf_esc(substr($this->content, 0, 25)).' [...] '.Pluf_esc(substr($this->content, -55)));
}
function postSave($create=false)
{
/**
* [signal]
*
* IDF_Key::postSave
*
* [sender]
*
* IDF_Key
*
* [description]
*
* This signal allows an application to perform special
* operations after the saving of a SSH Key.
*
* [parameters]
*
* array('key' => $key,
* 'created' => true/false)
*
*/
$params = array('key' => $this, 'created' => $create);
Pluf_Signal::send('IDF_Key::postSave',
'IDF_Key', $params);
}
function preDelete()
{
/**
* [signal]
*
* IDF_Key::preDelete
*
* [sender]
*
* IDF_Key
*
* [description]
*
* This signal allows an application to perform special
* operations before a key is deleted.
*
* [parameters]
*
* array('key' => $key)
*
*/
$params = array('key' => $this);
Pluf_Signal::send('IDF_Key::preDelete',
'IDF_Key', $params);
}
}

View File

@@ -24,6 +24,7 @@
/**
* Project middleware.
*
* It must be after the session middleware.
*/
class IDF_Middleware
{
@@ -33,9 +34,9 @@ class IDF_Middleware
* When processing the request, check if matching a project. If
* so, directly set $request->project to the project.
*
* The url to match a project is in the format
* /p/(\w+)/whatever. This means that it will not try to match on
* /login/ or /logout/.
* The url to match a project is in the format /p/(\w+)/whatever
* or /api/p/(\w+)/whatever. This means that it will not try to
* match on /login/ or /logout/.
*
* @param Pluf_HTTP_Request The request
* @return bool false or redirect.
@@ -43,23 +44,71 @@ class IDF_Middleware
function process_request(&$request)
{
$match = array();
if (preg_match('#^/p/(\w+)/#', $request->query, $match)) {
$request->project = IDF_Project::getOr404($match[1]);
if (preg_match('#^/(?:api/p|p)/([\-\w]+)/?#', $request->query, $match)) {
try {
$request->project = IDF_Project::getOr404($match[1]);
} catch (Pluf_HTTP_Error404 $e) {
return new Pluf_HTTP_Response_NotFound($request);
}
$request->conf = new IDF_Conf();
$request->conf->setProject($request->project);
self::setRights($request);
}
return false;
}
public static function setRights(&$request)
{
$ak = array('downloads_access_rights' => 'hasDownloadsAccess',
'wiki_access_rights' => 'hasWikiAccess',
'review_access_rights' => 'hasReviewAccess',
'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));
}
}
/**
* Update the template tags and modifiers to not have them in the config.
*
* This is here at the moment because we do not want to put that
* in a IDF_Template class just for one method.
*
*/
public static function updateTemplateTagsModifiers($sender, &$params)
{
$params['tags'] = array_merge($params['tags'],
array(
'hotkey' => 'IDF_Template_HotKey',
'issuetext' => 'IDF_Template_IssueComment',
'timeline' => 'IDF_Template_TimelineFragment',
'markdown' => 'IDF_Template_Markdown',
'showuser' => 'IDF_Template_ShowUser',
'ashowuser' => 'IDF_Template_AssignShowUser',
));
$params['modifiers'] = array_merge($params['modifiers'],
array(
'size' => 'IDF_Views_Source_PrettySize',
'ssize' => 'IDF_Views_Source_PrettySizeSimple',
));
}
}
function IDF_Middleware_ContextPreProcessor($request)
{
$c = array();
$c['request'] = $request;
$c['isAdmin'] = ($request->user->administrator or $request->user->staff);
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 SSH key.
*/
function IDF_Migrations_10SshKey_up($params=null)
{
$models = array(
'IDF_Key',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->createTables();
}
}
function IDF_Migrations_10SshKey_down($params=null)
{
$models = array(
'IDF_Key',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->dropTables();
}
}

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 DB based Git cache.
*/
function IDF_Migrations_11GitCache_up($params=null)
{
$models = array(
'IDF_Scm_Cache_Git',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->createTables();
}
}
function IDF_Migrations_11GitCache_down($params=null)
{
$models = array(
'IDF_Scm_Cache_Git',
);
$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 private column for the project.
*/
function IDF_Migrations_12DownloadDesc_up($params=null)
{
$table = Pluf::factory('IDF_Upload')->getSqlTable();
$sql = array();
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' ADD COLUMN "changelog" TEXT DEFAULT \'\'';
$sql['MySQL'] = 'ALTER TABLE '.$table.' ADD COLUMN `changelog` LONGTEXT DEFAULT \'\'';
$db = Pluf::db();
$engine = Pluf::f('db_engine');
if (!isset($sql[$engine])) {
throw new Exception('SQLite complex migration not supported.');
}
$db->execute($sql[$engine]);
}
function IDF_Migrations_12DownloadDesc_down($params=null)
{
$table = Pluf::factory('IDF_Upload')->getSqlTable();
$sql = array();
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' DROP COLUMN "changelog"';
$sql['MySQL'] = 'ALTER TABLE '.$table.' DROP COLUMN `changelog`';
$db = Pluf::db();
$engine = Pluf::f('db_engine');
if (!isset($sql[$engine])) {
throw new Exception('SQLite complex migration not supported.');
}
$db->execute($sql[$engine]);
}

View File

@@ -0,0 +1,60 @@
<?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 ***** */
/**
* Remove the old review and add the new one.
*
* This is a destructive operation.
*/
function IDF_Migrations_13NewReview_up($params=null)
{
$extra = (Pluf::f('db_engine') == 'PostgreSQL') ? ' CASCADE' : '';
$pfx = Pluf::f('db_table_prefix');
$tables = array('idf_review_filecomments',
'idf_review_patches',
'idf_review_pluf_user_assoc',
'idf_review_idf_tag_assoc',
'idf_reviews');
$db = Pluf::db();
foreach ($tables as $table) {
$db->execute('DROP TABLE IF EXISTS '.$pfx.$table.$extra);
}
$models = array(
'IDF_Review',
'IDF_Review_Patch',
'IDF_Review_Comment',
'IDF_Review_FileComment',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->createTables();
}
}
function IDF_Migrations_13NewReview_down($params=null)
{
// We do nothing as we cannot go back to the old reviews
}

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

@@ -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_3Attachments_up($params=null)
{
$models = array(
'IDF_IssueFile',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->createTables();
}
}
function IDF_Migrations_3Attachments_down($params=null)
{
$models = array(
'IDF_IssueFile',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->dropTables();
}
}

View File

@@ -0,0 +1,54 @@
<?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_4Timeline_up($params=null)
{
$models = array(
'IDF_Commit',
'IDF_Timeline',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->createTables();
}
}
function IDF_Migrations_4Timeline_down($params=null)
{
$models = array(
'IDF_Timeline',
'IDF_Commit',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->dropTables();
}
}

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 ***** */
/**
* Add the download of files.
*/
function IDF_Migrations_5DescToText_up($params=null)
{
$table = Pluf::factory('IDF_Conf')->getSqlTable();
$sql = array();
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' ALTER vdesc TYPE text';
$sql['MySQL'] = 'ALTER TABLE '.$table.' CHANGE vdesc TYPE text';
$db = Pluf::db();
$engine = Pluf::f('db_engine');
if (!isset($sql[$engine])) {
echo 'Skip SQLite upgrade as not needed.'."\n";
return;
}
$db->execute($sql[$engine]);
}
function IDF_Migrations_5DescToText_down($params=null)
{
// lazy, do not care
return;
}

View File

@@ -0,0 +1,63 @@
<?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 private column for the project.
*/
function IDF_Migrations_6PrivateProject_up($params=null)
{
$table = Pluf::factory('IDF_Project')->getSqlTable();
$sql = array();
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' ADD COLUMN "private" INTEGER DEFAULT 0';
$sql['MySQL'] = 'ALTER TABLE '.$table.' ADD COLUMN `private` INTEGER DEFAULT 0';
$db = Pluf::db();
$engine = Pluf::f('db_engine');
if (!isset($sql[$engine])) {
throw new Exception('SQLite complex migration not supported.');
}
$db->execute($sql[$engine]);
$perm = new Pluf_Permission();
$perm->name = 'Project authorized users';
$perm->code_name = 'project-authorized-user';
$perm->description = 'Permission given to users allowed to access a project.';
$perm->application = 'IDF';
$perm->create();
}
function IDF_Migrations_6PrivateProject_down($params=null)
{
$perm = Pluf_Permission::getFromString('IDF.project-authorized-user');
if ($perm) $perm->delete();
$table = Pluf::factory('IDF_Project')->getSqlTable();
$sql = array();
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' DROP COLUMN "private"';
$sql['MySQL'] = 'ALTER TABLE '.$table.' DROP COLUMN `private`';
$db = Pluf::db();
$engine = Pluf::f('db_engine');
if (!isset($sql[$engine])) {
throw new Exception('SQLite complex migration not supported.');
}
$db->execute($sql[$engine]);
}

View File

@@ -0,0 +1,54 @@
<?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_7Wiki_up($params=null)
{
$models = array(
'IDF_WikiPage',
'IDF_WikiRevision',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->createTables();
}
}
function IDF_Migrations_7Wiki_down($params=null)
{
$models = array(
'IDF_WikiRevision',
'IDF_WikiPage',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->dropTables();
}
}

View File

@@ -0,0 +1,56 @@
<?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 code review.
*/
function IDF_Migrations_8CodeReview_up($params=null)
{
$models = array(
'IDF_Review',
'IDF_Review_Patch',
'IDF_Review_FileComment',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->createTables();
}
}
function IDF_Migrations_8CodeReview_down($params=null)
{
$models = array(
'IDF_Review_FileComment',
'IDF_Review_Patch',
'IDF_Review',
);
$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 private column for the project.
*/
function IDF_Migrations_9ShortDescription_up($params=null)
{
$table = Pluf::factory('IDF_Project')->getSqlTable();
$sql = array();
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' ADD COLUMN "shortdesc" VARCHAR(255) DEFAULT \'\'';
$sql['MySQL'] = 'ALTER TABLE '.$table.' ADD COLUMN `shortdesc` VARCHAR(255) DEFAULT \'\'';
$db = Pluf::db();
$engine = Pluf::f('db_engine');
if (!isset($sql[$engine])) {
throw new Exception('SQLite complex migration not supported.');
}
$db->execute($sql[$engine]);
}
function IDF_Migrations_9ShortDescription_down($params=null)
{
$table = Pluf::factory('IDF_Project')->getSqlTable();
$sql = array();
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' DROP COLUMN "shortdesc"';
$sql['MySQL'] = 'ALTER TABLE '.$table.' DROP COLUMN `shortdesc`';
$db = Pluf::db();
$engine = Pluf::f('db_engine');
if (!isset($sql[$engine])) {
throw new Exception('SQLite complex migration not supported.');
}
$db->execute($sql[$engine]);
}

View File

@@ -0,0 +1,109 @@
<?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 ***** */
/**
* Backup of InDefero.
*
* !! You need also to backup Pluf if you want the full backup. !!
*
* @param string Path to the folder where to store the backup
* @param string Name of the backup (null)
* @return int The backup was correctly written
*/
function IDF_Migrations_Backup_run($folder, $name=null)
{
$models = array(
'IDF_Project',
'IDF_Tag',
'IDF_Issue',
'IDF_IssueComment',
'IDF_Conf',
'IDF_Upload',
'IDF_Search_Occ',
'IDF_IssueFile',
'IDF_Commit',
'IDF_Timeline',
'IDF_WikiPage',
'IDF_WikiRevision',
'IDF_Review',
'IDF_Review_Patch',
'IDF_Review_Comment',
'IDF_Review_FileComment',
'IDF_Key',
'IDF_Scm_Cache_Git',
);
$db = Pluf::db();
// Now, for each table, we dump the content in json, this is a
// memory intensive operation
$to_json = array();
foreach ($models as $model) {
$to_json[$model] = Pluf_Test_Fixture::dump($model, false);
}
if (null == $name) {
$name = date('Y-m-d');
}
return file_put_contents(sprintf('%s/%s-IDF.json', $folder, $name),
json_encode($to_json), LOCK_EX);
}
/**
* Restore IDF from a backup.
*
* @param string Path to the backup folder
* @param string Backup name
* @return bool Success
*/
function IDF_Migrations_Backup_restore($folder, $name)
{
$models = array(
'IDF_Project',
'IDF_Tag',
'IDF_Issue',
'IDF_IssueComment',
'IDF_Conf',
'IDF_Upload',
'IDF_Search_Occ',
'IDF_IssueFile',
'IDF_Commit',
'IDF_Timeline',
'IDF_WikiPage',
'IDF_WikiRevision',
'IDF_Review',
'IDF_Review_Patch',
'IDF_Review_Comment',
'IDF_Review_FileComment',
'IDF_Key',
'IDF_Scm_Cache_Git',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->createTables();
}
$full_data = json_decode(file_get_contents(sprintf('%s/%s-IDF.json', $folder, $name)), true);
foreach ($full_data as $model => $data) {
Pluf_Test_Fixture::load($data, false);
}
return true;
}

View File

@@ -35,6 +35,19 @@ function IDF_Migrations_Install_setup($params=null)
'IDF_Issue',
'IDF_IssueComment',
'IDF_Conf',
'IDF_Upload',
'IDF_Search_Occ',
'IDF_IssueFile',
'IDF_Commit',
'IDF_Timeline',
'IDF_WikiPage',
'IDF_WikiRevision',
'IDF_Review',
'IDF_Review_Patch',
'IDF_Review_Comment',
'IDF_Review_FileComment',
'IDF_Key',
'IDF_Scm_Cache_Git',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
@@ -55,6 +68,12 @@ function IDF_Migrations_Install_setup($params=null)
$perm->description = 'Permission given to project owners.';
$perm->application = 'IDF';
$perm->create();
$perm = new Pluf_Permission();
$perm->name = 'Project authorized users';
$perm->code_name = 'project-authorized-user';
$perm->description = 'Permission given to users allowed to access a project.';
$perm->application = 'IDF';
$perm->create();
}
function IDF_Migrations_Install_teardown($params=null)
@@ -63,11 +82,26 @@ function IDF_Migrations_Install_teardown($params=null)
if ($perm) $perm->delete();
$perm = Pluf_Permission::getFromString('IDF.project-owner');
if ($perm) $perm->delete();
$perm = Pluf_Permission::getFromString('IDF.project-authorized-user');
if ($perm) $perm->delete();
$models = array(
'IDF_Scm_Cache_Git',
'IDF_Key',
'IDF_Review_FileComment',
'IDF_Review_Comment',
'IDF_Review_Patch',
'IDF_Review',
'IDF_WikiRevision',
'IDF_WikiPage',
'IDF_Timeline',
'IDF_IssueFile',
'IDF_Search_Occ',
'IDF_Upload',
'IDF_Conf',
'IDF_IssueComment',
'IDF_Issue',
'IDF_Tag',
'IDF_Commit',
'IDF_Project',
);
$db = Pluf::db();

View File

@@ -0,0 +1,81 @@
<?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 ***** */
/**
* This class is a plugin which allows to synchronise access riths
* between InDefero and a common restricted SSH account for git
* access.
*
* As the authentication is directly performed by accessing the
* InDefero database, we only need to synchronize the SSH keys. This
* synchronization process can only be performed by a process running
* under the git user as we need to write in
* /home/git/.ssh/authorized_keys
*
* So, here, we are just creating a file informing that a sync needs
* to be done. We connect this plugin to the IDF_Key::postSave signal.
*/
class IDF_Plugin_SyncGit
{
/**
* Entry point of the plugin.
*/
static public function entry($signal, &$params)
{
// First check for the single mandatory config variable.
if (!Pluf::f('idf_plugin_syncgit_sync_file', false)) {
Pluf_Log::debug('IDF_Plugin_SyncGit plugin not configured.');
return;
}
if ($signal != 'gitpostupdate.php::run') {
Pluf_Log::event('IDF_Plugin_SyncGit', 'create',
Pluf::f('idf_plugin_syncgit_sync_file'));
@touch(Pluf::f('idf_plugin_syncgit_sync_file'));
@chmod(Pluf::f('idf_plugin_syncgit_sync_file'), 0777);
} else {
self::postUpdate($signal, $params);
}
}
/**
* Entry point for the post-update signal.
*
* It tries to find the name of the project, when found it runs an
* update of the timeline.
*/
static public function postUpdate($signal, &$params)
{
// Chop the ".git" and get what is left
$pname = basename($params['git_dir'], '.git');
try {
$project = IDF_Project::getOr404($pname);
} catch (Pluf_HTTP_Error404 $e) {
Pluf_Log::event(array('IDF_Plugin_SyncGit::postUpdate', 'Project not found.', array($pname, $params)));
return false; // Project not found
}
// Now we have the project and can update the timeline
Pluf_Log::debug(array('IDF_Plugin_SyncGit::postUpdate', 'Project found', $pname, $project->id));
IDF_Scm::syncTimeline($project, true);
Pluf_Log::event(array('IDF_Plugin_SyncGit::postUpdate', 'sync', array($pname, $project->id)));
}
}

View File

@@ -0,0 +1,125 @@
<?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 ***** */
/**
* Synchronize the SSH keys with InDefero.
*/
class IDF_Plugin_SyncGit_Cron
{
/**
* Template for the SSH key.
*/
public $template = 'command="python %s %s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s';
/**
* Synchronize.
*/
public static function sync()
{
$template = Pluf::factory(__CLASS__)->template;
$cmd = Pluf::f('idf_plugin_syncgit_path_gitserve', '/dev/null');
$authorized_keys = Pluf::f('idf_plugin_syncgit_path_authorized_keys', false);
if (false == $authorized_keys) {
throw new Pluf_Exception_SettingError('Setting idf_plugin_syncgit_path_authorized_keys not set.');
}
if (!is_writable($authorized_keys)) {
throw new Exception('Cannot create file: '.$authorized_keys);
}
$out = '';
$keys = Pluf::factory('IDF_Key')->getList(array('view'=>'join_user'));
foreach ($keys as $key) {
if (strlen($key->content) > 40 // minimal check
and preg_match('/^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$/', $key->login)) {
$content = trim(str_replace(array("\n", "\r"), '', $key->content));
$out .= sprintf($template, $cmd, $key->login, $content)."\n";
}
}
file_put_contents($authorized_keys, $out, LOCK_EX);
}
/**
* Mark export of git repositories for the daemon.
*/
public static function markExport()
{
foreach (Pluf::factory('IDF_Project')->getList() as $project) {
$rep = sprintf(Pluf::f('git_repositories'), $project->shortname);
$serve = new IDF_Plugin_SyncGit_Serve();
$serve->setGitExport($project->shortname, $rep);
}
}
/**
* Remove orphan repositories.
*/
public static function removeOrphanRepositories()
{
$path = Pluf::f('idf_plugin_syncgit_base_repositories', '/home/git/repositories');
if (!is_dir($path) || is_link($path)) {
throw new Pluf_Exception_SettingError(sprintf(
'Directory %s does not exist! Setting "idf_plugin_syncgit_base_repositories not set.',
$path));
}
if (!is_writable($path)) {
throw new Exception(sprintf('Repository %s is not writable.', $path));
}
$projects = array();
foreach (Pluf::factory('IDF_Project')->getList() as $project) {
$projects[] = $project->shortname;
}
unset($project);
$it = new DirectoryIterator($path);
$orphans = array();
while ($it->valid()) {
if (!$it->isDot() && $it->isDir() && !in_array(basename($it->getFileName(), '.git'), $projects)) {
$orphans[] = $it->getPathName();
}
$it->next();
}
if (count($orphans)) {
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'rm -rf '.implode(' ', $orphans);
exec($cmd);
while (list(, $project) = each($orphans)) {
if (is_dir($project)) {
throw new Exception(sprintf('Cannot remove %s directory.', $project));
}
}
}
}
/**
* Check if a sync is needed.
*
*/
public static function main()
{
if (file_exists(Pluf::f('idf_plugin_syncgit_sync_file'))) {
@unlink(Pluf::f('idf_plugin_syncgit_sync_file'));
self::sync();
self::markExport();
if (Pluf::f('idf_plugin_syncgit_remove_orphans', false)) {
self::removeOrphanRepositories();
}
}
}
}

View File

@@ -0,0 +1,294 @@
<?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 ***** */
/**
* Main application to serve git repositories through a restricted SSH
* access.
*/
class IDF_Plugin_SyncGit_Serve
{
/**
* Regular expression to match the path in the git command.
*/
public $preg = '#^\'/*(?P<path>[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)\'$#';
public $commands_readonly = array('git-upload-pack', 'git upload-pack');
public $commands_write = array('git-receive-pack', 'git receive-pack');
/**
* Serve a git request.
*
* @param string Username.
* @param string Command to be run.
*/
public function serve($username, $cmd)
{
if (false !== strpos($cmd, "\n")) {
throw new Exception('Command may not contain newline.');
}
$splitted = preg_split('/\s/', $cmd, 2);
if (count($splitted) != 2) {
throw new Exception('Unknown command denied.');
}
if ($splitted[0] == 'git') {
$sub_splitted = preg_split('/\s/', $splitted[1], 2);
if (count($sub_splitted) != 2) {
throw new Exception('Unknown command denied.');
}
$verb = sprintf('%s %s', $splitted[0], $sub_splitted[0]);
$args = $sub_splitted[1];
} else {
$verb = $splitted[0];
$args = $splitted[1];
}
if (!in_array($verb, $this->commands_write)
and !in_array($verb, $this->commands_readonly)) {
throw new Exception('Unknown command denied.');
}
if (!preg_match($this->preg, $args, $matches)) {
throw new Exception('Arguments to command look dangerous.');
}
$path = $matches['path'];
// Check read/write rights
$new_path = $this->haveAccess($username, $path, 'writable');
if ($new_path == false) {
$new_path = $this->haveAccess($username, $path, 'readonly');
if ($new_path == false) {
throw new Exception('Repository read access denied.');
}
if (in_array($verb, $this->commands_write)) {
throw new Exception('Repository write access denied.');
}
}
list($topdir, $relpath) = $new_path;
$repopath = sprintf('%s.git', $relpath);
$fullpath = $topdir.DIRECTORY_SEPARATOR.$repopath;
if (!file_exists($fullpath)
and in_array($verb, $this->commands_write)) {
// it doesn't exist on the filesystem, but the
// configuration refers to it, we're serving a write
// request, and the user is authorized to do that: create
// the repository on the fly
$p = explode(DIRECTORY_SEPARATOR, $fullpath);
$mpath = implode(DIRECTORY_SEPARATOR, array_slice($p, 0, -1));
if (!file_exists($mpath)) {
mkdir($mpath, 0750, true);
}
$this->initRepository($fullpath);
$this->setGitExport($relpath, $fullpath);
}
$new_cmd = sprintf("%s '%s'", $verb, $fullpath);
Pluf_Log::info(array('IDF_Plugin_Git_Serve::serve', $username, $cmd, $new_cmd));
return $new_cmd;
}
/**
* Main function called by the serve script.
*/
public static function main($argv, $env)
{
if (count($argv) != 2) {
self::fatalError('Missing argument USER.');
}
$username = $argv[1];
umask(0022);
if (!isset($env['SSH_ORIGINAL_COMMAND'])) {
self::fatalError('Need SSH_ORIGINAL_COMMAND in environment.');
}
$cmd = $env['SSH_ORIGINAL_COMMAND'];
chdir(Pluf::f('idf_plugin_syncgit_git_home_dir', '/home/git'));
$serve = new IDF_Plugin_SyncGit_Serve();
try {
$new_cmd = $serve->serve($username, $cmd);
} catch (Exception $e) {
self::fatalError($e->getMessage());
}
print $new_cmd;
exit(0);
}
/**
* Control the access rights to the repository.
*
* @param string Username
* @param string Path including the possible .git
* @param string Type of access. 'readonly' or ('writable')
* @return mixed False or array(base_git_reps, relative path to repo)
*/
public function haveAccess($username, $path, $mode='writable')
{
if ('.git' == substr($path, -4)) {
$path = substr($path, 0, -4);
}
$sql = new Pluf_SQL('shortname=%s', array($path));
$projects = Pluf::factory('IDF_Project')->getList(array('filter'=>$sql->gen()));
if ($projects->count() != 1) {
return false;
}
$project = $projects[0];
$conf = new IDF_Conf();
$conf->setProject($project);
$scm = $conf->getVal('scm', 'git');
if ($scm != 'git') {
return false;
}
$sql = new Pluf_SQL('login=%s', array($username));
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
if ($users->count() != 1 or !$users[0]->active) {
return false;
}
$user = $users[0];
$request = new StdClass();
$request->user = $user;
$request->conf = $conf;
$request->project = $project;
if (true === IDF_Precondition::accessSource($request)) {
if ($mode == 'readonly') {
return array(Pluf::f('idf_plugin_syncgit_base_repositories', '/home/git/repositories'),
$project->shortname);
}
if (true === IDF_Precondition::projectMemberOrOwner($request)) {
return array(Pluf::f('idf_plugin_syncgit_base_repositories', '/home/git/repositories'),
$project->shortname);
}
}
return false;
}
/**
* Die on a message on stderr.
*
* @param string Message
*/
public static function fatalError($mess)
{
fwrite(STDERR, $mess."\n");
exit(1);
}
/**
* Init a new empty bare repository.
*
* @param string Full path to the repository
*/
public function initRepository($fullpath)
{
if (!file_exists($fullpath)) {
mkdir($fullpath, 0750, true);
}
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').
Pluf::f('git_path', 'git').' --git-dir=%s init', escapeshellarg($fullpath)),
$out, $res);
if ($res != 0) {
Pluf_Log::error(array('IDF_Plugin_Git_Serve::initRepository', $res, $fullpath));
throw new Exception(sprintf('Init repository error, exit status %d.', $res));
}
Pluf_Log::event(array('IDF_Plugin_Git_Serve::initRepository', 'success', $fullpath));
// Add the post-update hook by removing the original one and add the
// Indefero's one.
$p = realpath(dirname(__FILE__).'/../../../../scripts/git-post-update');
$p = Pluf::f('idf_plugin_syncgit_post_update', $p);
if (!@unlink($fullpath.'/hooks/post-update')) {
Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository',
'post-update hook removal error.',
$fullpath.'/hooks/post-update'));
return;
}
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s',
escapeshellarg($p),
escapeshellarg($fullpath.'/hooks/post-update')),
$out, $res);
if ($res != 0) {
Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository',
'post-update hook creation error.',
$fullpath.'/hooks/post-update'));
return;
}
Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository',
'Added post-update hook.', $fullpath));
}
/**
* Set the git export value.
*
* @param string Relative path of the repository (not .git)
* @param string Full path of the repository with .git
*/
public function setGitExport($relpath, $fullpath)
{
$sql = new Pluf_SQL('shortname=%s', array($relpath));
$projects = Pluf::factory('IDF_Project')->getList(array('filter'=>$sql->gen()));
if ($projects->count() != 1 and file_exists($fullpath)) {
return $this->gitExportDeny($fullpath);
}
$project = $projects[0];
$conf = new IDF_Conf();
$conf->setProject($project);
$scm = $conf->getVal('scm', 'git');
if ($scm == 'git' and !file_exists($fullpath)) {
// No repository yet, just skip
return false;
}
if ($scm != 'git' or $project->private) {
return $this->gitExportDeny($fullpath);
}
if ('all' == $conf->getVal('source_access_rights', 'all')) {
return $this->gitExportAllow($fullpath);
}
return $this->gitExportDeny($fullpath);
}
/**
* Remove the export flag.
*
* @param string Full path to the repository
*/
public function gitExportDeny($fullpath)
{
if (!file_exists($fullpath)) {
return; // Not created yet.
}
@unlink($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok');
if (file_exists($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok')) {
throw new Exception('Cannot remove git-daemon-export-ok file.');
}
return true;
}
/**
* Set the export flag.
*
* @param string Full path to the repository
*/
public function gitExportAllow($fullpath)
{
if (!file_exists($fullpath)) {
return; // Not created yet.
}
touch($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok');
if (!file_exists($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok')) {
throw new Exception('Cannot create git-daemon-export-ok file.');
}
return true;
}
}

View File

@@ -0,0 +1,249 @@
<?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 ***** */
/**
* This classes is a plugin which allows to synchronise access rights
* between indefero and mercurial web-published repositories.
*/
class IDF_Plugin_SyncMercurial
{
/**
* Entry point of the plugin.
*/
static public function entry($signal, &$params)
{
// First check for the 3 mandatory config variables.
if (!Pluf::f('idf_plugin_syncmercurial_passwd_file', false) or
!Pluf::f('idf_plugin_syncmercurial_path', false) or
!Pluf::f('idf_plugin_syncmercurial_hgrc', false)) {
return;
}
include_once 'File/Passwd/Authdigest.php';
$plug = new IDF_Plugin_SyncMercurial();
switch ($signal) {
case 'IDF_Project::created':
$plug->processMercurialCreate($params['project']);
break;
case 'IDF_Project::membershipsUpdated':
$plug->processSyncAuthz($params['project']);
break;
case 'Pluf_User::passwordUpdated':
$plug->processSyncPasswd($params['user']);
break;
case 'hgchangegroup.php::run':
$plug->processSyncTimeline($params);
break;
}
}
/**
* Run hg init command to create the corresponding Mercurial
* repository.
*
* @param IDF_Project
* @return bool Success
*/
function processMercurialCreate($project)
{
if ($project->getConf()->getVal('scm') != 'mercurial') {
return false;
}
$shortname = $project->shortname;
if (false===($mercurial_path=Pluf::f('idf_plugin_syncmercurial_path',false))) {
throw new Pluf_Exception_SettingError("'idf_plugin_syncmercurial_path' must be defined in your configuration file.");
}
if (file_exists($mercurial_path.'/'.$shortname)) {
throw new Exception(sprintf(__('The repository %s already exists.'),
$mercurial_path.'/'.$shortname));
}
$return = 0;
$output = array();
$cmd = sprintf(Pluf::f('hg_path', 'hg').' init %s',
escapeshellarg($mercurial_path.'/'.$shortname));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$ll = exec($cmd, $output, $return);
return ($return == 0);
}
/**
* Synchronise an user's password.
*
* @param Pluf_User
*/
function processSyncPasswd($user)
{
$passwd_file = Pluf::f('idf_plugin_syncmercurial_passwd_file');
if (!file_exists($passwd_file) or !is_writable($passwd_file)) {
return false;
}
$ht = new File_Passwd_Authbasic($passwd_file);
$ht->load();
$ht->setMode(Pluf::f('idf_plugin_syncmercurial_passwd_mode',
FILE_PASSWD_SHA));
if ($ht->userExists($user->login)) {
$ht->changePasswd($user->login, $this->getMercurialPass($user));
} else {
$ht->addUser($user->login, $this->getMercurialPass($user));
}
$ht->save();
return true;
}
/**
* Synchronize the hgrc file and the passwd file for the project.
*
* @param IDF_Project
*/
function processSyncAuthz($project)
{
if ($project->getConf()->getVal('scm') != 'mercurial') {
return false;
}
$this->SyncAccess($project);
$this->generateProjectPasswd($project);
}
/**
* Get the repository password for the user
*/
function getMercurialPass($user){
return substr(sha1($user->password.Pluf::f('secret_key')), 0, 8);
}
/**
* For a particular project: update all passwd information
*/
function generateProjectPasswd($project)
{
$passwd_file = Pluf::f('idf_plugin_syncmercurial_passwd_file');
if (!file_exists($passwd_file) or !is_writable($passwd_file)) {
throw new Exception (sprintf(__('%s does not exist or is not writable.'), $passwd_file));
}
$ht = new File_Passwd_Authbasic($passwd_file);
$ht->setMode(Pluf::f('idf_plugin_syncmercurial_passwd_mode',
FILE_PASSWD_SHA));
$ht->load();
$mem = $project->getMembershipData();
$members = array_merge((array)$mem['members'], (array)$mem['owners'],
(array)$mem['authorized']);
foreach($members as $user) {
if ($ht->userExists($user->login)) {
$ht->changePasswd($user->login, $this->getMercurialPass($user));
} else {
$ht->addUser($user->login, $this->getMercurialPass($user));
}
}
$ht->save();
}
/**
* Generate the hgrc file
*/
function SyncAccess($project)
{
$shortname = $project->shortname;
$hgrc_file = Pluf::f('idf_plugin_syncmercurial_path').sprintf('/%s/.hg/hgrc', $shortname);
// Get allow_push list
$allow_push = '';
$mem = $project->getMembershipData();
foreach ($mem['owners'] as $v) {
$allow_push .= $v->login.' ';
}
foreach ($mem['members'] as $v) {
$allow_push .= $v->login.' ';
}
// Generate hgrc content
if (is_file($hgrc_file)) {
$tmp_content = parse_ini_file($hgrc_file, true);
$tmp_content['web']['allow_push'] = $allow_push;
}
else {
$tmp_content = Pluf::f('idf_plugin_syncmercurial_hgrc');
$tmp_content['web']['allow_push'] = $allow_push;
}
$fcontent = '';
foreach ($tmp_content as $key => $elem){
$fcontent .= '['.$key."]\n";
foreach ($elem as $key2 => $elem2){
$fcontent .= $key2.' = '.$elem2."\n";
}
}
file_put_contents($hgrc_file, $fcontent, LOCK_EX);
// Generate private repository config file
$private_file = Pluf::f('idf_plugin_syncmercurial_private_include');
$notify_file = Pluf::f('idf_plugin_syncmercurial_private_notify');
$fcontent = '';
foreach (Pluf::factory('IDF_Project')->getList() as $project) {
$conf = new IDF_Conf();
$conf->setProject($project);
if ($project->private == true){
$mem = $project->getMembershipData();
$user = '';
foreach ($mem['owners'] as $v) {
$user .= $v->login.' ';
}
foreach ($mem['members'] as $v) {
$user .= $v->login.' ';
}
foreach ($mem['authorized'] as $v) {
$user .= $v->login.' ';
}
$fcontent .= '<Location '. sprintf(Pluf::f('idf_plugin_syncmercurial_private_url'), $project->shortname).'>'."\n";
$fcontent .= 'AuthType Basic'."\n";
$fcontent .= 'AuthName "Restricted"'."\n";
$fcontent .= sprintf('AuthUserFile %s', Pluf::f('idf_plugin_syncmercurial_passwd_file'))."\n";
$fcontent .= sprintf('Require user %s', $user)."\n";
$fcontent .= '</Location>'."\n\n";
}
}
file_put_contents($private_file, $fcontent, LOCK_EX);
file_put_contents($notify_file, ' ', LOCK_EX);
return true;
}
/**
* Update the timeline in post commit.
*
*/
public function processSyncTimeline($params)
{
$pname = basename($params['rel_dir']);
try {
$project = IDF_Project::getOr404($pname);
} catch (Pluf_HTTP_Error404 $e) {
Pluf_Log::event(array('IDF_Plugin_SyncMercurial::processSyncTimeline', 'Project not found.', array($pname, $params)));
return false; // Project not found
}
// Now we have the project and can update the timeline
Pluf_Log::debug(array('IDF_Plugin_SyncMercurial::processSyncTimeline', 'Project found', $pname, $project->id));
IDF_Scm::syncTimeline($project, true);
Pluf_Log::event(array('IDF_Plugin_SyncMercurial::processSyncTimeline', 'sync', array($pname, $project->id)));
}
}

259
src/IDF/Plugin/SyncSvn.php Normal file
View File

@@ -0,0 +1,259 @@
<?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 ***** */
/**
* This classes is a plugin which allows to synchronise access rights
* between indefero and a DAV powered Subversion repository.
*/
class IDF_Plugin_SyncSvn
{
/**
* Entry point of the plugin.
*/
static public function entry($signal, &$params)
{
// First check for the 3 mandatory config variables.
if (!Pluf::f('idf_plugin_syncsvn_authz_file', false) or
!Pluf::f('idf_plugin_syncsvn_passwd_file', false) or
!Pluf::f('idf_plugin_syncsvn_svn_path', false)) {
return;
}
include_once 'File/Passwd/Authdigest.php'; // $ pear install File_Passwd
$plug = new IDF_Plugin_SyncSvn();
switch ($signal) {
case 'IDF_Project::created':
$plug->processSvnCreate($params['project']);
break;
case 'IDF_Project::membershipsUpdated':
$plug->processSyncAuthz($params['project']);
break;
case 'Pluf_User::passwordUpdated':
$plug->processSyncPasswd($params['user']);
break;
case 'IDF_Project::preDelete':
$plug->processSvnDelete($params['project']);
break;
case 'svnpostcommit.php::run':
$plug->processSvnUpdateTimeline($params);
break;
}
}
/**
* Run svnadmin command to create the corresponding Subversion
* repository.
*
* @param IDF_Project
* @return bool Success
*/
function processSvnCreate($project)
{
if ($project->getConf()->getVal('scm') != 'svn') {
return false;
}
$shortname = $project->shortname;
if (false===($svn_path=Pluf::f('idf_plugin_syncsvn_svn_path',false))) {
throw new Pluf_Exception_SettingError("'idf_plugin_syncsvn_svn_path' must be defined in your configuration file.");
}
if (file_exists($svn_path.'/'.$shortname)) {
throw new Exception(sprintf(__('The repository %s already exists.'),
$svn_path.'/'.$shortname));
}
$return = 0;
$output = array();
$cmd = sprintf(Pluf::f('svnadmin_path', 'svnadmin').' create %s',
escapeshellarg($svn_path.'/'.$shortname));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$ll = exec($cmd, $output, $return);
return ($return == 0);
}
/**
* Remove the project from the drive and update the access rights.
*
* @param IDF_Project
* @return bool Success
*/
function processSvnDelete($project)
{
if (!Pluf::f('idf_plugin_syncsvn_remove_orphans', false)) {
return;
}
if ($project->getConf()->getVal('scm') != 'svn') {
return false;
}
$this->SyncAccess($project); // exclude $project
$shortname = $project->shortname;
if (false===($svn_path=Pluf::f('idf_plugin_syncsvn_svn_path',false))) {
throw new Pluf_Exception_SettingError("'idf_plugin_syncsvn_svn_path' must be defined in your configuration file.");
}
if (file_exists($svn_path.'/'.$shortname)) {
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'rm -rf '.$svn_path.'/'.$shortname;
exec($cmd);
}
}
/**
* Synchronise an user's password.
*
* @param Pluf_User
*/
function processSyncPasswd($user)
{
$passwd_file = Pluf::f('idf_plugin_syncsvn_passwd_file');
if (!file_exists($passwd_file) or !is_writable($passwd_file)) {
return false;
}
$ht = new File_Passwd_Authbasic($passwd_file);
$ht->load();
$ht->setMode(FILE_PASSWD_SHA);
if ($ht->userExists($user->login)) {
$ht->changePasswd($user->login, $this->getSvnPass($user));
} else {
$ht->addUser($user->login, $this->getSvnPass($user));
}
$ht->save();
return true;
}
/**
* Synchronize the authz file and the passwd file for the project.
*
* @param IDF_Project
*/
function processSyncAuthz($project)
{
$this->SyncAccess();
$this->generateProjectPasswd($project);
}
/**
* Get the repository password for the user
*/
function getSvnPass($user){
return substr(sha1($user->password.Pluf::f('secret_key')), 0, 8);
}
/**
* For a particular project: update all passwd information
*/
function generateProjectPasswd($project)
{
$passwd_file = Pluf::f('idf_plugin_syncsvn_passwd_file');
if (!file_exists($passwd_file) or !is_writable($passwd_file)) {
return false;
}
$ht = new File_Passwd_Authbasic($passwd_file);
$ht->setMode(FILE_PASSWD_SHA);
$ht->load();
$mem = $project->getMembershipData();
$members = array_merge((array)$mem['members'], (array)$mem['owners'],
(array)$mem['authorized']);
foreach($members as $user) {
if ($ht->userExists($user->login)) {
$ht->changePasswd($user->login, $this->getSvnPass($user));
} else {
$ht->addUser($user->login, $this->getSvnPass($user));
}
}
$ht->save();
}
/**
* Generate the dav_svn.authz file
*
* We rebuild the complete file each time. This is just to be sure
* not to bork the rights when trying to just edit part of the
* file.
*
* @param IDF_Project Possibly exclude a project (null)
*/
function SyncAccess($exclude=null)
{
$authz_file = Pluf::f('idf_plugin_syncsvn_authz_file');
$access_owners = Pluf::f('idf_plugin_syncsvn_access_owners', 'rw');
$access_members = Pluf::f('idf_plugin_syncsvn_access_members', 'rw');
$access_extra = Pluf::f('idf_plugin_syncsvn_access_extra', 'r');
$access_public = Pluf::f('idf_plugin_syncsvn_access_public', 'r');
$access_public_priv = Pluf::f('idf_plugin_syncsvn_access_private', '');
if (!file_exists($authz_file) or !is_writable($authz_file)) {
return false;
}
$fcontent = '';
foreach (Pluf::factory('IDF_Project')->getList() as $project) {
if ($exclude and $exclude->id == $project->id) {
continue;
}
$conf = new IDF_Conf();
$conf->setProject($project);
if ($conf->getVal('scm') != 'svn' or
strlen($conf->getVal('svn_remote_url')) > 0) {
continue;
}
$mem = $project->getMembershipData();
// [shortname:/]
$fcontent .= '['.$project->shortname.':/]'."\n";
foreach ($mem['owners'] as $v) {
$fcontent .= $v->login.' = '.$access_owners."\n";
}
foreach ($mem['members'] as $v) {
$fcontent .= $v->login.' = '.$access_members."\n";
}
// access for all users
if ($project->private == true) {
foreach ($mem['authorized'] as $v) {
$fcontent .= $v->login.' = '.$access_extra."\n";
}
$fcontent .= '* = '.$access_public_priv."\n";
} else {
$fcontent .= '* = '.$access_public."\n";
}
$fcontent .= "\n";
}
file_put_contents($authz_file, $fcontent, LOCK_EX);
return true;
}
/**
* Update the timeline in post commit.
*
*/
public function processSvnUpdateTimeline($params)
{
$pname = basename($params['repo_dir']);
try {
$project = IDF_Project::getOr404($pname);
} catch (Pluf_HTTP_Error404 $e) {
Pluf_Log::event(array('IDF_Plugin_SyncSvn::processSvnUpdateTimeline', 'Project not found.', array($pname, $params)));
return false; // Project not found
}
// Now we have the project and can update the timeline
Pluf_Log::debug(array('IDF_Plugin_SyncGit::processSvnUpdateTimeline', 'Project found', $pname, $project->id));
IDF_Scm::syncTimeline($project, true);
Pluf_Log::event(array('IDF_Plugin_SyncGit::processSvnUpdateTimeline', 'sync', array($pname, $project->id)));
}
}

View File

@@ -23,6 +23,26 @@
class IDF_Precondition
{
/**
* Check if the user has a base authorization to access a given
* tab. This used in the case of private project. You need to
* further control with the accessSource, accessIssues,
* etc. preconditions.
*
* @param Pluf_HTTP_Request
* @return mixed
*/
static public function baseAccess($request)
{
if (!$request->project->private) {
return true;
}
if ($request->user->hasPerm('IDF.project-authorized-user', $request->project)) {
return true;
}
return self::projectMemberOrOwner($request);
}
/**
* Check if the user is project owner.
*
@@ -40,4 +60,200 @@ 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)
{
$res = self::baseAccess($request);
if (true !== $res) {
return $res;
}
return self::accessTabGeneric($request, 'source_access_rights');
}
static public function accessIssues($request)
{
$res = self::baseAccess($request);
if (true !== $res) {
return $res;
}
return self::accessTabGeneric($request, 'issues_access_rights');
}
static public function accessDownloads($request)
{
$res = self::baseAccess($request);
if (true !== $res) {
return $res;
}
return self::accessTabGeneric($request, 'downloads_access_rights');
}
static public function accessWiki($request)
{
$res = self::baseAccess($request);
if (true !== $res) {
return $res;
}
return self::accessTabGeneric($request, 'wiki_access_rights');
}
static public function accessReview($request)
{
$res = self::baseAccess($request);
if (true !== $res) {
return $res;
}
return self::accessTabGeneric($request, 'review_access_rights');
}
/**
* Based on the request, it is automatically setting the user.
*
* API calls are not translated.
*/
static public function apiSetUser($request)
{
// REQUEST is used to be used both for POST and GET requests.
if (!isset($request->REQUEST['_hash'])
or !isset($request->REQUEST['_login'])
or !isset($request->REQUEST['_salt'])) {
// equivalent to anonymous access.
return true;
}
$db =& Pluf::db();
$true = Pluf_DB_BooleanToDb(true, $db);
$sql = new Pluf_SQL('login=%s AND active='.$true,
$request->REQUEST['_login']);
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
if ($users->count() != 1 or !$users[0]->active) {
// Should return a special authentication error like user
// not found.
return true;
}
$hash = sha1($request->REQUEST['_salt'].sha1($users[0]->password));
if ($hash != $request->REQUEST['_hash']) {
return true; // Again need authentication error
}
$request->user = $users[0];
IDF_Middleware::setRights($request);
return true;
}
/**
* Based on the request, it is automatically setting the user.
*
* Authenticated feeds have a token set at the end of the url in
* the for of 'authenticated/url/token/234092384023woeiur/'. If
* you remove 'token/234092384023woeiur/' the url is not
* authenticated.
*
* If the user is already logged in and not anonymous and no token
* is given, then the user is unset and a non authenticated user
* is loaded. This is to avoid people to not understand why a
* normally not authenticated feed is providing authenticated
* data.
*/
static public function feedSetUser($request)
{
if (!isset($request->project)) {
return true; // we do not act on non project pages at the
// moment.
}
if (!$request->user->isAnonymous()) {
// by default anonymous
$request->user = new Pluf_User();
IDF_Middleware::setRights($request);
}
$match = array();
if (!preg_match('#/token/([^/]+)/$#', $request->query, $match)) {
return true; // anonymous
}
$token = $match[1];
$hash = substr($token, 0, 2);
$encrypted = substr($token, 2);
if ($hash != substr(md5(Pluf::f('secret_key').$encrypted), 0, 2)) {
return true; // no match in the hash, anonymous
}
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
list($userid, $projectid) = explode(':', $cr->decrypt($encrypted), 2);
if ($projectid != $request->project->id) {
return true; // anonymous
}
$user = new Pluf_User($userid);
if (!$user->active) {
return true; // anonymous
}
$request->user = $user;
IDF_Middleware::setRights($request);
return true;
}
/**
* Generate the token for the feed.
*
* @param IDF_Project
* @param Pluf_User
* @return string Token
*/
static public function genFeedToken($project, $user)
{
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
$encrypted = trim($cr->encrypt($user->id.':'.$project->id), '~');
return substr(md5(Pluf::f('secret_key').$encrypted), 0, 2).$encrypted;
}
}

View File

@@ -31,9 +31,19 @@ class IDF_Project extends Pluf_Model
{
public $_model = __CLASS__;
public $_extra_cache = array();
protected $_pconf = null;
/**
* Check if the project as one restricted tab.
*
* This is the cached information.
*
* @see self::isRestricted
*/
protected $_isRestricted = null;
function init()
{
$this->_pconf = null;
$this->_extra_cache = array();
$this->_a['table'] = 'idf_projects';
$this->_a['model'] = __CLASS__;
@@ -60,6 +70,14 @@ class IDF_Project extends Pluf_Model
'help_text' => __('Used in the url to access the project, must be short with only letters and numbers.'),
'unique' => true,
),
'shortdesc' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 255,
'verbose' => __('short description'),
'help_text' => __('A one line description of the project.'),
),
'description' =>
array(
'type' => 'Pluf_DB_Field_Text',
@@ -68,8 +86,14 @@ class IDF_Project extends Pluf_Model
'verbose' => __('description'),
'help_text' => __('The description can be extended using the markdown syntax.'),
),
'private' =>
array(
'type' => 'Pluf_DB_Field_Integer',
'blank' => false,
'verbose' => __('private'),
'default' => 0,
),
);
$this->_a['idx'] = array( );
}
@@ -89,8 +113,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,13 +215,12 @@ 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);
$_s = explode('=', $s, 2);
$v = trim($_s[0]);
$_v = split(':', $v, 2);
$_v = explode(':', $v, 2);
if (count($_v) > 1) {
$class = trim($_v[0]);
$name = trim($_v[1]);
@@ -213,7 +236,7 @@ class IDF_Project extends Pluf_Model
/**
* Return membership data.
*
* The array has 2 keys: 'members' and 'owners'.
* The array has 3 keys: 'members', 'owners' and 'authorized'.
*
* The list of users is only taken using the row level permission
* table. That is, if you set a user as administrator, he will
@@ -227,12 +250,13 @@ class IDF_Project extends Pluf_Model
{
$mperm = Pluf_Permission::getFromString('IDF.project-member');
$operm = Pluf_Permission::getFromString('IDF.project-owner');
$aperm = Pluf_Permission::getFromString('IDF.project-authorized-user');
$grow = new Pluf_RowPermission();
$db =& Pluf::db();
$false = Pluf_DB_BooleanToDb(false, $db);
$sql = new Pluf_SQL('model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative='.$false,
array('IDF_Project', $this->id, 'Pluf_User', $operm->id));
$owners = array();
$owners = new Pluf_Template_ContextVars(array());
foreach ($grow->getList(array('filter' => $sql->gen())) as $row) {
if ($fmt == 'objects') {
$owners[] = Pluf::factory('Pluf_User', $row->owner_id);
@@ -242,7 +266,7 @@ class IDF_Project extends Pluf_Model
}
$sql = new Pluf_SQL('model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative='.$false,
array('IDF_Project', $this->id, 'Pluf_User', $mperm->id));
$members = array();
$members = new Pluf_Template_ContextVars(array());
foreach ($grow->getList(array('filter' => $sql->gen())) as $row) {
if ($fmt == 'objects') {
$members[] = Pluf::factory('Pluf_User', $row->owner_id);
@@ -250,41 +274,388 @@ class IDF_Project extends Pluf_Model
$members[] = Pluf::factory('Pluf_User', $row->owner_id)->login;
}
}
$authorized = new Pluf_Template_ContextVars(array());
if ($aperm != false) {
$sql = new Pluf_SQL('model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative='.$false,
array('IDF_Project', $this->id, 'Pluf_User', $aperm->id));
foreach ($grow->getList(array('filter' => $sql->gen())) as $row) {
if ($fmt == 'objects') {
$authorized[] = Pluf::factory('Pluf_User', $row->owner_id);
} else {
$authorized[] = Pluf::factory('Pluf_User', $row->owner_id)->login;
}
}
}
if ($fmt == 'objects') {
return array('members' => $members, 'owners' => $owners);
return new Pluf_Template_ContextVars(array('members' => $members, 'owners' => $owners, 'authorized' => $authorized));
} else {
return array('members' => implode("\n", $members),
'owners' => implode("\n", $owners));
return array('members' => implode("\n", (array) $members),
'owners' => implode("\n", (array) $owners),
'authorized' => implode("\n", (array) $authorized),
);
}
}
/**
* Generate the tag clouds.
*
* 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.
* Return an array of tags sorted by class, then name. Each tag
* get the extra property 'nb_use' for the number of use in the
* project.
*
* @param string ('issues') 'closed_issues', 'wiki' 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));
} elseif ($what == 'wiki') {
$dep_ids = IDF_Views_Wiki::getDeprecatedPagesIds($this);
$extra = '';
if (count($dep_ids)) {
$extra = ' AND idf_wikipage_id NOT IN ('.implode(', ', $dep_ids).') ';
}
$what_t = Pluf::factory('IDF_WikiPage')->getSqlTable();
$asso_t = $this->_con->pfx.'idf_tag_idf_wikipage_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_wikipage_id='.$what_t.'.id '."\n".
'WHERE idf_tag_id IS NOT NULL '.$extra.' 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';
} elseif ($what == 'downloads') {
$dep_ids = IDF_Views_Download::getDeprecatedFilesIds($this);
$extra = '';
if (count($dep_ids)) {
$extra = ' AND idf_upload_id NOT IN ('.implode(', ', $dep_ids).') ';
}
$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 '.$extra.' 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']);
$tag->nb_use = $idc['nb_use'];
$tags[] = $tag;
}
return $tags;
return new Pluf_Template_ContextVars($tags);
}
}
/**
* Get the repository size.
*
* @param bool Force to skip the cache (false)
* @return int Size in byte or -1 if not available
*/
public function getRepositorySize($force=false)
{
$last_eval = $this->getConf()->getVal('repository_size_check_date', 0);
if (!$force and $last_eval > time()-86400) {
return $this->getConf()->getVal('repository_size', -1);
}
$scm = IDF_Scm::get($this);
$this->getConf()->setVal('repository_size', $scm->getRepositorySize());
$this->getConf()->setVal('repository_size_check_date', time());
return $this->getConf()->getVal('repository_size', -1);
}
/**
* Get the access url to the repository.
*
* This will return the right url based on the user.
*
* @param Pluf_User The user (null)
*/
public function getSourceAccessUrl($user=null)
{
$right = $this->getConf()->getVal('source_access_rights', 'all');
if (($user == null or $user->isAnonymous())
and $right == 'all' and !$this->private) {
return $this->getRemoteAccessUrl();
}
return $this->getWriteRemoteAccessUrl($user);
}
/**
* Get the remote access url to the repository.
*
* This will always return the anonymous access url.
*/
public function getRemoteAccessUrl()
{
$conf = $this->getConf();
$scm = $conf->getVal('scm', 'git');
$scms = Pluf::f('allowed_scm');
Pluf::loadClass($scms[$scm]);
return call_user_func(array($scms[$scm], 'getAnonymousAccessUrl'),
$this);
}
/**
* Get the remote write access url to the repository.
*
* Some SCM have a remote access URL to write which is not the
* same as the one to read. For example, you do a checkout with
* git-daemon and push with SSH.
*/
public function getWriteRemoteAccessUrl($user)
{
$conf = $this->getConf();
$scm = $conf->getVal('scm', 'git');
$scms = Pluf::f('allowed_scm');
return call_user_func(array($scms[$scm], 'getAuthAccessUrl'),
$this, $user);
}
/**
* Get the root name of the project scm
*
* @return string SCM root
*/
public function getScmRoot()
{
$conf = $this->getConf();
$roots = array(
'git' => 'master',
'svn' => 'HEAD',
'mercurial' => 'tip'
);
$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;
}
/**
* Get simple statistics about the project.
*
* This returns an associative array with number of tickets,
* number of downloads, etc.
*
* @return array Stats
*/
public function getStats()
{
$stats = array();
$stats['total'] = 0;
$what = array('downloads' => 'IDF_Upload',
'reviews' => 'IDF_Review',
'issues' => 'IDF_Issue',
'docpages' => 'IDF_WikiPage',
'commits' => 'IDF_Commit',
);
foreach ($what as $key=>$m) {
$i = Pluf::factory($m)->getCount(array('filter' => 'project='.(int)$this->id));
$stats[$key] = $i;
$stats['total'] += $i;
}
/**
* [signal]
*
* IDF_Project::getStats
*
* [sender]
*
* IDF_Project
*
* [description]
*
* This signal allows an application to update the statistics
* array of a project. For example to add the on disk size
* of the repository if available.
*
* [parameters]
*
* array('project' => $project,
* 'stats' => $stats)
*
*/
$params = array('project' => $this,
'stats' => $stats);
Pluf_Signal::send('IDF_Project::getStats',
'IDF_Project', $params);
return $stats;
}
/**
* Needs to be called when you update the memberships of a
* project.
*
* This will allow a plugin to, for example, update some access
* rights to a repository.
*/
public function membershipsUpdated()
{
/**
* [signal]
*
* IDF_Project::membershipsUpdated
*
* [sender]
*
* IDF_Project
*
* [description]
*
* This signal allows an application to update the some access
* rights to a repository when the project memberships is
* updated.
*
* [parameters]
*
* array('project' => $project)
*
*/
$params = array('project' => $this);
Pluf_Signal::send('IDF_Project::membershipsUpdated',
'IDF_Project', $params);
}
/**
* Needs to be called when you create a project.
*
* We cannot put it into the postSave call as the configuration of
* the project is not defined at that time.
*/
function created()
{
/**
* [signal]
*
* IDF_Project::created
*
* [sender]
*
* IDF_Project
*
* [description]
*
* This signal allows an application to perform special
* operations at the creation of a project.
*
* [parameters]
*
* array('project' => $project)
*
*/
$params = array('project' => $this);
Pluf_Signal::send('IDF_Project::created',
'IDF_Project', $params);
}
/**
* The delete() call do not like circular references and the
* IDF_Tag is creating some. We predelete to solve these issues.
*/
public function preDelete()
{
/**
* [signal]
*
* IDF_Project::preDelete
*
* [sender]
*
* IDF_Project
*
* [description]
*
* This signal allows an application to perform special
* operations at the deletion of a project.
*
* [parameters]
*
* array('project' => $project)
*
*/
$params = array('project' => $this);
Pluf_Signal::send('IDF_Project::preDelete',
'IDF_Project', $params);
$what = array('IDF_Upload', 'IDF_Review', 'IDF_Issue',
'IDF_WikiPage', 'IDF_Commit',
);
foreach ($what as $m) {
foreach (Pluf::factory($m)->getList(array('filter' => 'project='.(int)$this->id)) as $item) {
$item->delete();
}
}
}
/**
* Check if the project has one restricted tab.
*
* @return bool
*/
public function isRestricted()
{
if ($this->_isRestricted !== null) {
return $this->_isRestricted;
}
if ($this->private) {
$this->_isRestricted = true;
return true;
}
$tabs = array(
'source_access_rights',
'issues_access_rights',
'downloads_access_rights',
'wiki_access_rights',
'review_access_rights'
);
$conf = $this->getConf();
foreach ($tabs as $tab) {
if (!in_array($conf->getVal($tab, 'all'),
array('all', 'none'))) {
$this->_isRestricted = true;
return true;
}
}
$this->_isRestricted = false;
return false;
}
}

198
src/IDF/Review.php Normal file
View File

@@ -0,0 +1,198 @@
<?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_Template_dateAgo');
/**
* Base definition of a code review.
*
* A code review has a status, submitter, summary, description and is
* associated to a project.
*
* The real content of the review is in the IDF_Review_Patch which
* contains a given patch and associated comments from reviewers.
*
* Basically the hierarchy of the models is:
* - Review > Patch > Comment > Comment on file
*
* For each review, one can have several patches. Each patch, is
* getting a series of comments. A comment is tracking the state
* change in the review (like the issue comments). For each comment,
* we have a series of file comments. The file comments are associated
* to the a given modified file in the patch.
*/
class IDF_Review extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_reviews';
$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' => 'reviews',
),
'summary' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 250,
'verbose' => __('summary'),
),
'submitter' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User',
'blank' => false,
'verbose' => __('submitter'),
'relate_name' => 'submitted_review',
),
'interested' =>
array(
'type' => 'Pluf_DB_Field_Manytomany',
'model' => 'Pluf_User',
'blank' => true,
'help_text' => 'Interested users will get an email notification when the review is changed.',
),
'tags' =>
array(
'type' => 'Pluf_DB_Field_Manytomany',
'blank' => true,
'model' => 'IDF_Tag',
'verbose' => __('labels'),
),
'status' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'blank' => false,
'model' => 'IDF_Tag',
'verbose' => __('status'),
),
'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_review_idf_tag_assoc';
$this->_a['views'] = array(
'join_tags' =>
array(
'join' => 'LEFT JOIN '.$table
.' ON idf_review_id=id',
),
);
}
/**
* Iterate through the patches and comments to get the reviewers.
*/
function getReviewers()
{
$rev = new ArrayObject();
foreach ($this->get_patches_list() as $p) {
foreach ($p->get_comments_list() as $c) {
$rev[] = $c->get_submitter();
}
}
return Pluf_Model_RemoveDuplicates($rev);
}
function __toString()
{
return $this->id.' - '.$this->summary;
}
function _toIndex()
{
return '';
}
function preDelete()
{
IDF_Timeline::remove($this);
IDF_Search::remove($this);
}
function preSave($create=false)
{
if ($create) {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
}
$this->modif_dtime = gmdate('Y-m-d H:i:s');
}
function postSave($create=false)
{
// At creation, we index after saving the associated patch.
if (!$create) IDF_Search::index($this);
}
/**
* Returns an HTML fragment used to display this review in the
* timeline.
*
* The request object is given to be able to check the rights and
* as such create links to other items etc. You can consider that
* if displayed, you can create a link to it.
*
* @param Pluf_HTTP_Request
* @return Pluf_Template_SafeString
*/
public function timelineFragment($request)
{
return '';
}
public function feedFragment($request)
{
return '';
}
}

224
src/IDF/Review/Comment.php Normal file
View File

@@ -0,0 +1,224 @@
<?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 ***** */
/**
* A comment set on a review.
*
* A comment is associated to a patch as a review can have many
* patches associated to it.
*
* A comment is also tracking the changes in the review in the same
* way the issue comment is tracking the changes in the issue.
*
*
*/
class IDF_Review_Comment extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_review_comments';
$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,
),
'patch' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Review_Patch',
'blank' => false,
'verbose' => __('patch'),
'relate_name' => 'comments',
),
'content' =>
array(
'type' => 'Pluf_DB_Field_Text',
'blank' => true, // if only commented on lines
'verbose' => __('comment'),
),
'submitter' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User',
'blank' => false,
'verbose' => __('submitter'),
),
'changes' =>
array(
'type' => 'Pluf_DB_Field_Serialized',
'blank' => true,
'verbose' => __('changes'),
'help_text' => 'Serialized array of the changes in the review.',
),
'vote' =>
array(
'type' => 'Pluf_DB_Field_Integer',
'default' => 0,
'blank' => true,
'verbose' => __('vote'),
'help_text' => '1, 0 or -1 for positive, neutral or negative vote.',
),
'creation_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('creation date'),
'index' => true,
),
);
}
function changedReview()
{
return (is_array($this->changes) and count($this->changes) > 0);
}
function _toIndex()
{
return $this->content;
}
function preDelete()
{
IDF_Timeline::remove($this);
}
function preSave($create=false)
{
if ($create) {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
}
}
function postSave($create=false)
{
if ($create) {
IDF_Timeline::insert($this,
$this->get_patch()->get_review()->get_project(),
$this->get_submitter());
}
}
public function timelineFragment($request)
{
$review = $this->get_patch()->get_review();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
array($request->project->shortname,
$review->id));
$out = '<tr class="log"><td><a href="'.$url.'">'.
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
'</a></td><td>';
$stag = new IDF_Template_ShowUser();
$user = $stag->start($this->get_submitter(), $request, '', false);
$ic = (in_array($review->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
$out .= sprintf(__('<a href="%1$s" class="%2$s" title="View review">Review %3$d</a>, %4$s'), $url, $ic, $review->id, Pluf_esc($review->summary)).'</td>';
$out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Update of <a href="%s" class="%s">review&nbsp;%d</a>, by %s'), $url, $ic, $review->id, $user).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
public function feedFragment($request)
{
$review = $this->get_patch()->get_review();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
array($request->project->shortname,
$review->id));
$title = sprintf(__('%s: Updated review %d - %s'),
Pluf_esc($request->project->name),
$review->id, Pluf_esc($review->summary));
$url .= '#ic'.$this->id;
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
$context = new Pluf_Template_Context_Request(
$request,
array('url' => $url,
'author' => $this->get_submitter(),
'title' => $title,
'c' => $this,
'review' => $review,
'date' => $date)
);
$tmpl = new Pluf_Template('idf/review/feedfragment.xml');
return $tmpl->render($context);
}
/**
* Notify of the update of the review.
*
*
* @param IDF_Conf Current configuration
* @param bool Creation (true)
*/
public function notify($conf, $create=true)
{
$patch = $this->get_patch();
$review = $patch->get_review();
$prj = $review->get_project();
$to_email = array();
if ('' != $conf->getVal('review_notification_email', '')) {
$langs = Pluf::f('languages', array('en'));
$to_email[] = array($conf->getVal('issues_notification_email'),
$langs[0]);
}
$current_locale = Pluf_Translation::getLocale();
$reviewers = $review->getReviewers();
if (!Pluf_Model_InArray($review->get_submitter(), $reviewers)) {
$reviewers[] = $review->get_submitter();
}
$comments = $patch->getFileComments(array('order' => 'id DESC'));
$gcomments = $patch->get_comments_list(array('order' => 'id DESC'));
$context = new Pluf_Template_Context(
array(
'review' => $review,
'patch' => $patch,
'comments' => $comments,
'gcomments' => $gcomments,
'project' => $prj,
'url_base' => Pluf::f('url_base'),
)
);
// build the list of emails and lang
foreach ($reviewers as $user) {
$email_lang = array($user->email,
$user->language);
if (!in_array($email_lang, $to_email)) {
$to_email[] = $email_lang;
}
}
$tmpl = new Pluf_Template('idf/review/review-updated-email.txt');
foreach ($to_email as $email_lang) {
Pluf_Translation::loadSetLocale($email_lang[1]);
$email = new Pluf_Mail(Pluf::f('from_email'), $email_lang[0],
sprintf(__('Updated Code Review %s - %s (%s)'),
$review->id, $review->summary, $prj->shortname));
$email->addTextMessage($tmpl->render($context));
$email->sendMail();
}
Pluf_Translation::loadSetLocale($current_locale);
}
}

View File

@@ -0,0 +1,110 @@
<?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 ***** */
/**
* A comment to a file affected by a patch.
*
*/
class IDF_Review_FileComment extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_review_filecomments';
$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,
),
'comment' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Review_Comment',
'blank' => false,
'relate_name' => 'filecomments',
'verbose' => __('comment'),
),
'cfile' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 250,
'help_text' => 'The changed file, for example src/foo/bar.txt, this is the path to access it in the repository.',
),
'cline' =>
array(
'type' => 'Pluf_DB_Field_Integer',
'blank' => false,
'default' => 0,
'help_text' => 'The commented line, negative value is the old file, positive the new, 0 general comment.',
),
'content' =>
array(
'type' => 'Pluf_DB_Field_Text',
'blank' => false,
'verbose' => __('comment'),
),
'creation_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('creation date'),
'index' => true,
),
);
}
function _toIndex()
{
return $this->cfile.' '.$this->content;
}
function preDelete()
{
}
function preSave($create=false)
{
if ($create) {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
}
}
function postSave($create=false)
{
}
public function timelineFragment($request)
{
return '';
}
public function feedFragment($request)
{
return '';
}
}

210
src/IDF/Review/Patch.php Normal file
View File

@@ -0,0 +1,210 @@
<?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 ***** */
/**
* A patch to be reviewed.
*
* A patch can be marked as being directly the commit, in that case
* the patch does not store the diff file as it can be retrieved from
* the backend.
*
*/
class IDF_Review_Patch extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_review_patches';
$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,
),
'review' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Review',
'blank' => false,
'verbose' => __('review'),
'relate_name' => 'patches',
),
'summary' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 250,
'verbose' => __('summary'),
),
'commit' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Commit',
'blank' => false,
'verbose' => __('commit'),
'relate_name' => 'patches',
),
'description' =>
array(
'type' => 'Pluf_DB_Field_Text',
'blank' => false,
'verbose' => __('description'),
),
'patch' =>
array(
'type' => 'Pluf_DB_Field_File',
'blank' => false,
'verbose' => __('patch'),
'help_text' => 'The patch is stored at the same place as the issue attachments with the same approach for the name.',
),
'creation_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('creation date'),
'index' => true,
),
);
}
/**
* Get the list of file comments.
*
* It will go through the patch comments and find for each the
* file comments.
*
* @param array Filter to apply to the file comment list (array())
*/
function getFileComments($filter=array())
{
$files = new ArrayObject();
foreach ($this->get_comments_list(array('order'=>'creation_dtime ASC')) as $ct) {
foreach ($ct->get_filecomments_list($filter) as $fc) {
$files[] = $fc;
}
}
return $files;
}
function _toIndex()
{
return '';
}
function preDelete()
{
IDF_Timeline::remove($this);
}
function preSave($create=false)
{
if ($create) {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
}
}
function postSave($create=false)
{
if ($create) {
IDF_Timeline::insert($this,
$this->get_review()->get_project(),
$this->get_review()->get_submitter());
IDF_Search::index($this->get_review());
}
}
public function timelineFragment($request)
{
$review = $this->get_review();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
array($request->project->shortname,
$review->id));
$out = '<tr class="log"><td><a href="'.$url.'">'.
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
'</a></td><td>';
$stag = new IDF_Template_ShowUser();
$user = $stag->start($review->get_submitter(), $request, '', false);
$ic = (in_array($review->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
$out .= sprintf(__('<a href="%1$s" class="%2$s" title="View review">Review %3$d</a>, %4$s'), $url, $ic, $review->id, Pluf_esc($review->summary)).'</td>';
$out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">review&nbsp;%d</a>, by %s'), $url, $ic, $review->id, $user).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
public function feedFragment($request)
{
$review = $this->get_review();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
array($request->project->shortname,
$review->id));
$title = sprintf(__('%s: Creation of Review %d - %s'),
Pluf_esc($request->project->name),
$review->id, Pluf_esc($review->summary));
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
$context = new Pluf_Template_Context_Request(
$request,
array('url' => $url,
'author' => $review->get_submitter(),
'title' => $title,
'p' => $this,
'review' => $review,
'date' => $date)
);
$tmpl = new Pluf_Template('idf/review/feedfragment.xml');
return $tmpl->render($context);
}
public function notify($conf, $create=true)
{
if ('' == $conf->getVal('review_notification_email', '')) {
return;
}
$current_locale = Pluf_Translation::getLocale();
$langs = Pluf::f('languages', array('en'));
Pluf_Translation::loadSetLocale($langs[0]);
$context = new Pluf_Template_Context(
array(
'review' => $this->get_review(),
'patch' => $this,
'comments' => array(),
'project' => $this->get_review()->get_project(),
'url_base' => Pluf::f('url_base'),
)
);
$tmpl = new Pluf_Template('idf/review/review-created-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail(Pluf::f('from_email'),
$conf->getVal('review_notification_email'),
sprintf(__('New Code Review %s - %s (%s)'),
$this->get_review()->id,
$this->get_review()->summary,
$this->get_review()->get_project()->shortname));
$email->addTextMessage($text_email);
$email->sendMail();
Pluf_Translation::loadSetLocale($current_locale);
}
}

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

@@ -0,0 +1,419 @@
<?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.
*
* This is the base class with the different required methods to be
* implemented by the SCMs. Each SCM backend need to extend this
* class. We are not using an interface because this is not really
* needed.
*
* The philosophy behind the interface is not to provide a wrapper
* around the different SCMs but to provide methods to retrieve in the
* most efficient way the informations to be displayed/needed in the
* web interface. This means that each SCM can use the best options,
* including caching to retrieve the informations.
*
* Note on caching: You must not cache ephemeral information like the
* changelog, but you can cache the commit info (except with
* subversion where you can change commit info...). It is ok to do
* some caching for the lifetime of the IDF_Scm object, for example
* not to retrieve several times the list of branches, etc.
*
* All the output of the methods must be serializable. This means that
* if you are parsing XML you need to correctly cast the results as
* string when needed.
*/
class IDF_Scm
{
/**
* String template for consistent error messages.
*/
public $error_tpl = 'Error command "%s" returns code %d and output: %s';
/**
* Path to the repository.
*/
public $repo = '';
/**
* Corresponding project object.
*/
public $project = null;
/**
* Cache storage.
*
* It must only be used to store data for the lifetime of the
* object. For example if you need to get the list of branches in
* several functions, better to try to get from the cache first.
*/
protected $cache = array();
/**
* Returns an instance of the correct scm backend object.
*
* @param IDF_Project
* @return Object
*/
public static function get($project)
{
// Get scm type from project conf ; defaults to git
// We will need to cache the factory
$scm = $project->getConf()->getVal('scm', 'git');
$scms = Pluf::f('allowed_scm');
return call_user_func(array($scms[$scm], 'factory'), $project);
}
/**
* Run exec and log some information.
*
* @param $caller Calling method
* @param $cmd Command to run
* @param &$out Array of output
* @param &$return Return value
* @return string Last line of the command
*/
public static function exec($caller, $cmd, &$out=null, &$return=null)
{
Pluf_Log::stime('timer');
$ret = exec($cmd, $out, $return);
Pluf_Log::perf(array($caller, $cmd, Pluf_Log::etime('timer', 'total_exec')));
Pluf_Log::debug(array($caller, $cmd, $out));
Pluf_Log::inc('exec_calls');
return $ret;
}
/**
* Run shell_exec and log some information.
*
* @param $caller Calling method
* @param $cmd Command to run
* @return string The output
*/
public static function shell_exec($caller, $cmd)
{
Pluf_Log::stime('timer');
$ret = shell_exec($cmd);
Pluf_Log::perf(array($caller, $cmd, Pluf_Log::etime('timer', 'total_exec')));
Pluf_Log::debug(array($caller, $cmd, $ret));
Pluf_Log::inc('exec_calls');
return $ret;
}
/**
* Return the size of the repository in bytes.
*
* @return int Size in byte, -1 if the size cannot be evaluated.
*/
public function getRepositorySize()
{
return -1;
}
/**
* Returns the URL of the git daemon.
*
* @param IDF_Project
* @return string URL
*/
public static function getAnonymousAccessUrl($project)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Returns the URL for SSH access
*
* @param IDF_Project
* @param Pluf_User
* @return string URL
*/
public static function getAuthAccessUrl($project, $user)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Check if the backend is available for display.
*
* @return bool Available
*/
public function isAvailable()
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Check if a revision or commit is valid.
*
* @param string Revision or commit
* @return bool
*/
public function isValidRevision($rev)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Returns in which branches a commit/path is.
*
* A commit can be in several branches and some of the SCMs are
* managing branches using subfolders (like Subversion).
*
* This means that to know in which branch we are at the moment,
* one needs to have both the path and the commit.
*
* @param string Commit
* @param string Path
* @return array Branches
*/
public function inBranches($commit, $path)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Returns the list of branches.
*
* The return value must be a branch indexed array with the
* optional path to access the branch as value. For example with
* git you would get (note that some people are using / in the
* name of their git branches):
*
* <pre>
* array('master' => '',
* 'foo-branch' => '',
* 'design/feature1' => '')
* </pre>
*
* But with Subversion, as the branches are managed as subfolder
* with a special folder for trunk, you would get something like:
*
* <pre>
* array('trunk' => 'trunk',
* 'foo-branch' => 'branches/foo-branch',)
* </pre>
*
* @return array Branches
*/
public function getBranches()
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Returns the list of tags.
*
* The format is the same as for the branches.
*
* @see self::getBranches()
*
* @return array Tags
*/
public function getTags()
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Returns in which tags a commit/path is.
*
* A commit can be in several tags and some of the SCMs are
* managing tags using subfolders (like Subversion).
*
* This means that to know in which tag we are at the moment,
* one needs to have both the path and the commit.
*
* @param string Commit
* @param string Path
* @return array Tags
*/
public function inTags($commit, $path)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Returns the main branch.
*
* The main branch is the one displayed by default. For example
* master, trunk or tip.
*
* @return string
*/
public function getMainBranch()
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Returns the list of files in a given folder.
*
* The list is an array of standard class objects with attributes
* for each file/directory/external element.
*
* This is the most important method of the SCM backend as this is
* the one conveying the speed feeling of the application. All the
* dirty optimization tricks are allowed there.
*
* @param string Revision or commit
* @param string Folder ('/')
* @param string Branch (null)
* @return array
*/
public function getTree($rev, $folder='/', $branch=null)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Get commit details.
*
* @param string Commit or revision number
* @param bool Get commit diff (false)
* @return stdClass
*/
public function getCommit($commit, $getdiff=false)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Get latest changes.
*
* It default to the main branch. If possible you should code in a
* way to avoid repetitive calls to getCommit. Try to be
* efficient.
*
* @param string Branch (null)
* @param int Number of changes (25)
* @return array List of commits
*/
public function getChangeLog($branch=null, $n=10)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Given the string describing the author from the log find the
* author in the database.
*
* If the input is an array, it will return an array of results.
*
* @param mixed string/array Author
* @return mixed Pluf_User or null or array
*/
public function findAuthor($author)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Given a revision and a file path, retrieve the file content.
*
* The $cmd_only parameter is to only request the command that is
* used to get the file content. This is used when downloading a
* file at a given revision as it can be passed to a
* Pluf_HTTP_Response_CommandPassThru reponse. This allows to
* stream a large response without buffering it in memory.
*
* The file definition is coming from getPathInfo().
*
* @see self::getPathInfo()
*
* @param stdClass File definition
* @param bool Returns command only (false)
* @return string File content
*/
public function getFile($def, $cmd_only=false)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Get information about a file or a path.
*
* @param string File or path
* @param string Revision (null)
* @return mixed False or stdClass with info
*/
public function getPathInfo($file, $rev=null)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Given a revision and possible path returns additional properties.
*
* @param string Revision
* @param string Path ('')
* @return mixed null or array of properties
*/
public function getProperties($rev, $path='')
{
return null;
}
/**
* Generate the command to create a zip archive at a given commit.
*
* @param string Commit
* @param string Prefix ('repository/')
* @return string Command
*/
public function getArchiveCommand($commit, $prefix='repository/')
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Sync the changes in the repository with the timeline.
*
*/
public static function syncTimeline($project, $force=false)
{
$cache = Pluf_Cache::factory();
$key = 'IDF_Scm:'.$project->shortname.':lastsync';
if ($force or null === ($res=$cache->get($key))) {
$scm = IDF_Scm::get($project);
if ($scm->isAvailable()) {
foreach ($scm->getChangeLog($scm->getMainBranch(), 25) as $change) {
IDF_Commit::getOrAdd($change, $project);
}
$cache->set($key, true, (int)(Pluf::f('cache_timeout', 300)/2));
}
}
}
/**
* Given a path, encode everything but the /
*/
public static function smartEncode($path)
{
return str_replace('%2F', '/', rawurlencode($path));
}
}

134
src/IDF/Scm/Cache/Git.php 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 ***** */
/**
* This class implements the cache storage for the Git commits.
*
* The storage is simple. Each commit is linked to a project to drop
* the cache when the project is dropped. The key is the commit hash
* and the data is the date, author and one line title of information.
*
* A clean interface is available to bulk set/get a series of commit
* info with the minimum of SQL queries. The goal is to be fast.
*/
class IDF_Scm_Cache_Git extends Pluf_Model
{
public $_model = __CLASS__;
/**
* The current project to limit the search to.
*/
public $_project = null;
/**
* Store in the cache blob infos.
*
* The info is an array of stdClasses, with hash, date, title and
* author properties.
*
* @param array Blob infos
*/
public function store($infos)
{
foreach ($infos as $blob) {
$cache = new IDF_Scm_Cache_Git();
$cache->project = $this->_project;
$cache->githash = $blob->hash;
$blob->title = IDF_Commit::toUTF8($blob->title);
$cache->content = $blob->date.chr(31).$blob->author.chr(31).$blob->title;
$sql = new Pluf_SQL('project=%s AND githash=%s',
array($this->_project->id, $blob->hash));
if (0 == Pluf::factory(__CLASS__)->getCount(array('filter' => $sql->gen()))) {
$cache->create();
}
}
}
/**
* Get for the given hashes the corresponding date, title and
* author.
*
* It returns an hash indexed array with the info. If an hash is
* not in the db, the key is not set.
*
* Note that the hashes must always come from internal tools.
*
* @param array Hashes to get info
* @return array Blob infos
*/
public function retrieve($hashes)
{
$res = array();
$db = $this->getDbConnection();
$hashes = array_map(array($db, 'esc'), $hashes);
$sql = new Pluf_SQL('project=%s AND githash IN ('.implode(', ', $hashes).')',
array($this->_project->id));
foreach (Pluf::factory(__CLASS__)->getList(array('filter' => $sql->gen())) as $blob) {
$tmp = explode(chr(31), $blob->content, 3);
$res[$blob->githash] = (object) array(
'hash' => $blob->githash,
'date' => $tmp[0],
'title' => $tmp[2],
'author' => $tmp[1],
);
}
return $res;
}
/**
* The storage is composed of 4 columns, id, project, hash and the
* raw data.
*/
function init()
{
$this->_a['table'] = 'idf_scm_cache_git';
$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,
),
'githash' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 40,
'index' => true,
),
'content' =>
array(
'type' => 'Pluf_DB_Field_Text',
'blank' => false,
),
);
}
}

26
src/IDF/Scm/Exception.php Normal file
View File

@@ -0,0 +1,26 @@
<?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 ***** */
class IDF_Scm_Exception extends Exception
{
}

804
src/IDF/Scm/Git.php Normal file
View File

@@ -0,0 +1,804 @@
<?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 ***** */
/**
* Git utils.
*
*/
class IDF_Scm_Git extends IDF_Scm
{
public $mediumtree_fmt = 'commit %H%nAuthor: %an <%ae>%nTree: %T%nDate: %ai%n%n%s%n%n%b';
/* ============================================== *
* *
* Common Methods Implemented By All The SCMs *
* *
* ============================================== */
public function __construct($repo, $project=null)
{
$this->repo = $repo;
$this->project = $project;
}
public function getRepositorySize()
{
if (!file_exists($this->repo)) {
return 0;
}
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
.escapeshellarg($this->repo);
$out = explode(' ',
self::shell_exec('IDF_Scm_Git::getRepositorySize', $cmd),
2);
return (int) $out[0]*1024;
}
public function isAvailable()
{
try {
$branches = $this->getBranches();
} catch (IDF_Scm_Exception $e) {
return false;
}
return (count($branches) > 0);
}
public function getBranches()
{
if (isset($this->cache['branches'])) {
return $this->cache['branches'];
}
$cmd = Pluf::f('idf_exec_cmd_prefix', '')
.sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' branch',
escapeshellarg($this->repo));
self::exec('IDF_Scm_Git::getBranches',
$cmd, $out, $return);
if ($return != 0) {
throw new IDF_Scm_Exception(sprintf($this->error_tpl,
$cmd, $return,
implode("\n", $out)));
}
$res = array();
foreach ($out as $b) {
$b = substr($b, 2);
if (false !== strpos($b, '/')) {
$res[$this->getCommit($b)->commit] = $b;
} else {
$res[$b] = '';
}
}
$this->cache['branches'] = $res;
return $res;
}
public function getMainBranch()
{
$branches = $this->getBranches();
if (array_key_exists('master', $branches))
return 'master';
static $possible = array('main', 'trunk', 'local');
for ($i = 0; 3 > $i; ++$i) {
if (array_key_exists($possible[$i], $branches))
return $possible[$i];
}
return key($branches);
}
/**
* Note: Running the `git branch --contains $commit` is
* theoritically the best way to do it, until you figure out that
* you cannot cache the result and that it takes several seconds
* to execute on a big tree.
*/
public function inBranches($commit, $path)
{
return $this->_inObject($commit, 'branch');
}
/**
* @see IDF_Scm::getTags()
**/
public function getTags()
{
if (isset($this->cache['tags'])) {
return $this->cache['tags'];
}
$cmd = Pluf::f('idf_exec_cmd_prefix', '')
.sprintf('GIT_DIR=%s %s tag',
escapeshellarg($this->repo),
Pluf::f('git_path', 'git'));
self::exec('IDF_Scm_Git::getTags', $cmd, $out, $return);
if (0 != $return) {
throw new IDF_Scm_Exception(sprintf($this->error_tpl,
$cmd, $return,
implode("\n", $out)));
}
$res = array();
foreach ($out as $b) {
if (false !== strpos($b, '/')) {
$res[$this->getCommit($b)->commit] = $b;
} else {
$res[$b] = '';
}
}
$this->cache['tags'] = $res;
return $res;
}
/**
* @see IDF_Scm::inTags()
**/
public function inTags($commit, $path)
{
return $this->_inObject($commit, 'tag');
}
/**
* Returns in which branches or tags a commit is.
*
* @param string Commit
* @param string Object's type: 'branch' or 'tag'.
* @return array
*/
private function _inObject($commit, $object)
{
$object = strtolower($object);
if ('branch' === $object) {
$objects = $this->getBranches();
} else if ('tag' === $object) {
$objects = $this->getTags();
} else {
throw new InvalidArgumentException(sprintf(__('Invalid value for the parameter %1$s: %2$s. Use %3$s.'),
'$object',
$object,
'\'branch\' or \'tag\''));
}
unset($object);
$result = array();
if (array_key_exists($commit, $objects)) {
$result[] = $commit;
}
return $result;
}
/**
* Git "tree" is not the same as the tree we get here.
*
* With git each commit object stores a related tree object. This
* tree is basically providing what is in the given folder at the
* given commit. It looks something like that:
*
* <pre>
* 100644 blob bcd155e609c51b4651aab9838b270cce964670af AUTHORS
* 100644 blob 87b44c5c7df3cc90c031317c1ac8efcfd8a13631 COPYING
* 100644 blob 2a0f899cbfe33ea755c343b06a13d7de6c22799f INSTALL.mdtext
* 040000 tree 2f469c4c5318aa4ad48756874373370f6112f77b doc
* 040000 tree 911e0bd2706f0069b04744d6ef41353faf06a0a7 logo
* </pre>
*
* You can then follow what is in the given folder (let say doc)
* by using the hash.
*
* This means that you will have not to confuse the git tree and
* the output tree in the following method.
*
* @see http://www.kernel.org/pub/software/scm/git/docs/git-ls-tree.html
*
*/
public function getTree($commit, $folder='/', $branch=null)
{
$folder = ($folder == '/') ? '' : $folder;
// now we grab the info about this commit including its tree.
if (false == ($co = $this->getCommit($commit))) {
return false;
}
if ($folder) {
// As we are limiting to a given folder, we need to find
// the tree corresponding to this folder.
$tinfo = $this->getTreeInfo($commit, $folder);
if (isset($tinfo[0]) and $tinfo[0]->type == 'tree') {
$tree = $tinfo[0]->hash;
} else {
throw new Exception(sprintf(__('Folder %1$s not found in commit %2$s.'), $folder, $commit));
}
} else {
$tree = $co->tree;
}
$res = array();
foreach ($this->getTreeInfo($tree) as $file) {
// Now we grab the files in the current tree with as much
// information as possible.
if ($file->type == 'blob') {
$file->date = $co->date;
$file->log = '----';
$file->author = 'Unknown';
}
$file->fullpath = ($folder) ? $folder.'/'.$file->file : $file->file;
$file->efullpath = self::smartEncode($file->fullpath);
if ($file->type == 'commit') {
// We have a submodule
$file = $this->getSubmodule($file, $commit);
}
$res[] = $file;
}
// Grab the details for each blob and return the list.
return $this->getTreeDetails($res);
}
/**
* Given the string describing the author from the log find the
* author in the database.
*
* @param string Author
* @return mixed Pluf_User or null
*/
public function findAuthor($author)
{
// We extract the email.
$match = array();
if (!preg_match('/<(.*)>/', $author, $match)) {
return null;
}
foreach (array('email', 'login') as $what) {
$sql = new Pluf_SQL($what.'=%s', array($match[1]));
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
if ($users->count() > 0) {
return $users[0];
}
}
return null;
}
public static function getAnonymousAccessUrl($project)
{
return sprintf(Pluf::f('git_remote_url'), $project->shortname);
}
public static function getAuthAccessUrl($project, $user)
{
return sprintf(Pluf::f('git_write_remote_url'), $project->shortname);
}
/**
* Returns this object correctly initialized for the project.
*
* @param IDF_Project
* @return IDF_Scm_Git
*/
public static function factory($project)
{
$rep = sprintf(Pluf::f('git_repositories'), $project->shortname);
return new IDF_Scm_Git($rep, $project);
}
public function isValidRevision($commit)
{
$type = $this->testHash($commit);
return ('commit' == $type || 'tag' == $type);
}
/**
* Test a given object hash.
*
* @param string Object hash.
* @return mixed false if not valid or 'blob', 'tree', 'commit', 'tag'
*/
public function testHash($hash)
{
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' cat-file -t %s',
escapeshellarg($this->repo),
escapeshellarg($hash));
$ret = 0; $out = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Git::testHash', $cmd, $out, $ret);
if ($ret != 0) return false;
return trim($out[0]);
}
/**
* Get the tree info.
*
* @param string Tree hash
* @param bool Do we recurse in subtrees (true)
* @param string Folder in which we want to get the info ('')
* @return array Array of file information.
*/
public function getTreeInfo($tree, $folder='')
{
if (!in_array($this->testHash($tree), array('tree', 'commit', 'tag'))) {
throw new Exception(sprintf(__('Not a valid tree: %s.'), $tree));
}
$cmd_tmpl = 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' ls-tree -l %s %s';
$cmd = Pluf::f('idf_exec_cmd_prefix', '')
.sprintf($cmd_tmpl, escapeshellarg($this->repo),
escapeshellarg($tree), escapeshellarg($folder));
$out = array();
$res = array();
self::exec('IDF_Scm_Git::getTreeInfo', $cmd, $out);
foreach ($out as $line) {
list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY);
$res[] = (object) array('perm' => $perm, 'type' => $type,
'size' => $size, 'hash' => $hash,
'file' => $file);
}
return $res;
}
/**
* Get the file info.
*
* @param string File
* @param string Commit ('HEAD')
* @return false Information
*/
public function getPathInfo($totest, $commit='HEAD')
{
$cmd_tmpl = 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' ls-tree -r -t -l %s';
$cmd = sprintf($cmd_tmpl,
escapeshellarg($this->repo),
escapeshellarg($commit));
$out = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Git::getPathInfo', $cmd, $out);
foreach ($out as $line) {
list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY);
if ($totest == $file) {
$pathinfo = pathinfo($file);
return (object) array('perm' => $perm, 'type' => $type,
'size' => $size, 'hash' => $hash,
'fullpath' => $file,
'file' => $pathinfo['basename']);
}
}
return false;
}
public function getFile($def, $cmd_only=false)
{
$cmd = sprintf(Pluf::f('idf_exec_cmd_prefix', '').
'GIT_DIR=%s '.Pluf::f('git_path', 'git').' cat-file blob %s',
escapeshellarg($this->repo),
escapeshellarg($def->hash));
return ($cmd_only)
? $cmd : self::shell_exec('IDF_Scm_Git::getFile', $cmd);
}
/**
* Get commit details.
*
* @param string Commit
* @param bool Get commit diff (false)
* @return array Changes
*/
public function getCommit($commit, $getdiff=false)
{
if ($getdiff) {
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' show --date=iso --pretty=format:%s %s',
escapeshellarg($this->repo),
"'".$this->mediumtree_fmt."'",
escapeshellarg($commit));
} else {
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log -1 --date=iso --pretty=format:%s %s',
escapeshellarg($this->repo),
"'".$this->mediumtree_fmt."'",
escapeshellarg($commit));
}
$out = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Git::getCommit', $cmd, $out, $ret);
if ($ret != 0 or count($out) == 0) {
return false;
}
if ($getdiff) {
$log = array();
$change = array();
$inchange = false;
foreach ($out as $line) {
if (!$inchange and 0 === strpos($line, 'diff --git a')) {
$inchange = true;
}
if ($inchange) {
$change[] = $line;
} else {
$log[] = $line;
}
}
$out = self::parseLog($log);
$out[0]->changes = implode("\n", $change);
} else {
$out = self::parseLog($out);
$out[0]->changes = '';
}
return $out[0];
}
/**
* Check if a commit is big.
*
* @param string Commit ('HEAD')
* @return bool The commit is big
*/
public function isCommitLarge($commit='HEAD')
{
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log --numstat -1 --pretty=format:%s %s',
escapeshellarg($this->repo),
"'commit %H%n'",
escapeshellarg($commit));
$out = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Git::isCommitLarge', $cmd, $out);
$affected = count($out) - 2;
$added = 0;
$removed = 0;
$c=0;
foreach ($out as $line) {
$c++;
if ($c < 3) {
continue;
}
list($a, $r, $f) = preg_split("/[\s]+/", $line, 3, PREG_SPLIT_NO_EMPTY);
$added+=$a;
$removed+=$r;
}
return ($affected > 100 or ($added + $removed) > 20000);
}
/**
* Get latest changes.
*
* @param string Commit ('HEAD').
* @param int Number of changes (10).
* @return array Changes.
*/
public function getChangeLog($commit='HEAD', $n=10)
{
if ($n === null) $n = '';
else $n = ' -'.$n;
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log%s --date=iso --pretty=format:\'%s\' %s',
escapeshellarg($this->repo), $n, $this->mediumtree_fmt,
escapeshellarg($commit));
$out = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Git::getChangeLog', $cmd, $out);
return self::parseLog($out);
}
/**
* Parse the log lines of a --pretty=medium log output.
*
* @param array Lines.
* @return array Change log.
*/
public static function parseLog($lines)
{
$res = array();
$c = array();
$inheads = true;
$next_is_title = false;
foreach ($lines as $line) {
if (preg_match('/^commit (\w{40})$/', $line)) {
if (count($c) > 0) {
$c['full_message'] = trim($c['full_message']);
$c['full_message'] = IDF_Commit::toUTF8($c['full_message']);
$c['title'] = IDF_Commit::toUTF8($c['title']);
$res[] = (object) $c;
}
$c = array();
$c['commit'] = trim(substr($line, 7, 40));
$c['full_message'] = '';
$inheads = true;
$next_is_title = false;
continue;
}
if ($next_is_title) {
$c['title'] = trim($line);
$next_is_title = false;
continue;
}
$match = array();
if ($inheads and preg_match('/(\S+)\s*:\s*(.*)/', $line, $match)) {
$match[1] = strtolower($match[1]);
$c[$match[1]] = trim($match[2]);
if ($match[1] == 'date') {
$c['date'] = gmdate('Y-m-d H:i:s', strtotime($match[2]));
}
continue;
}
if ($inheads and !$next_is_title and $line == '') {
$next_is_title = true;
$inheads = false;
}
if (!$inheads) {
$c['full_message'] .= trim($line)."\n";
continue;
}
}
$c['full_message'] = !empty($c['full_message']) ? trim($c['full_message']) : '';
$c['full_message'] = IDF_Commit::toUTF8($c['full_message']);
$c['title'] = IDF_Commit::toUTF8($c['title']);
$res[] = (object) $c;
return $res;
}
public function getArchiveCommand($commit, $prefix='repository/')
{
return sprintf(Pluf::f('idf_exec_cmd_prefix', '').
'GIT_DIR=%s '.Pluf::f('git_path', 'git').' archive --format=zip --prefix=%s %s',
escapeshellarg($this->repo),
escapeshellarg($prefix),
escapeshellarg($commit));
}
/*
* =====================================================
* Specific Git Commands
* =====================================================
*/
/**
* Get submodule details.
*
* Given a "commit" file in the tree, find the submodule details.
*
* @param stdClass File description of the module
* @param string Current commit
* @return stdClass File description
*/
public function getSubmodule($file, $commit)
{
$file->type = 'extern';
$file->extern = '';
$info = $this->getPathInfo('.gitmodules', $commit);
if ($info == false) {
return $file;
}
$gitmodules = $this->getFile($info);
if (preg_match('#\[submodule\s+\"'.$file->fullpath.'\"\]\s+path\s=\s(\S+)\s+url\s=\s(\S+)#mi', $gitmodules, $matches)) {
$file->extern = $matches[2];
}
return $file;
}
/**
* Foreach file in the tree, find the details.
*
* @param array Tree information
* @return array Updated tree information
*/
public function getTreeDetails($tree)
{
$n = count($tree);
$details = array();
for ($i=0;$i<$n;$i++) {
if ($tree[$i]->type == 'blob') {
$details[$tree[$i]->hash] = $i;
}
}
if (!count($details)) {
return $tree;
}
$res = $this->getCachedBlobInfo($details);
$toapp = array();
foreach ($details as $blob => $idx) {
if (isset($res[$blob])) {
$tree[$idx]->date = $res[$blob]->date;
$tree[$idx]->log = $res[$blob]->title;
$tree[$idx]->author = $res[$blob]->author;
} else {
$toapp[$blob] = $idx;
}
}
if (count($toapp)) {
$res = $this->appendBlobInfoCache($toapp);
foreach ($details as $blob => $idx) {
if (isset($res[$blob])) {
$tree[$idx]->date = $res[$blob]->date;
$tree[$idx]->log = $res[$blob]->title;
$tree[$idx]->author = $res[$blob]->author;
}
}
}
return $tree;
}
/**
* Append build info cache.
*
* The append method tries to get only the necessary details, so
* instead of going through all the commits one at a time, it will
* try to find a smarter way with regex.
*
* @see self::buildBlobInfoCache
*
* @param array The blob for which we need the information
* @return array The information
*/
public function appendBlobInfoCache($blobs)
{
$rawlog = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '')
.sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log --raw --abbrev=40 --pretty=oneline -5000 --skip=%%s',
escapeshellarg($this->repo));
$skip = 0;
$res = array();
self::exec('IDF_Scm_Git::appendBlobInfoCache',
sprintf($cmd, $skip), $rawlog);
while (count($rawlog) and count($blobs)) {
$rawlog = implode("\n", array_reverse($rawlog));
foreach ($blobs as $blob => $idx) {
if (preg_match('/^\:\d{6} \d{6} [0-9a-f]{40} '
.$blob.' .*^([0-9a-f]{40})/msU',
$rawlog, $matches)) {
$fc = $this->getCommit($matches[1]);
$res[$blob] = (object) array('hash' => $blob,
'date' => $fc->date,
'title' => $fc->title,
'author' => $fc->author);
unset($blobs[$blob]);
}
}
$rawlog = array();
$skip += 5000;
if ($skip > 20000) {
// We are in the case of the import of a big old
// repository, we can store as unknown the commit info
// not to try to retrieve them each time.
foreach ($blobs as $blob => $idx) {
$res[$blob] = (object) array('hash' => $blob,
'date' => '0',
'title' => '----',
'author' => 'Unknown');
}
break;
}
self::exec('IDF_Scm_Git::appendBlobInfoCache',
sprintf($cmd, $skip), $rawlog);
}
$this->cacheBlobInfo($res);
return $res;
}
/**
* Build the blob info cache.
*
* We build the blob info cache 500 commits at a time.
*/
public function buildBlobInfoCache()
{
$rawlog = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '')
.sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log --raw --abbrev=40 --pretty=oneline -500 --skip=%%s',
escapeshellarg($this->repo));
$skip = 0;
self::exec('IDF_Scm_Git::buildBlobInfoCache',
sprintf($cmd, $skip), $rawlog);
while (count($rawlog)) {
$commit = '';
$data = array();
foreach ($rawlog as $line) {
if (substr($line, 0, 1) != ':') {
$commit = $this->getCommit(substr($line, 0, 40));
continue;
}
$blob = substr($line, 56, 40);
$data[] = (object) array('hash' => $blob,
'date' => $commit->date,
'title' => $commit->title,
'author' => $commit->author);
}
$this->cacheBlobInfo($data);
$rawlog = array();
$skip += 500;
self::exec('IDF_Scm_Git::buildBlobInfoCache',
sprintf($cmd, $skip), $rawlog);
}
}
/**
* Get blob info.
*
* When we display the tree, we want to know when a given file was
* created, who was the author and at which date. This is a very
* slow operation for git as we need to go through the full
* history, find when then blob was introduced, then grab the
* corresponding commit. This is why we need a cache.
*
* @param array List as keys of blob hashs to get info for
* @return array Hash indexed results, when not found not set
*/
public function getCachedBlobInfo($hashes)
{
$cache = new IDF_Scm_Cache_Git();
$cache->_project = $this->project;
return $cache->retrieve(array_keys($hashes));
}
/**
* Cache blob info.
*
* Given a series of blob info, cache them.
*
* @param array Blob info
* @return bool Success
*/
public function cacheBlobInfo($info)
{
$cache = new IDF_Scm_Cache_Git();
$cache->_project = $this->project;
return $cache->store($info);
}
public function getFileCachedBlobInfo($hashes)
{
$res = array();
$cache = Pluf::f('tmp_folder').'/IDF_Scm_Git-'.md5($this->repo).'.cache.db';
if (!file_exists($cache)) {
return $res;
}
$data = file_get_contents($cache);
if (false === $data) {
return $res;
}
$data = explode(chr(30), $data);
foreach ($data as $rec) {
if (isset($hashes[substr($rec, 0, 40)])) {
$tmp = explode(chr(31), substr($rec, 40), 3);
$res[substr($rec, 0, 40)] =
(object) array('hash' => substr($rec, 0, 40),
'date' => $tmp[0],
'title' => $tmp[2],
'author' => $tmp[1]);
}
}
return $res;
}
/**
* File cache blob info.
*
* Given a series of blob info, cache them.
*
* @param array Blob info
* @return bool Success
*/
public function fileCacheBlobInfo($info)
{
// Prepare the data
$data = array();
foreach ($info as $file) {
$data[] = $file->hash.$file->date.chr(31).$file->author.chr(31).$file->title;
}
$data = implode(chr(30), $data).chr(30);
$cache = Pluf::f('tmp_folder').'/IDF_Scm_Git-'.md5($this->repo).'.cache.db';
$fp = fopen($cache, 'ab');
if ($fp) {
flock($fp, LOCK_EX);
fwrite($fp, $data, strlen($data));
fclose($fp); // releases the lock too
return true;
}
return false;
}
}

460
src/IDF/Scm/Mercurial.php Normal file
View File

@@ -0,0 +1,460 @@
<?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 ***** */
/**
* Mercurial utils.
*
*/
class IDF_Scm_Mercurial extends IDF_Scm
{
public function __construct($repo, $project=null)
{
$this->repo = $repo;
$this->project = $project;
}
public function getRepositorySize()
{
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
.escapeshellarg($this->repo);
$out = explode(' ',
self::shell_exec('IDF_Scm_Mercurial::getRepositorySize',
$cmd),
2);
return (int) $out[0]*1024;
}
public static function factory($project)
{
$rep = sprintf(Pluf::f('mercurial_repositories'), $project->shortname);
return new IDF_Scm_Mercurial($rep, $project);
}
public function isAvailable()
{
try {
$branches = $this->getBranches();
} catch (IDF_Scm_Exception $e) {
return false;
}
return (count($branches) > 0);
}
public function findAuthor($author)
{
// We extract the email.
$match = array();
if (!preg_match('/<(.*)>/', $author, $match)) {
return null;
}
$sql = new Pluf_SQL('email=%s', array($match[1]));
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
return ($users->count() > 0) ? $users[0] : null;
}
public function getMainBranch()
{
return 'tip';
}
public static function getAnonymousAccessUrl($project)
{
return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname);
}
public static function getAuthAccessUrl($project, $user)
{
return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname);
}
public function isValidRevision($rev)
{
$cmd = sprintf(Pluf::f('hg_path', 'hg').' log -R %s -r %s',
escapeshellarg($this->repo),
escapeshellarg($rev));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::isValidRevision', $cmd, $out, $ret);
return ($ret == 0) && (count($out) > 0);
}
/**
* 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, $dummy=null)
{
$cmd = sprintf(Pluf::f('hg_path', 'hg').' log -R %s -r %s',
escapeshellarg($this->repo),
escapeshellarg($hash));
$ret = 0;
$out = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::testHash', $cmd, $out, $ret);
return ($ret != 0) ? false : 'commit';
}
public function getTree($commit, $folder='/', $branch=null)
{
// now we grab the info about this commit including its tree.
$folder = ($folder == '/') ? '' : $folder;
$co = $this->getCommit($commit);
if ($folder) {
// As we are limiting to a given folder, we need to find
// the tree corresponding to this folder.
$found = false;
foreach ($this->getTreeInfo($co->tree, true, '', true) as $file) {
if ($file->type == 'tree' and $file->file == $folder) {
$found = true;
break;
}
}
if (!$found) {
throw new Exception(sprintf(__('Folder %1$s not found in commit %2$s.'), $folder, $commit));
}
}
$res = $this->getTreeInfo($commit, $recurse=true, $folder);
return $res;
}
/**
* Get the tree info.
*
* @param string Tree hash
* @param bool Do we recurse in subtrees (true)
* @return array Array of file information.
*/
public function getTreeInfo($tree, $recurse=true, $folder='', $root=false)
{
if ('commit' != $this->testHash($tree)) {
throw new Exception(sprintf(__('Not a valid tree: %s.'), $tree));
}
$cmd_tmpl = Pluf::f('hg_path', 'hg').' manifest -R %s --debug -r %s';
$cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo), $tree, ($recurse) ? '' : '');
$out = array();
$res = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::getTreeInfo', $cmd, $out);
$tmp_hack = array();
while (null !== ($line = array_pop($out))) {
list($hash, $perm, $exec, $file) = preg_split('/ |\t/', $line, 4);
$file = trim($file);
$dir = explode('/', $file, -1);
$tmp = '';
for ($i=0, $n=count($dir); $i<$n; $i++) {
if ($i > 0) {
$tmp .= '/';
}
$tmp .= $dir[$i];
if (!isset($tmp_hack["empty\t000\t\t$tmp/"])) {
$out[] = "empty\t000\t\t$tmp/";
$tmp_hack["empty\t000\t\t$tmp/"] = 1;
}
}
if (preg_match('/^(.*)\/$/', $file, $match)) {
$type = 'tree';
$file = $match[1];
} else {
$type = 'blob';
}
if (!$root and !$folder and preg_match('/^.*\/.*$/', $file)) {
continue;
}
if ($folder) {
preg_match('|^'.$folder.'[/]?([^/]+)?$|', $file,$match);
if (count($match) > 1) {
$file = $match[1];
} else {
continue;
}
}
$fullpath = ($folder) ? $folder.'/'.$file : $file;
$efullpath = self::smartEncode($fullpath);
$res[] = (object) array('perm' => $perm, 'type' => $type,
'hash' => $hash, 'fullpath' => $fullpath,
'efullpath' => $efullpath, 'file' => $file);
}
return $res;
}
public function getPathInfo($totest, $commit='tip')
{
$cmd_tmpl = Pluf::f('hg_path', 'hg').' manifest -R %s --debug -r %s';
$cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo), $commit);
$out = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::getPathInfo', $cmd, $out);
$tmp_hack = array();
while (null !== ($line = array_pop($out))) {
list($hash, $perm, $exec, $file) = preg_split('/ |\t/', $line, 4);
$file = trim($file);
$dir = explode('/', $file, -1);
$tmp = '';
for ($i=0, $n=count($dir); $i<$n; $i++) {
if ($i > 0) {
$tmp .= '/';
}
$tmp .= $dir[$i];
if ($tmp == $totest) {
$pathinfo = pathinfo($totest);
return (object) array('perm' => '000', 'type' => 'tree',
'hash' => $hash,
'fullpath' => $totest,
'file' => $pathinfo['basename'],
'commit' => $commit
);
}
if (!isset($tmp_hack["empty\t000\t\t$tmp/"])) {
$out[] = "empty\t000\t\t$tmp/";
$tmp_hack["empty\t000\t\t$tmp/"] = 1;
}
}
if (preg_match('/^(.*)\/$/', $file, $match)) {
$type = 'tree';
$file = $match[1];
} else {
$type = 'blob';
}
if ($totest == $file) {
$pathinfo = pathinfo($totest);
return (object) array('perm' => $perm, 'type' => $type,
'hash' => $hash,
'fullpath' => $totest,
'file' => $pathinfo['basename'],
'commit' => $commit
);
}
}
return false;
}
public function getFile($def, $cmd_only=false)
{
$cmd = sprintf(Pluf::f('hg_path', 'hg').' cat -R %s -r %s %s',
escapeshellarg($this->repo),
escapeshellarg($def->commit),
escapeshellarg($this->repo.'/'.$def->fullpath));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
return ($cmd_only) ?
$cmd : self::shell_exec('IDF_Scm_Mercurial::getFile', $cmd);
}
/**
* Get the branches.
*
* @return array Branches.
*/
public function getBranches()
{
if (isset($this->cache['branches'])) {
return $this->cache['branches'];
}
$out = array();
$cmd = sprintf(Pluf::f('hg_path', 'hg').' branches -R %s',
escapeshellarg($this->repo));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::getBranches', $cmd, $out);
$res = array();
foreach ($out as $b) {
preg_match('/(\S+).*\S+:(\S+)/', $b, $match);
$res[$match[1]] = '';
}
$this->cache['branches'] = $res;
return $res;
}
/**
* Get the tags.
*
* @return array Tags.
*/
public function getTags()
{
if (isset($this->cache['tags'])) {
return $this->cache['tags'];
}
$out = array();
$cmd = sprintf(Pluf::f('hg_path', 'hg').' tags -R %s',
escapeshellarg($this->repo));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::getTags', $cmd, $out);
$res = array();
foreach ($out as $b) {
preg_match('/(\S+).*\S+:(\S+)/', $b, $match);
$res[$match[1]] = '';
}
$this->cache['tags'] = $res;
return $res;
}
public function inBranches($commit, $path)
{
return (in_array($commit, array_keys($this->getBranches())))
? array($commit) : array();
}
public function inTags($commit, $path)
{
return (in_array($commit, array_keys($this->getTags())))
? array($commit) : array();
}
/**
* Get commit details.
*
* @param string Commit ('HEAD')
* @param bool Get commit diff (false)
* @return array Changes
*/
public function getCommit($commit, $getdiff=false)
{
if (!$this->isValidRevision($commit)) {
return false;
}
$tmpl = ($getdiff) ?
Pluf::f('hg_path', 'hg').' log -p -r %s -R %s' : Pluf::f('hg_path', 'hg').' log -r %s -R %s';
$cmd = sprintf($tmpl,
escapeshellarg($commit), escapeshellarg($this->repo));
$out = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::getCommit', $cmd, $out);
$log = array();
$change = array();
$inchange = false;
foreach ($out as $line) {
if (!$inchange and 0 === strpos($line, 'diff -r')) {
$inchange = true;
}
if ($inchange) {
$change[] = $line;
} else {
$log[] = $line;
}
}
$out = self::parseLog($log, 6);
$out[0]->changes = implode("\n", $change);
return $out[0];
}
/**
* Check if a commit is big.
*
* @param string Commit ('HEAD')
* @return bool The commit is big
*/
public function isCommitLarge($commit='HEAD')
{
return false;
}
/**
* Get latest changes.
*
* @param string Commit ('HEAD').
* @param int Number of changes (10).
* @return array Changes.
*/
public function getChangeLog($commit='tip', $n=10)
{
$cmd = sprintf(Pluf::f('hg_path', 'hg').' log -R %s -l%s ', escapeshellarg($this->repo), $n, $commit);
$out = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::getChangeLog', $cmd, $out);
return self::parseLog($out, 6);
}
/**
* Parse the log lines of a --pretty=medium log output.
*
* @param array Lines.
* @param int Number of lines in the headers (3)
* @return array Change log.
*/
public static function parseLog($lines, $hdrs=3)
{
$res = array();
$c = array();
$i = 0;
$hdrs += 1;
foreach ($lines as $line) {
$i++;
if (0 === strpos($line, 'changeset:')) {
if (count($c) > 0) {
$c['full_message'] = trim($c['full_message']);
$res[] = (object) $c;
}
$c = array();
$c['commit'] = substr(strrchr($line, ':'), 1);
$c['full_message'] = '';
$i=1;
continue;
}
if ($i == $hdrs) {
$c['title'] = trim($line);
continue;
}
$match = array();
if (preg_match('/(\S+)\s*:\s*(.*)/', $line, $match)) {
$match[1] = strtolower($match[1]);
if ($match[1] == 'user') {
$c['author'] = $match[2];
} elseif ($match[1] == 'summary') {
$c['title'] = $match[2];
} else {
$c[$match[1]] = trim($match[2]);
}
if ($match[1] == 'date') {
$c['date'] = gmdate('Y-m-d H:i:s', strtotime($match[2]));
}
continue;
}
if ($i > ($hdrs+1)) {
$c['full_message'] .= trim($line)."\n";
continue;
}
}
$c['tree'] = !empty($c['commit']) ? trim($c['commit']) : '';
$c['full_message'] = !empty($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='')
{
return sprintf(Pluf::f('idf_exec_cmd_prefix', '').
Pluf::f('hg_path', 'hg').' archive --type=zip -R %s -r %s -',
escapeshellarg($this->repo),
escapeshellarg($commit));
}
}

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

@@ -0,0 +1,571 @@
<?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 ***** */
/**
* Subversion backend.
* When a branch is not a branch.
*
* Contrary to most other SCMs, Subversion is using folders to manage
* the branches and so what is either the commit or the branch in
* other SCMs is the revision number with Subversion. So, do not be
* surprised if you have the feeling that the methods are not really
* returning what could be expected from their names.
*/
class IDF_Scm_Svn extends IDF_Scm
{
public $username = '';
public $password = '';
private $assoc = array('dir' => 'tree',
'file' => 'blob');
public function __construct($repo, $project=null)
{
$this->repo = $repo;
$this->project = $project;
$this->cache['commitmess'] = array();
}
public function isAvailable()
{
return true;
}
public function getRepositorySize()
{
if (strpos($this->repo, 'file://') !== 0) {
return -1;
}
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
.escapeshellarg(substr($this->repo, 7));
$out = explode(' ', self::shell_exec('IDF_Scm_Svn::getRepositorySize', $cmd), 2);
return (int) $out[0]*1024;
}
/**
* Given the string describing the author from the log find the
* author in the database.
*
* @param string Author
* @return mixed Pluf_User or null
*/
public function findAuthor($author)
{
$sql = new Pluf_SQL('login=%s', array(trim($author)));
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
return ($users->count() > 0) ? $users[0] : null;
}
/**
* Returns the URL of the subversion repository.
*
* @param IDF_Project
* @return string URL
*/
public static function getAnonymousAccessUrl($project)
{
$conf = $project->getConf();
if (false !== ($url=$conf->getVal('svn_remote_url', false))
&& !empty($url)) {
// Remote repository
return $url;
}
return sprintf(Pluf::f('svn_remote_url'), $project->shortname);
}
/**
* Returns the URL of the subversion repository.
*
* @param IDF_Project
* @return string URL
*/
public static function getAuthAccessUrl($project, $user)
{
$conf = $project->getConf();
if (false !== ($url=$conf->getVal('svn_remote_url', false))
&& !empty($url)) {
// Remote repository
return $url;
}
return sprintf(Pluf::f('svn_remote_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))
&& !empty($rep)) {
// Remote repository
$scm = new IDF_Scm_Svn($rep, $project);
$scm->username = $conf->getVal('svn_username');
$scm->password = $conf->getVal('svn_password');
return $scm;
} else {
$rep = sprintf(Pluf::f('svn_repositories'), $project->shortname);
return new IDF_Scm_Svn($rep, $project);
}
}
/**
* Subversion revisions are either a number or 'HEAD'.
*/
public function isValidRevision($rev)
{
if ($rev == 'HEAD') {
return true;
}
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo),
escapeshellarg($rev));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Svn::isValidRevision', $cmd, $out, $ret);
return (0 == $ret);
}
/**
* 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(Pluf::f('svn_path', 'svn').' info --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.self::smartEncode($path)),
escapeshellarg($rev));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$xmlInfo = self::shell_exec('IDF_Scm_Svn::testHash', $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';
}
public function getTree($commit, $folder='/', $branch=null)
{
$cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.self::smartEncode($folder)),
escapeshellarg($commit));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getTree', $cmd));
$res = array();
$folder = (strlen($folder) and ($folder != '/')) ? $folder.'/' : '';
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['efullpath'] = self::smartEncode($file['fullpath']);
$file['date'] = gmdate('Y-m-d H:i:s',
strtotime((string) $entry->commit->date));
$file['rev'] = (string) $entry->commit['revision'];
$file['log'] = $this->getCommitMessage($file['rev']);
// Get the size if the type is blob
if ($file['type'] == 'blob') {
$file['size'] = (string) $entry->size;
}
$file['author'] = (string) $entry->commit->author;
$file['perm'] = '';
$res[] = (object) $file;
}
return $res;
}
/**
* Get the commit message of a revision revision.
*
* @param string Commit ('HEAD')
* @return String commit message
*/
private function getCommitMessage($rev='HEAD')
{
if (isset($this->cache['commitmess'][$rev])) {
return $this->cache['commitmess'][$rev];
}
$cmd = sprintf(Pluf::f('svn_path', 'svn').' log --xml --limit 1 --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo),
escapeshellarg($rev));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getCommitMessage', $cmd));
$this->cache['commitmess'][$rev] = (string) $xml->logentry->msg;
return $this->cache['commitmess'][$rev];
}
public function getPathInfo($filename, $rev=null)
{
if ($rev == null) {
$rev = 'HEAD';
}
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.self::smartEncode($filename)),
escapeshellarg($rev));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getPathInfo', $cmd));
if (!isset($xml->entry)) {
return false;
}
$entry = $xml->entry;
$file = array();
$file['fullpath'] = $filename;
$file['hash'] = (string) $entry->repository->uuid;
$file['type'] = $this->assoc[(string) $entry['kind']];
$pathinfo = pathinfo($filename);
$file['file'] = $pathinfo['basename'];
$file['rev'] = $rev;
$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;
}
public function getFile($def, $cmd_only=false)
{
$cmd = sprintf(Pluf::f('svn_path', 'svn').' cat --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.self::smartEncode($def->fullpath)),
escapeshellarg($def->rev));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
return ($cmd_only) ?
$cmd : self::shell_exec('IDF_Scm_Svn::getFile', $cmd);
}
/**
* Subversion branches are folder based.
*
* One need to list the folder to know them.
*/
public function getBranches()
{
if (isset($this->cache['branches'])) {
return $this->cache['branches'];
}
$res = array();
$cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --username=%s --password=%s %s@HEAD',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/branches'));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Svn::getBranches', $cmd, $out, $ret);
if ($ret == 0) {
foreach ($out as $entry) {
if (substr(trim($entry), -1) == '/') {
$branch = substr(trim($entry), 0, -1);
$res[$branch] = 'branches/'.$branch;
}
}
}
ksort($res);
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --username=%s --password=%s %s@HEAD',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/trunk'));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Svn::getBranches', $cmd, $out, $ret);
if ($ret == 0) {
$res = array('trunk' => 'trunk') + $res;
}
$this->cache['branches'] = $res;
return $res;
}
/**
* Subversion tags are folder based.
*
* One need to list the folder to know them.
*/
public function getTags()
{
if (isset($this->cache['tags'])) {
return $this->cache['tags'];
}
$res = array();
$cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --username=%s --password=%s %s@HEAD',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/tags'));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Svn::getTags', $cmd, $out, $ret);
if ($ret == 0) {
foreach ($out as $entry) {
if (substr(trim($entry), -1) == '/') {
$tag = substr(trim($entry), 0, -1);
$res[$tag] = 'tags/'.$tag;
}
}
}
ksort($res);
$this->cache['tags'] = $res;
return $res;
}
public function getMainBranch()
{
return 'HEAD';
}
public function inBranches($commit, $path)
{
foreach ($this->getBranches() as $branch => $bpath) {
if ($bpath and 0 === strpos($path, $bpath)) {
return array($branch);
}
}
return array();
}
public function inTags($commit, $path)
{
foreach ($this->getTags() as $tag => $tpath) {
if ($tpath and 0 === strpos($path, $tpath)) {
return array($tag);
}
}
return array();
}
/**
* Get commit details.
*
* @param string Commit
* @param bool Get commit diff (false)
* @return array Changes
*/
public function getCommit($commit, $getdiff=false)
{
if (!$this->isValidRevision($commit)) {
return false;
}
$res = array();
$cmd = sprintf(Pluf::f('svn_path', 'svn').' log --xml --limit 1 -v --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo),
escapeshellarg($commit));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$xmlRes = self::shell_exec('IDF_Scm_Svn::getCommit', $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'] = ($getdiff) ? $this->getDiff($commit) : '';
$res['tree'] = '';
return (object) $res;
}
/**
* Check if a commit is big.
*
* @param string Commit ('HEAD')
* @return bool The commit is big
*/
public function isCommitLarge($commit='HEAD')
{
if (substr($this->repo, 0, 7) != 'file://') {
return false;
}
// We have a locally hosted repository, we can query it with
// svnlook
$repo = substr($this->repo, 7);
$cmd = sprintf(Pluf::f('svnlook_path', 'svnlook').' changed -r %s %s',
escapeshellarg($commit),
escapeshellarg($repo));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$out = self::shell_exec('IDF_Scm_Svn::isCommitLarge', $cmd);
$lines = preg_split("/\015\012|\015|\012/", $out);
return (count($lines) > 100);
}
private function getDiff($rev='HEAD')
{
$res = array();
$cmd = sprintf(Pluf::f('svn_path', 'svn').' diff -c %s --username=%s --password=%s %s',
escapeshellarg($rev),
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
return self::shell_exec('IDF_Scm_Svn::getDiff', $cmd);
}
/**
* Get latest changes.
*
* @param string Revision or ('HEAD').
* @param int Number of changes (10).
*
* @return array Changes.
*/
public function getChangeLog($branch=null, $n=10)
{
if ($branch != 'HEAD' and !preg_match('/^\d+$/', $branch)) {
// we accept only revisions or HEAD
$branch = 'HEAD';
}
$res = array();
$cmd = sprintf(Pluf::f('svn_path', 'svn').' log --xml -v --limit %s --username=%s --password=%s %s@%s',
escapeshellarg($n),
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo),
escapeshellarg($branch));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$xmlRes = self::shell_exec('IDF_Scm_Svn::getChangeLog', $cmd);
$xml = simplexml_load_string($xmlRes);
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));
$split = preg_split("[\n\r]", (string) $entry->msg, 2);
$log['title'] = $split[0];
$log['commit'] = (string) $entry['revision'];
$log['full_message'] = (isset($split[1])) ? trim($split[1]) : '';
$res[] = (object) $log;
}
return $res;
}
/**
* 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(Pluf::f('svn_path', 'svn').' proplist --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.self::smartEncode($path)),
escapeshellarg($rev));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$xmlProps = self::shell_exec('IDF_Scm_Svn::getProperties', $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(Pluf::f('svn_path', 'svn').' propget --xml %s --username=%s --password=%s %s@%s',
escapeshellarg($property),
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.self::smartEncode($path)),
escapeshellarg($rev));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$xmlProp = self::shell_exec('IDF_Scm_Svn::getProperty', $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(Pluf::f('svn_path', 'svn').' info --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo),
escapeshellarg($rev));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$xmlInfo = self::shell_exec('IDF_Scm_Svn::getLastCommit', $cmd);
$xml = simplexml_load_string($xmlInfo);
return (string) $xml->entry->commit['revision'];
}
}

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

@@ -0,0 +1,202 @@
<?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 Model class (null)
* @param string Stemmer class ('Pluf_Text_Stemmer_Porter')
* @return array Results
*/
public static function mySearch($query, $project=null, $model=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) or count($word_ids) == 0) {
return array();
}
return self::mySearchDocuments($word_ids, $project, $model);
}
/**
* 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.
* @param string Model class to limit the search.
* @return array Sorted by score, returns model_class, model_id and score.
*/
public static function mySearchDocuments($wids, $project, $model)
{
$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;
$md = (is_null($model)) ? '' : ' AND model_class='.$db->esc($model);
$select = 'SELECT model_class, model_id, SUM(pondocc) AS score FROM '.$gocc->getSqlTable().' WHERE '.implode(' OR ', $where).$prj.$md.' 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];
try {
$word->create();
$new_words++;
$ids[$i] = $word->id;
} catch (Exception $e) {
// 100% of the time, the word has been created
// by another process in the background.
$r_ids = self::getWordIds(array($word->word));
if ($r_ids[0]) {
$ids[$i] = $r_ids[0];
} else {
// give up for this word
continue;
}
}
}
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);
}
/**
* Remove an item from the index.
*
* You must call this function when you delete items wich are
* indexed. Just add the call:
*
* IDF_Search::remove($this);
*
* in the preDelete() method of your object.
*
* @param mixed Item to be removed
* @return bool Success
*/
public static function remove($item)
{
if ($item->id > 0) {
$sql = new Pluf_SQL('model_id=%s AND model_class=%s',
array($item->id, $item->_model));
$items = Pluf::factory('IDF_Search_Occ')->getList(array('filter'=>$sql->gen()));
foreach ($items as $tl) {
$tl->delete();
}
}
return true;
}
}

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

@@ -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 ***** */
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
/**
* Show the name of a user in a template.
*
* It will automatically make the link to the profile if needed. In
* the future it will allow us to add little badges on the side of the
* user based on karma or whatever.
*
* This will also provide a consistent display of a user name in the
* application.
*/
class IDF_Template_AssignShowUser extends Pluf_Template_Tag
{
/**
* We need the user object and the request.
*
* If the user object is null (for example a non associated
* commit), we can use the $text value for an alternative display.
*
* @param string Which variable to assign
* @param Pluf_User
* @param Pluf_HTTP_Request
* @param string Alternate text ('')
*/
function start($var, $user, $request, $text='')
{
$t = new IDF_Template_ShowUser($this->context);
$this->context->set($var,
Pluf_Template::markSafe($t->start($user, $request, $text, false)));
}
}

View File

@@ -31,6 +31,6 @@ class IDF_Template_HotKey extends Pluf_Template_Tag
function start($key, $view, $params=array(), $get_params=array())
{
$url = addslashes(Pluf_HTTP_URL_urlForView($view, $params, $get_params));
echo "jQuery.hotkeys.add('$key',function (){window.location.href='$url';});";
echo "jQuery.hotkeys.add('$key',{disableInInput: true},function (){window.location.href='$url';});";
}
}

View File

@@ -29,22 +29,40 @@ 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, $echo=true, $wordwrap=true, $esc=true, $autolink=true, $nl2br=false)
{
$this->project = $project;
$this->git = new IDF_Git(Pluf::f('git_repository'));
$text = wordwrap($text, 80, "\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);
echo $text;
$this->project = $request->project;
$this->request = $request;
$this->scm = IDF_Scm::get($request->project);
if ($esc) $text = Pluf_esc($text);
if ($autolink) {
$text = preg_replace('#([a-z]+://[^\s\(\)]+)#i',
'<a href="\1">\1</a>', $text);
}
if ($request->rights['hasIssuesAccess']) {
$text = preg_replace_callback('#((?:issue|bug|ticket)(s)?\s+|\s+\#)(\d+)(\#ic\d+)?(?(2)((?:[, \w]+(?:\s+\#)?)?\d+(?:\#ic\d+)?){0,})#im',
array($this, 'callbackIssues'), $text);
}
if ($request->rights['hasReviewAccess']) {
$text = preg_replace_callback('#(reviews?\s+)(\d+(?:(?:\s+and|\s+or|,)\s+\d+)*)\b#i',
array($this, 'callbackReviews'), $text);
}
if ($request->rights['hasSourceAccess']) {
$text = preg_replace_callback('#(commits?\s+)([0-9a-f]{1,40}(?:(?:\s+and|\s+or|,)\s+[0-9a-f]{1,40})*)\b#i',
array($this, 'callbackCommits'), $text);
$text = preg_replace_callback('#(src:)([^\s\(\)\\\\]+(?:(\\\\)\s+[^\s\(\)\\\\]+){0,})+#im',
array($this, 'callbackSource'), $text);
}
if ($wordwrap) $text = Pluf_Text::wrapHtml($text, 69, "\n");
if ($nl2br) $text = nl2br($text);
if ($echo) {
echo $text;
} else {
return $text;
}
}
/**
@@ -52,18 +70,29 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
*/
function callbackIssues($m)
{
if (count($m) == 3) {
$issue = new IDF_Issue($m[2]);
if ($issue->id > 0 and $issue->project == $this->project->id) {
return $this->linkIssue($issue, $m[1].' '.$m[2]);
} else {
return $m[0]; // not existing issue.
$c = count($m);
if (4 === $c || 5 === $c) {
$issue = new IDF_Issue($m[3]);
if (0 < $issue->id and $issue->project == $this->project->id) {
$m[1] = trim($m[1]);
$prefix = '';
if ('#' === $m[1]) {
$title = $m[1].$m[3];
$prefix = mb_substr($m[0], 0, strpos($m[0], $m[1])); // fixes \n matches
} else {
$title = $m[1].' '.$m[3];
}
if (4 === $c) {
return $prefix.$this->linkIssue($issue, $title);
} else {
return $prefix.$this->linkIssue($issue, $title, $m[4]);
}
}
} else {
return preg_replace_callback('/(\d+)/',
array($this, 'callbackIssue'),
$m[0]);
return $m[0]; // not existing issue.
}
return preg_replace_callback('#(\#)?(\d+)(\#ic\d+)?#',
array($this, 'callbackIssue'),
$m[0]);
}
/**
@@ -74,21 +103,103 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
*/
function callbackIssue($m)
{
$issue = new IDF_Issue($m[1]);
if ($issue->id > 0 and $issue->project == $this->project->id) {
return $this->linkIssue($issue, $m[1]);
$issue = new IDF_Issue($m[2]);
if (0 < $issue->id and $issue->project == $this->project->id) {
if (4 === count($m)) {
return $this->linkIssue($issue, $m[1].$m[2], $m[3]);
}
return $this->linkIssue($issue, $m[1].$m[2]);
}
return $m[0]; // not existing issue.
}
/**
* General call back to convert commits to HTML links.
*
* @param array $m Single regex match.
* @return string Content with converted commits.
*/
function callbackCommits($m)
{
$keyword = rtrim($m[1]);
if ('commits' === $keyword) {
// Multiple commits like 'commits 6e030e6, a25bfc1 and
// 3c094f8'.
return $m[1].preg_replace_callback('#\b[0-9a-f]{4,40}\b#i', array($this, 'callbackCommit'), $m[2]);
} else if ('commit' === $keyword) {
// Single commit like 'commit 6e030e6'.
return $m[1].call_user_func(array($this, 'callbackCommit'), array($m[2]));
}
return $m[0];
}
/**
* Convert plaintext commit to HTML link. Called from callbackCommits.
*
* Regex callback for {@link IDF_Template_IssueComment::callbackCommits()}.
*
* @param array Single regex match.
* @return string HTML A element with commit.
*/
function callbackCommit($m)
{
$co = $this->scm->getCommit($m[0]);
if (!$co) {
return $m[0]; // not a commit.
}
return '<a href="'
.Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit', array($this->project->shortname, $co->commit))
.'">'.$m[0].'</a>';
}
/**
* General call back to convert reviews to HTML links.
*
* @param array $m Single regex match.
* @return string Content with converted reviews.
*/
function callbackReviews($m)
{
$keyword = rtrim($m[1]);
if ('reviews' === $keyword) {
return $m[1].preg_replace_callback('#\b(\d+)\b#i', array($this, 'callbackReview'), $m[2]);
} else if ('review' === $keyword) {
return $m[1].call_user_func(array($this, 'callbackReview'), array('', $m[2]));
}
return $m[0];
}
/**
* Convert plaintext commit to HTML link. Called from callbackReviews.
*
* Regex callback for {@link IDF_Template_IssueComment::callbackReviews()}.
*
* @param array Single regex match.
* @return string HTML A element with review.
*/
function callbackReview($m)
{
$review = new IDF_Review($m[1]);
if ($review->id > 0 and $review->project == $this->project->id) {
return $this->linkReview($review, $m[1]);
} else {
return $m[0]; // not existing issue.
}
}
function callbackCommit($m)
function callbackSource($m)
{
if ($this->git->testHash($m[2]) != 'commit') {
if (!$this->scm->isAvailable()) return $m[0];
$file = $m[2];
if (!empty($m[3])) $file = str_replace($m[3], '', $file);
$request_file_info = $this->scm->getPathInfo($file);
if (!$request_file_info) {
return $m[0];
}
$co = $this->git->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>';
if ($request_file_info->type != 'tree') {
return $m[1].'<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Source::tree', array($this->project->shortname, $this->scm->getMainBranch(), $file)).'">'.$file.'</a>';
}
return $m[0];
}
/**
@@ -98,10 +209,24 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
* @param string Name of the link.
* @return string Linked issue.
*/
public function linkIssue($issue, $title)
public function linkIssue($issue, $title, $anchor='')
{
$ic = (in_array($issue->status, $this->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
return '<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($this->project->shortname, $issue->id)).'" class="'.$ic.'" title="'.Pluf_esc($issue->summary).'">'.Pluf_esc($title).'</a>';
array($this->project->shortname, $issue->id)).$anchor.'" class="'.$ic.'" title="'.Pluf_esc($issue->summary).'">'.Pluf_esc($title).'</a>';
}
/**
* Generate the link to a review.
*
* @param IDF_Review Review.
* @param string Name of the link.
* @return string Linked review.
*/
public function linkReview($review, $title, $anchor='')
{
$ic = (in_array($review->status, $this->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o';
return '<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
array($this->project->shortname, $review->id)).$anchor.'" class="'.$ic.'" title="'.Pluf_esc($review->summary).'">'.Pluf_esc($title).'</a>';
}
}

View File

@@ -0,0 +1,95 @@
<?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');
/**
* Make the links to issues and commits.
*/
class IDF_Template_Markdown extends Pluf_Template_Tag
{
private $project = null;
private $request = null;
private $scm = null;
function start($text, $request)
{
$this->project = $request->project;
$this->request = $request;
// Replace like in the issue text
$tag = new IDF_Template_IssueComment();
$text = $tag->start($text, $request, false, false, false, false);
// Replace [[[path/to/file.mdtext, commit]]] with embedding
// the content of the file into the wki page
if ($this->request->rights['hasSourceAccess']) {
$text = preg_replace_callback('#\[\[\[([^\,]+)(?:, ([^/]+))?\]\]\]#im',
array($this, 'callbackEmbeddedDoc'),
$text);
}
// Replace [[PageName]] with corresponding link to the page.
$text = preg_replace_callback('#\[\[([A-Za-z0-9\-]+)\]\]#im',
array($this, 'callbackWikiPage'),
$text);
$filter = new IDF_Template_MarkdownPrefilter();
echo $filter->go(Pluf_Text_MarkDown_parse($text));
}
function callbackWikiPage($m)
{
$sql = new Pluf_SQL('project=%s AND title=%s',
array($this->project->id, $m[1]));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
if ($pages->count() != 1 and !$this->request->rights['hasWikiAccess']) {
return $m[0];
}
if ($pages->count() != 1 and $this->request->rights['hasWikiAccess']
and !$this->request->user->isAnonymous()) {
return '<img style="vertical-align: text-bottom;" alt=" " src="'.Pluf::f('url_media').'/idf/img/add.png" /><a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::create', array($this->project->shortname), array('name'=>$m[1])).'" title="'.__('Create this documentation page').'">'.$m[1].'</a>';
}
if (!$this->request->rights['hasWikiAccess'] or $pages->count() == 0) {
return $m[1];
}
return '<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view', array($this->project->shortname, $pages[0]->title)).'" title="'.Pluf_esc($pages[0]->summary).'">'.$m[1].'</a>';
}
function callbackEmbeddedDoc($m)
{
$scm = IDF_Scm::get($this->request->project);
$view_source = new IDF_Views_Source();
$match = array('dummy', $this->request->project->shortname);
$match[] = (isset($m[2])) ? $m[2] : $scm->getMainBranch();
$match[] = $m[1];
$res = $view_source->getFile($this->request, $match);
if ($res->status_code != 200) {
return $m[0];
}
$info = pathinfo($m[1]);
$fileinfo = array($res->headers['Content-Type'], $m[1],
isset($info['extension']) ? $info['extension'] : 'bin');
if (!IDF_Views_Source::isText($fileinfo)) {
return $m[0];
}
return $res->content;
}
}

View File

@@ -0,0 +1,178 @@
<?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 ***** */
/**
* Should be renamed MarkdownPostfilter.
*/
class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
{
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',
);
public $allowed = array(
'a' => array('href', 'title', 'rel'),
'abbr' => array('title'),
'address' => array(),
'b' => array(),
'blockquote' => array(),
'br' => array(),
'caption' => array(),
'code' => array(),
'dd' => array(),
'div' => array('align', 'class'),
'dl' => array(),
'dt' => array(),
'em' => array(),
'h1' => array('id'),
'h2' => array('id'),
'h3' => array('id'),
'h4' => array('id'),
'h5' => array('id'),
'h6' => array('id'),
'hr' => array(),
'i' => array(),
'img' => array('src', 'class', 'alt', 'height', 'width', 'style'),
'li' => array(),
'ol' => array(),
'p' => array('align', 'class'),
'pre' => array(),
'strong' => array(),
'table' => array('summary'),
'td' => array('style'),
'th' => array(),
'tr' => array(),
'ul' => array(),
);
// tags which should always be self-closing (e.g. "<img />")
public $no_close = array(
'img',
'br',
'hr',
);
// tags which must always have seperate opening and closing tags
// (e.g. "<b></b>")
public $always_close = array(
'strong',
'em',
'b',
'code',
'i',
'ul',
'ol',
'li',
'p',
'table',
'caption',
'tr',
'td',
'span',
'a',
'blockquote',
'pre',
'iframe',
'h1', 'h2', 'h3', 'address'
);
// attributes which should be checked for valid protocols
public $protocol_attributes = array(
'src',
'href',
);
// protocols which are allowed
public $allowed_protocols = array(
'http',
'https',
'ftp',
'mailto',
);
// tags which should be removed if they contain no content
// (e.g. "<b></b>" or "<b />")
public $remove_blanks = array(
'p',
'strong',
'em',
'caption',
'li',
'span',
);
}

View File

@@ -0,0 +1,67 @@
<?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');
/**
* Show the name of a user in a template.
*
* It will automatically make the link to the profile if needed. In
* the future it will allow us to add little badges on the side of the
* user based on karma or whatever.
*
* This will also provide a consistent display of a user name in the
* application.
*/
class IDF_Template_ShowUser extends Pluf_Template_Tag
{
/**
* We need the user object and the request.
*
* If the user object is null (for example a non associated
* commit), we can use the $text value for an alternative display.
*
* @param Pluf_User
* @param Pluf_HTTP_Request
* @param string Alternate text ('')
*/
function start($user, $request, $text='', $echo=true)
{
if ($user == null) {
$out = (strlen($text)) ? strip_tags($text) : __('Anonymous');
} else {
if (!$user->isAnonymous() and $user->id == $request->user->id) {
$utext = __('Me');
$url = Pluf_HTTP_URL_urlForView('idf_dashboard');
} else {
$utext = Pluf_esc($user);
$url = Pluf_HTTP_URL_urlForView('IDF_Views_User::view',
array($user->login));
}
$out = sprintf('<a href="%s" class="username">%s</a>',
$url, $utext);
}
if ($echo) echo $out;
else return $out;
}
}

View File

@@ -0,0 +1,34 @@
<?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 ***** */
/**
* Make the links to issues and commits.
*/
class IDF_Template_TimelineFragment extends Pluf_Template_Tag
{
function start($item, $request)
{
$m = Pluf::factory($item->model_class, $item->model_id);
echo $m->timelineFragment($request);
}
}

Some files were not shown because too many files have changed in this diff Show More