185 Commits
dev ... v1.0

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
96784b6e7c Started again the work on the review. 2009-03-29 07:01:32 +02:00
Loic d'Anterroches
37cb05ff38 Added Patrick in the list of contributors. 2009-03-06 14:04:59 +01:00
153 changed files with 20514 additions and 7887 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ www/test.php
www/media/upload
src/IDF/gettexttemplates
indefero-*.zip
src/IDF/conf/path.php

13
AUTHORS
View File

@@ -10,9 +10,20 @@ Much appreciated contributors:
Julien Issler
Manuel Eidenberger <eidenberger@gmail.com>
Ciaran Gultnieks
Mehdi Kabab
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.

View File

@@ -59,6 +59,8 @@ Here is the step-by-step installation procedure:
* 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:**
@@ -73,8 +75,7 @@ Here is the step-by-step installation procedure:
**Bootstrap script:**
<?php
set_include_path(get_include_path().PATH_SEPARATOR.'/home/www/indefero/src');
set_include_path(get_include_path().PATH_SEPARATOR.'/home/www/pluf/src');
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'));
@@ -97,7 +98,7 @@ Here is the step-by-step installation procedure:
* 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 Administer link on top and create your first project.
* Click on the Forge Management link on top and create your first project.
## Upgrade InDefero
@@ -112,11 +113,11 @@ To upgrade:
**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.
@@ -162,6 +163,11 @@ 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,
@@ -177,3 +183,33 @@ If your SMTP server requires authentication, for example,
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

View File

@@ -70,6 +70,13 @@ folder. Here is a configuration example:
$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

View File

@@ -67,6 +67,14 @@ You also need to provide the base definition of the hgrc file. For example:
'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
@@ -76,3 +84,13 @@ 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

View File

@@ -66,6 +66,8 @@ need to put the following in your configuration file:
$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

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

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

View File

@@ -22,18 +22,12 @@
# ***** END LICENSE BLOCK ***** */
/**
* This script is used to control the access to the git repositories
* using a restricted shell access.
* This script is used to sync the SSH keys, mark the repositories for
* export and prune the deleted repositories.
*
* The only argument must be the login of the user.
*/
// Set the include path to have Pluf and IDF in it.
$indefero_path = dirname(__FILE__).'/../src';
//$pluf_path = '/path/to/pluf/src';
set_include_path(get_include_path()
.PATH_SEPARATOR.$indefero_path
// .PATH_SEPARATOR.$pluf_path
);
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'));

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);

View File

@@ -27,13 +27,8 @@
*
* The only argument must be the login of the user.
*/
// Set the include path to have Pluf and IDF in it.
$indefero_path = dirname(__FILE__).'/../src';
//$pluf_path = '/path/to/pluf/src';
set_include_path(get_include_path()
.PATH_SEPARATOR.$indefero_path
// .PATH_SEPARATOR.$pluf_path
);
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'));

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);

View File

@@ -13,7 +13,7 @@
# 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.
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
@@ -144,36 +144,56 @@ class IDF_Commit extends Pluf_Model
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 = $change->title;
$commit->fullmessage = $change->full_message;
$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();
// We notify the creation of the commit
if ('' != $project->getConf()->getVal('source_notification_email', '')) {
$context = new Pluf_Template_Context(
array(
'c' => $commit,
'project' => $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'),
$project->getConf()->getVal('source_notification_email'),
sprintf(__('New Commit %s - %s (%s)'),
$commit->scm_id, $commit->summary, $project->shortname));
$email->addTextMessage($text_email);
$email->sendMail();
$commit->notify($project->getConf());
return $commit;
}
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);
}
}
/**
@@ -202,7 +222,7 @@ class IDF_Commit extends Pluf_Model
</tr>
<tr class="extra">
<td colspan="2">
<div class="helptext right">'.__('Commit').'&nbsp;<a href="'.$url.'" class="mono">'.$this->scm_id.'</a>, '.__('by').' '.$user.'</div></td></tr>';
<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);
}
@@ -218,23 +238,55 @@ class IDF_Commit extends Pluf_Model
.Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit',
array($request->project->shortname,
$this->scm_id));
$tag = new IDF_Template_IssueComment();
$summary = '<content type="xhtml">'."\n"
.'<div xmlns="http://www.w3.org/1999/xhtml">'
.$tag->start($this->summary, $request, false, false, false);
if ($this->fullmessage) {
$summary .= '<br /><br />'
.$tag->start($this->fullmessage, $request, false, false, false);
}
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
$summary .= '</div></content>';
$out = '<entry>
<title>'.Pluf_esc($request->project->name).': '.__('Commit').' '.$this->scm_id.'</title>
<link href="'.$url.'"/>
<id>'.$url.'</id>
<updated>'.$date.'</updated>'.$summary.'
</entry>
';
return $out;
$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

@@ -187,10 +187,10 @@ 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;
}

View File

@@ -133,6 +133,16 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
'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'] : '';
@@ -148,7 +158,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
public function clean_shortname()
{
$shortname = $this->cleaned_data['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 (-).'));
}
@@ -208,7 +218,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
$project->name = $this->cleaned_data['name'];
$project->shortname = $this->cleaned_data['shortname'];
$project->private = $this->cleaned_data['private_project'];
$project->description = __('Click on the Administer tab to set the description of your 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);

View File

@@ -61,6 +61,16 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form
));
}
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()) {

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

@@ -179,8 +179,11 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form
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)) {
return mb_convert_case(mb_strtolower($first_name),
$first_name = mb_convert_case(mb_strtolower($first_name),
MB_CASE_TITLE, 'UTF-8');
}
return $first_name;

View File

@@ -141,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));
}

View File

@@ -67,6 +67,45 @@ class IDF_Form_MembersConf extends Pluf_Form
$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.

View File

@@ -48,6 +48,21 @@ class IDF_Form_Password extends Pluf_Form
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;
}
@@ -64,7 +79,11 @@ class IDF_Form_Password extends Pluf_Form
$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()),
@@ -72,7 +91,8 @@ class IDF_Form_Password extends Pluf_Form
$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),
$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)));
@@ -82,5 +102,11 @@ class IDF_Form_Password extends Pluf_Form
$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

@@ -95,6 +95,10 @@ class IDF_Form_PasswordInputKey extends Pluf_Form
return false;
}
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
return split(':', $cr->decrypt($encrypted), 3);
$f = explode(':', $cr->decrypt($encrypted), 3);
if (count($f) != 3) {
return false;
}
return $f;
}
}

View File

@@ -73,6 +73,9 @@ class IDF_Form_PasswordReset extends Pluf_Form
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;
}

View File

@@ -124,8 +124,14 @@ class IDF_Form_Register extends Pluf_Form
$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;
@@ -144,6 +150,5 @@ class IDF_Form_Register extends Pluf_Form
__('Confirm the creation of your account.'));
$email->addTextMessage($text_email);
$email->sendMail();
return $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

@@ -1,203 +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 ***** */
/**
* 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;
}
$this->fields['title'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Page title'),
'initial' => __('PageName'),
'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' => 58,
'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 (split(',', $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();
return $page;
}
}

View File

@@ -204,26 +204,7 @@ class IDF_Form_ReviewCreate extends Pluf_Form
$patch->commit = self::findCommit($this->cleaned_data['commit']);
$patch->patch = $this->cleaned_data['patch'];
$patch->create();
// Send create notification
if ('' != $this->project->getConf()->getVal('review_notification_email', '')) {
$context = new Pluf_Template_Context(
array(
'review' => $review,
'patch' => $patch,
'comments' => array(),
'project' => $this->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'),
$this->project->getConf()->getVal('review_notification_email'),
sprintf(__('New Code Review %s - %s (%s)'),
$review->id, $review->summary, $this->project->shortname));
$email->addTextMessage($text_email);
$email->sendMail();
}
$patch->notify($this->project->getConf());
return $review;
}

View File

@@ -30,12 +30,15 @@ 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,
@@ -48,6 +51,41 @@ class IDF_Form_ReviewFileComment extends Pluf_Form
),
));
}
$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,
),
));
}
}
@@ -64,6 +102,16 @@ class IDF_Form_ReviewFileComment extends Pluf_Form
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.
*
@@ -76,19 +124,40 @@ class IDF_Form_ReviewFileComment extends Pluf_Form
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->patch = $this->patch;
$c->comment = $bc;
$c->cfile = $filename;
$c->submitter = $this->user;
$c->content = $this->cleaned_data[md5($filename)];
$c->create();
}
}
$this->patch->get_review()->update(); // reindex and put up in
// the list.
return $this->patch;
$review->update(); // reindex and put up in the list.
return $bc;
}
}

View File

@@ -86,6 +86,11 @@ class IDF_Form_TabsConf extends Pluf_Form
));
}
public function clean_authorized_users()
{
return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['authorized_users']);
}
public function save($commit=true)
{
if (!$this->isValid()) {

View File

@@ -46,6 +46,16 @@ class IDF_Form_UpdateUpload extends Pluf_Form
'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 = '';
@@ -76,7 +86,7 @@ class IDF_Form_UpdateUpload extends Pluf_Form
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (split(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
foreach (explode(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
@@ -132,6 +142,7 @@ class IDF_Form_UpdateUpload extends Pluf_Form
}
// 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);

View File

@@ -44,6 +44,16 @@ class IDF_Form_Upload extends Pluf_Form
'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'),
@@ -71,7 +81,8 @@ class IDF_Form_Upload extends Pluf_Form
{
$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|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|wmv|zip)$/i', $this->cleaned_data['file'])) {
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'];
@@ -85,7 +96,7 @@ class IDF_Form_Upload extends Pluf_Form
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (split(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
foreach (explode(',', $conf->getVal('labels_download_one_max', IDF_Form_UploadConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
@@ -155,6 +166,7 @@ class IDF_Form_Upload extends Pluf_Form
$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;
@@ -163,21 +175,7 @@ class IDF_Form_Upload extends Pluf_Form
$upload->setAssoc($tag);
}
// Send the notification
if ('' != $this->project->getConf()->getVal('downloads_notification_email', '')) {
$context = new Pluf_Template_Context(
array('file' => $upload,
'urlfile' => $upload->getAbsoluteUrl($this->project),
'project' => $this->project,
'tags' => $upload->get_tags_list(),
));
$tmpl = new Pluf_Template('idf/downloads/download-created-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail(Pluf::f('from_email'), $this->project->getConf()->getVal('downloads_notification_email'),
sprintf(__('New download - %s (%s)'),
$upload->summary, $this->project->shortname));
$email->addTextMessage($text_email);
$email->sendMail();
}
$upload->notify($this->project->getConf());
return $upload;
}
}

View File

@@ -94,7 +94,7 @@ class IDF_Form_UserAccount extends Pluf_Form
$this->fields['ssh_key'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Your public SSH key'),
'label' => __('Add a public SSH key'),
'initial' => '',
'widget_attrs' => array('rows' => 3,
'cols' => 40),
@@ -105,7 +105,6 @@ class IDF_Form_UserAccount extends Pluf_Form
}
/**
* Save the model in the database.
*
@@ -151,22 +150,17 @@ class IDF_Form_UserAccount extends Pluf_Form
$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);
// Get keys
$keys = $this->user->get_idf_key_list();
if ($keys->count() > 0) {
$key = $keys[0];
} else {
// 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) {
$this->user->update();
if ($key->id != '') {
$key->update();
} else {
$key->create();
}
}
if ($commit) {
$this->user->update();
if ($update_pass) {
/**
* [signal]
@@ -195,6 +189,57 @@ class IDF_Form_UserAccount extends Pluf_Form
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']);

View File

@@ -63,7 +63,7 @@ class IDF_Form_UserChangeEmail extends Pluf_Form
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 split(':', $cr->decrypt($encrypted), 3);
return explode(':', $cr->decrypt($encrypted), 3);
}

View File

@@ -127,7 +127,7 @@ Add your content here. Format your content with:
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (split(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
foreach (explode(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
@@ -199,25 +199,7 @@ Add your content here. Format your content with:
$rev->submitter = $this->user;
$rev->summary = __('Initial page creation');
$rev->create();
// send the notification
if ('' != $this->project->getConf()->getVal('wiki_notification_email', '')) {
$context = new Pluf_Template_Context(
array(
'page' => $page,
'rev' => $rev,
'project' => $this->project,
'url_base' => Pluf::f('url_base'),
)
);
$tmpl = new Pluf_Template('idf/wiki/wiki-created-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail(Pluf::f('from_email'),
$this->project->getConf()->getVal('wiki_notification_email'),
sprintf(__('New Documentation Page %s - %s (%s)'),
$page->title, $page->summary, $this->project->shortname));
$email->addTextMessage($text_email);
$email->sendMail();
}
$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;
}
}

View File

@@ -138,7 +138,7 @@ class IDF_Form_WikiUpdate extends Pluf_Form
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (split(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
foreach (explode(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
@@ -236,25 +236,7 @@ class IDF_Form_WikiUpdate extends Pluf_Form
$rev->summary = $this->cleaned_data['comment'];
$rev->changes = $changes;
$rev->create();
// send the notification
if ('' != $this->project->getConf()->getVal('wiki_notification_email', '')) {
$context = new Pluf_Template_Context(
array(
'page' => $this->page,
'rev' => $rev,
'project' => $this->project,
'url_base' => Pluf::f('url_base'),
)
);
$tmpl = new Pluf_Template('idf/wiki/wiki-updated-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail(Pluf::f('from_email'),
$this->project->getConf()->getVal('wiki_notification_email'),
sprintf(__('Documentation Page Changed %s - %s (%s)'),
$this->page->title, $this->page->summary, $this->project->shortname));
$email->addTextMessage($text_email);
$email->sendMail();
}
$rev->notify($this->project->getConf(), false);
return $this->page;
}
}

View File

@@ -193,38 +193,134 @@ class IDF_Issue extends Pluf_Model
$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>'), $url, $ic, $this->id).', '.__('by').' '.$user.'</div></td></tr>';
<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)
{
$base = '<entry>
<title>%%title%%</title>
<link href="%%url%%"/>
<id>%%url%%</id>
<updated>%%date%%</updated>
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
<pre>%%content%%</pre>
</div></content>
</entry>';
$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'),
Pluf_esc($request->project->name),
$this->id, Pluf_esc($this->summary));
// Get the first comment of this issue.
$request->project->name,
$this->id, $this->summary);
$cts = $this->get_comments_list(array('order' => 'id ASC',
'nb' => 1));
$tag = new IDF_Template_IssueComment();
$content = $tag->start($cts[0]->content, $request, false);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
return Pluf_Translation::sprintf($base,
$context = new Pluf_Template_Context_Request(
$request,
array('url' => $url,
'author' => $this->get_submitter(),
'title' => $title,
'content' => $content,
'date' => $date));
'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

@@ -167,66 +167,33 @@ class IDF_IssueComment extends Pluf_Model
$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>'), $url, $ic, $issue->id).', '.__('by').' '.$user.'</div></td></tr>';
<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)
{
$base = '<entry>
<title>%%title%%</title>
<link href="%%url%%"/>
<id>%%url%%</id>
<updated>%%date%%</updated>
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
%%content%%
</div></content>
</entry>';
$issue = $this->get_issue();
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($request->project->shortname,
$issue->id));
$url .= '#ic'.$this->id;
$title = sprintf(__('%s: Comment on issue %d - %s'),
Pluf_esc($request->project->name),
$issue->id, Pluf_esc($issue->summary));
$submitter = $this->get_submitter();
$tag = new IDF_Template_IssueComment();
$content = '<p><pre>'.$tag->start($this->content, $request, false).'</pre></p>';
if ($this->changedIssue()) {
$content .= '<p>';
foreach ($this->changes as $w => $v) {
$content .= '<strong>';
switch ($w) {
case 'su':
$content .= __('Summary:'); break;
case 'st':
$content .= __('Status:'); break;
case 'ow':
$content .= __('Owner:'); break;
case 'lb':
$content .= __('Labels:'); break;
}
$content .= '</strong> ';
if ($w == 'lb') {
$content .= Pluf_esc(implode(', ', $v));
} else {
$content .= Pluf_esc($v);
}
$content .= ' ';
}
$content .= '</p>';
}
$url .= '#ic'.$this->id;
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
return Pluf_Translation::sprintf($base,
$context = new Pluf_Template_Context_Request(
$request,
array('url' => $url,
'author' => $issue->get_submitter(),
'title' => $title,
'content' => $content,
'date' => $date));
'c' => $this,
'issue' => $issue,
'date' => $date)
);
$tmpl = new Pluf_Template('idf/issues/feedfragment.xml');
return $tmpl->render($context);
}
}

View File

@@ -70,6 +70,11 @@ class IDF_Key extends Pluf_Model
);
}
function showCompact()
{
return Pluf_Template::markSafe(Pluf_esc(substr($this->content, 0, 25)).' [...] '.Pluf_esc(substr($this->content, -55)));
}
function postSave($create=false)
{
/**
@@ -97,4 +102,30 @@ class IDF_Key extends Pluf_Model
'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

@@ -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,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

@@ -44,8 +44,10 @@ function IDF_Migrations_Install_setup($params=null)
'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);
@@ -83,8 +85,10 @@ function IDF_Migrations_Install_teardown($params=null)
$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',

View File

@@ -44,9 +44,38 @@ class IDF_Plugin_SyncGit
{
// 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'), 0666);
@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

@@ -40,7 +40,7 @@ class IDF_Plugin_SyncGit_Cron
$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 git_path_authorized_keys not set.');
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);
@@ -69,6 +69,44 @@ class IDF_Plugin_SyncGit_Cron
}
}
/**
* 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.
*
@@ -79,6 +117,9 @@ class IDF_Plugin_SyncGit_Cron
@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

@@ -91,11 +91,14 @@ class IDF_Plugin_SyncGit_Serve
// 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;
}
@@ -159,7 +162,7 @@ class IDF_Plugin_SyncGit_Serve
$request->user = $user;
$request->conf = $conf;
$request->project = $project;
if (true === IDF_Precondition::accessTabGeneric($request, 'source_access_rights')) {
if (true === IDF_Precondition::accessSource($request)) {
if ($mode == 'readonly') {
return array(Pluf::f('idf_plugin_syncgit_base_repositories', '/home/git/repositories'),
$project->shortname);
@@ -190,13 +193,39 @@ class IDF_Plugin_SyncGit_Serve
*/
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));
}
/**
@@ -209,7 +238,7 @@ class IDF_Plugin_SyncGit_Serve
{
$sql = new Pluf_SQL('shortname=%s', array($relpath));
$projects = Pluf::factory('IDF_Project')->getList(array('filter'=>$sql->gen()));
if ($projects->count() != 1) {
if ($projects->count() != 1 and file_exists($fullpath)) {
return $this->gitExportDeny($fullpath);
}
$project = $projects[0];
@@ -217,7 +246,8 @@ class IDF_Plugin_SyncGit_Serve
$conf->setProject($project);
$scm = $conf->getVal('scm', 'git');
if ($scm == 'git' and !file_exists($fullpath)) {
$this->initRepository($fullpath);
// No repository yet, just skip
return false;
}
if ($scm != 'git' or $project->private) {
return $this->gitExportDeny($fullpath);

View File

@@ -51,6 +51,9 @@ class IDF_Plugin_SyncMercurial
case 'Pluf_User::passwordUpdated':
$plug->processSyncPasswd($params['user']);
break;
case 'hgchangegroup.php::run':
$plug->processSyncTimeline($params);
break;
}
}
@@ -97,7 +100,8 @@ class IDF_Plugin_SyncMercurial
}
$ht = new File_Passwd_Authbasic($passwd_file);
$ht->load();
$ht->setMode(FILE_PASSWD_SHA);
$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 {
@@ -138,7 +142,8 @@ class IDF_Plugin_SyncMercurial
throw new Exception (sprintf(__('%s does not exist or is not writable.'), $passwd_file));
}
$ht = new File_Passwd_Authbasic($passwd_file);
$ht->setMode(FILE_PASSWD_SHA);
$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'],
@@ -220,4 +225,25 @@ class IDF_Plugin_SyncMercurial
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)));
}
}

View File

@@ -37,7 +37,7 @@ class IDF_Plugin_SyncSvn
// 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)) {
!Pluf::f('idf_plugin_syncsvn_svn_path', false)) {
return;
}
include_once 'File/Passwd/Authdigest.php'; // $ pear install File_Passwd
@@ -52,6 +52,12 @@ class IDF_Plugin_SyncSvn
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;
}
}
@@ -84,6 +90,31 @@ class IDF_Plugin_SyncSvn
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.
*
@@ -156,8 +187,10 @@ class IDF_Plugin_SyncSvn
* 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()
function SyncAccess($exclude=null)
{
$authz_file = Pluf::f('idf_plugin_syncsvn_authz_file');
$access_owners = Pluf::f('idf_plugin_syncsvn_access_owners', 'rw');
@@ -170,6 +203,9 @@ class IDF_Plugin_SyncSvn
}
$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
@@ -199,4 +235,25 @@ class IDF_Plugin_SyncSvn
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

@@ -230,7 +230,7 @@ class IDF_Precondition
return true; // no match in the hash, anonymous
}
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
list($userid, $projectid) = split(':', $cr->decrypt($encrypted), 2);
list($userid, $projectid) = explode(':', $cr->decrypt($encrypted), 2);
if ($projectid != $request->project->id) {
return true; // anonymous
}

View File

@@ -32,6 +32,14 @@ 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()
{
@@ -210,9 +218,9 @@ class IDF_Project extends Pluf_Model
$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]);
@@ -349,15 +357,53 @@ class IDF_Project extends Pluf_Model
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);
}
@@ -548,6 +594,28 @@ class IDF_Project extends Pluf_Model
*/
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',
);
@@ -557,4 +625,37 @@ class IDF_Project extends Pluf_Model
}
}
}
/**
* 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;
}
}

View File

@@ -32,6 +32,15 @@ Pluf::loadFunction('Pluf_Template_dateAgo');
*
* 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
{
@@ -71,12 +80,12 @@ class IDF_Review extends Pluf_Model
'verbose' => __('submitter'),
'relate_name' => 'submitted_review',
),
'reviewers' =>
'interested' =>
array(
'type' => 'Pluf_DB_Field_Manytomany',
'model' => 'Pluf_User',
'blank' => true,
'help_text' => 'Reviewers will get an email notification when the review is changed.',
'help_text' => 'Interested users will get an email notification when the review is changed.',
),
'tags' =>
array(
@@ -117,11 +126,25 @@ class IDF_Review extends Pluf_Model
'join_tags' =>
array(
'join' => 'LEFT JOIN '.$table
.' ON idf_issue_id=id',
.' 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;
@@ -140,7 +163,7 @@ class IDF_Review extends Pluf_Model
function preSave($create=false)
{
if ($this->id == '') {
if ($create) {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
}
$this->modif_dtime = gmdate('Y-m-d H:i:s');
@@ -148,6 +171,8 @@ class IDF_Review extends Pluf_Model
function postSave($create=false)
{
// At creation, we index after saving the associated patch.
if (!$create) IDF_Search::index($this);
}
/**

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

@@ -40,13 +40,13 @@ class IDF_Review_FileComment extends Pluf_Model
'type' => 'Pluf_DB_Field_Sequence',
'blank' => true,
),
'patch' =>
'comment' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Review_Patch',
'model' => 'IDF_Review_Comment',
'blank' => false,
'verbose' => __('patch'),
'relate_name' => 'filecomments',
'verbose' => __('comment'),
),
'cfile' =>
array(
@@ -55,32 +55,25 @@ class IDF_Review_FileComment extends Pluf_Model
'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'),
),
'submitter' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User',
'blank' => false,
'verbose' => __('submitter'),
'relate_name' => 'commented_patched_files',
),
'creation_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('creation date'),
),
);
$this->_a['idx'] = array(
'creation_dtime_idx' =>
array(
'col' => 'creation_dtime',
'type' => 'normal',
'index' => true,
),
);
}
@@ -92,12 +85,11 @@ class IDF_Review_FileComment extends Pluf_Model
function preDelete()
{
IDF_Timeline::remove($this);
}
function preSave($create=false)
{
if ($this->id == '') {
if ($create) {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
}
}

View File

@@ -24,6 +24,10 @@
/**
* 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
{
@@ -81,15 +85,28 @@ class IDF_Review_Patch extends Pluf_Model
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('creation date'),
'index' => true,
),
);
$this->_a['idx'] = array(
'creation_dtime_idx' =>
array(
'col' => 'creation_dtime',
'type' => 'normal',
),
);
}
/**
* 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()
@@ -99,25 +116,95 @@ class IDF_Review_Patch extends Pluf_Model
function preDelete()
{
IDF_Timeline::remove($this);
}
function preSave($create=false)
{
if ($this->id == '') {
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)
{
return '';
$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);
}
}

View File

@@ -71,14 +71,6 @@ class IDF_Scm
*/
protected $cache = array();
/**
* Default constructor.
*/
public function __construct($repo, $project=null)
{
$this->repo = $repo;
$this->project = $project;
}
/**
* Returns an instance of the correct scm backend object.
@@ -95,6 +87,52 @@ class IDF_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.
*
@@ -200,6 +238,24 @@ class IDF_Scm
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.
*
@@ -337,19 +393,27 @@ class IDF_Scm
* Sync the changes in the repository with the timeline.
*
*/
public static function syncTimeline($project)
public static function syncTimeline($project, $force=false)
{
$cache = Pluf_Cache::factory();
$key = 'IDF_Scm:'.$project->shortname.':lastsync';
if (null === ($res=$cache->get($key))) {
if ($force or null === ($res=$cache->get($key))) {
$scm = IDF_Scm::get($project);
foreach ($scm->getBranches() as $branche) {
foreach ($scm->getChangeLog($branche, 25) as $change) {
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));
}
}

View File

@@ -54,6 +54,7 @@ class IDF_Scm_Cache_Git extends Pluf_Model
$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));
@@ -83,7 +84,7 @@ class IDF_Scm_Cache_Git extends Pluf_Model
$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 = split(chr(31), $blob->content, 3);
$tmp = explode(chr(31), $blob->content, 3);
$res[$blob->githash] = (object) array(
'hash' => $blob->githash,

View File

@@ -35,14 +35,33 @@ class IDF_Scm_Git extends IDF_Scm
* *
* ============================================== */
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 {
$this->getBranches();
$branches = $this->getBranches();
} catch (IDF_Scm_Exception $e) {
return false;
}
return true;
return (count($branches) > 0);
}
public function getBranches()
@@ -53,7 +72,8 @@ class IDF_Scm_Git extends IDF_Scm
$cmd = Pluf::f('idf_exec_cmd_prefix', '')
.sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' branch',
escapeshellarg($this->repo));
exec($cmd, $out, $return);
self::exec('IDF_Scm_Git::getBranches',
$cmd, $out, $return);
if ($return != 0) {
throw new IDF_Scm_Exception(sprintf($this->error_tpl,
$cmd, $return,
@@ -61,7 +81,12 @@ class IDF_Scm_Git extends IDF_Scm
}
$res = array();
foreach ($out as $b) {
$res[substr($b, 2)] = '';
$b = substr($b, 2);
if (false !== strpos($b, '/')) {
$res[$this->getCommit($b)->commit] = $b;
} else {
$res[$b] = '';
}
}
$this->cache['branches'] = $res;
return $res;
@@ -69,14 +94,15 @@ class IDF_Scm_Git extends IDF_Scm
public function getMainBranch()
{
$possible = array('master', 'main', 'trunk', 'local');
$branches = array_keys($this->getBranches());
foreach ($possible as $p) {
if (in_array($p, $branches)) {
return $p;
$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 $branches[0];
return key($branches);
}
/**
@@ -87,10 +113,74 @@ class IDF_Scm_Git extends IDF_Scm
*/
public function inBranches($commit, $path)
{
return (in_array($commit, array_keys($this->getBranches())))
? array($commit) : array();
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.
@@ -120,7 +210,9 @@ class IDF_Scm_Git extends IDF_Scm
{
$folder = ($folder == '/') ? '' : $folder;
// now we grab the info about this commit including its tree.
$co = $this->getCommit($commit);
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.
@@ -143,6 +235,7 @@ class IDF_Scm_Git extends IDF_Scm
$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);
@@ -167,9 +260,14 @@ class IDF_Scm_Git extends IDF_Scm
if (!preg_match('/<(.*)>/', $author, $match)) {
return null;
}
$sql = new Pluf_SQL('email=%s', array($match[1]));
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()));
return ($users->count() > 0) ? $users[0] : null;
if ($users->count() > 0) {
return $users[0];
}
}
return null;
}
public static function getAnonymousAccessUrl($project)
@@ -197,14 +295,15 @@ class IDF_Scm_Git extends IDF_Scm
public function isValidRevision($commit)
{
return ('commit' == $this->testHash($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'
* @return mixed false if not valid or 'blob', 'tree', 'commit', 'tag'
*/
public function testHash($hash)
{
@@ -212,26 +311,12 @@ class IDF_Scm_Git extends IDF_Scm
escapeshellarg($this->repo),
escapeshellarg($hash));
$ret = 0; $out = array();
exec($cmd, $out, $ret);
$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]);
}
/**
* 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='')
{
}
/**
* Get the tree info.
*
@@ -242,7 +327,7 @@ class IDF_Scm_Git extends IDF_Scm
*/
public function getTreeInfo($tree, $folder='')
{
if (!in_array($this->testHash($tree), array('tree', 'commit'))) {
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';
@@ -251,7 +336,7 @@ class IDF_Scm_Git extends IDF_Scm
escapeshellarg($tree), escapeshellarg($folder));
$out = array();
$res = array();
exec($cmd, $out);
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,
@@ -261,7 +346,6 @@ class IDF_Scm_Git extends IDF_Scm
return $res;
}
/**
* Get the file info.
*
@@ -276,13 +360,16 @@ class IDF_Scm_Git extends IDF_Scm
escapeshellarg($this->repo),
escapeshellarg($commit));
$out = array();
exec($cmd, $out);
$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,
'file' => $file);
'fullpath' => $file,
'file' => $pathinfo['basename']);
}
}
return false;
@@ -294,10 +381,10 @@ class IDF_Scm_Git extends IDF_Scm
'GIT_DIR=%s '.Pluf::f('git_path', 'git').' cat-file blob %s',
escapeshellarg($this->repo),
escapeshellarg($def->hash));
return ($cmd_only) ? $cmd : shell_exec($cmd);
return ($cmd_only)
? $cmd : self::shell_exec('IDF_Scm_Git::getFile', $cmd);
}
/**
* Get commit details.
*
@@ -319,7 +406,12 @@ class IDF_Scm_Git extends IDF_Scm
escapeshellarg($commit));
}
$out = array();
exec($cmd, $out);
$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;
@@ -333,8 +425,12 @@ class IDF_Scm_Git extends IDF_Scm
$log[] = $line;
}
}
$out = self::parseLog($log, 4);
$out = self::parseLog($log);
$out[0]->changes = implode("\n", $change);
} else {
$out = self::parseLog($out);
$out[0]->changes = '';
}
return $out[0];
}
@@ -351,7 +447,8 @@ class IDF_Scm_Git extends IDF_Scm
"'commit %H%n'",
escapeshellarg($commit));
$out = array();
exec($cmd, $out);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Git::isCommitLarge', $cmd, $out);
$affected = count($out) - 2;
$added = 0;
$removed = 0;
@@ -383,28 +480,29 @@ class IDF_Scm_Git extends IDF_Scm
escapeshellarg($this->repo), $n, $this->mediumtree_fmt,
escapeshellarg($commit));
$out = array();
exec($cmd, $out);
return self::parseLog($out, 4);
$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.
* @param int Number of lines in the headers (3)
* @return array Change log.
*/
public static function parseLog($lines, $hdrs=3)
public static function parseLog($lines)
{
$res = array();
$c = array();
$hdrs += 2;
$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();
@@ -437,7 +535,9 @@ class IDF_Scm_Git extends IDF_Scm
continue;
}
}
$c['full_message'] = trim($c['full_message']);
$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;
}
@@ -543,7 +643,8 @@ class IDF_Scm_Git extends IDF_Scm
escapeshellarg($this->repo));
$skip = 0;
$res = array();
exec(sprintf($cmd, $skip), $rawlog);
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) {
@@ -572,7 +673,8 @@ class IDF_Scm_Git extends IDF_Scm
}
break;
}
exec(sprintf($cmd, $skip), $rawlog);
self::exec('IDF_Scm_Git::appendBlobInfoCache',
sprintf($cmd, $skip), $rawlog);
}
$this->cacheBlobInfo($res);
return $res;
@@ -590,7 +692,8 @@ class IDF_Scm_Git extends IDF_Scm
.sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log --raw --abbrev=40 --pretty=oneline -500 --skip=%%s',
escapeshellarg($this->repo));
$skip = 0;
exec(sprintf($cmd, $skip), $rawlog);
self::exec('IDF_Scm_Git::buildBlobInfoCache',
sprintf($cmd, $skip), $rawlog);
while (count($rawlog)) {
$commit = '';
$data = array();
@@ -608,7 +711,8 @@ class IDF_Scm_Git extends IDF_Scm
$this->cacheBlobInfo($data);
$rawlog = array();
$skip += 500;
exec(sprintf($cmd, $skip), $rawlog);
self::exec('IDF_Scm_Git::buildBlobInfoCache',
sprintf($cmd, $skip), $rawlog);
}
}
@@ -657,10 +761,10 @@ class IDF_Scm_Git extends IDF_Scm
if (false === $data) {
return $res;
}
$data = split(chr(30), $data);
$data = explode(chr(30), $data);
foreach ($data as $rec) {
if (isset($hashes[substr($rec, 0, 40)])) {
$tmp = split(chr(31), substr($rec, 40), 3);
$tmp = explode(chr(31), substr($rec, 40), 3);
$res[substr($rec, 0, 40)] =
(object) array('hash' => substr($rec, 0, 40),
'date' => $tmp[0],

View File

@@ -25,22 +25,41 @@
* Mercurial utils.
*
*/
class IDF_Scm_Mercurial
class IDF_Scm_Mercurial extends IDF_Scm
{
public $repo = '';
public function __construct($repo)
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);
}
/**
* 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.
@@ -53,27 +72,29 @@ class IDF_Scm_Mercurial
return ($users->count() > 0) ? $users[0] : null;
}
/**
* Returns the URL of the git daemon.
*
* @param IDF_Project
* @return string URL
*/
public static function getRemoteAccessUrl($project)
public function getMainBranch()
{
return 'tip';
}
public static function getAnonymousAccessUrl($project)
{
return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname);
}
/**
* Returns this object correctly initialized for the project.
*
* @param IDF_Project
* @return IDF_Scm_Git
*/
public static function factory($project)
public static function getAuthAccessUrl($project, $user)
{
$rep = sprintf(Pluf::f('mercurial_repositories'), $project->shortname);
return new IDF_Scm_Mercurial($rep);
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);
}
/**
@@ -90,24 +111,15 @@ class IDF_Scm_Mercurial
escapeshellarg($hash));
$ret = 0;
$out = array();
IDF_Scm::exec($cmd, $out, $ret);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::testHash', $cmd, $out, $ret);
return ($ret != 0) ? false : 'commit';
}
/**
* Given a commit hash returns an array of files in it.
*
* A file is a class with the following properties:
*
* 'perm', 'type', 'size', 'hash', 'file'
*
* @param string Commit ('HEAD')
* @param string Base folder ('')
* @return array
*/
public function filesAtCommit($commit='tip', $folder='')
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
@@ -143,29 +155,24 @@ class IDF_Scm_Mercurial
$cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo), $tree, ($recurse) ? '' : '');
$out = array();
$res = array();
IDF_Scm::exec($cmd, $out);
$out_hack = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::getTreeInfo', $cmd, $out);
$tmp_hack = array();
foreach ($out as $line) {
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; $i < count($dir); $i++) {
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_hack[] = "empty\t000\t\t$tmp/";
$out[] = "empty\t000\t\t$tmp/";
$tmp_hack["empty\t000\t\t$tmp/"] = 1;
}
}
$out_hack[] = "$hash\t$perm\t$exec\t$file";
}
foreach ($out_hack as $line) {
list($hash, $perm, $exec, $file) = preg_split('/ |\t/', $line, 4);
$file = trim($file);
if (preg_match('/^(.*)\/$/', $file, $match)) {
$type = 'tree';
$file = $match[1];
@@ -183,48 +190,47 @@ class IDF_Scm_Mercurial
continue;
}
}
$fullpath = ($folder) ? $folder.'/'.$file : $file;
$efullpath = self::smartEncode($fullpath);
$res[] = (object) array('perm' => $perm, 'type' => $type,
'hash' => $hash, 'fullpath' => ($folder) ? $folder.'/'.$file : $file,
'file' => $file);
'hash' => $hash, 'fullpath' => $fullpath,
'efullpath' => $efullpath, 'file' => $file);
}
return $res;
}
/**
* Get the file info.
*
* @param string Commit ('HEAD')
* @return false Information
*/
public function getFileInfo($totest, $commit='tip')
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();
$res = array();
IDF_Scm::exec($cmd, $out);
$out_hack = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::getPathInfo', $cmd, $out);
$tmp_hack = array();
foreach ($out as $line) {
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; $i < count($dir); $i++) {
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_hack[] = "empty\t000\t\t$tmp/";
$out[] = "empty\t000\t\t$tmp/";
$tmp_hack["empty\t000\t\t$tmp/"] = 1;
}
}
$out_hack[] = "$hash\t$perm\t$exec\t$file";
}
foreach ($out_hack as $line) {
list($hash, $perm, $exec, $file) = preg_split('/ |\t/', $line, 4);
$file = trim ($file);
if (preg_match('/^(.*)\/$/', $file, $match)) {
$type = 'tree';
$file = $match[1];
@@ -232,9 +238,11 @@ class IDF_Scm_Mercurial
$type = 'blob';
}
if ($totest == $file) {
$pathinfo = pathinfo($totest);
return (object) array('perm' => $perm, 'type' => $type,
'hash' => $hash,
'file' => $file,
'fullpath' => $totest,
'file' => $pathinfo['basename'],
'commit' => $commit
);
}
@@ -242,19 +250,15 @@ class IDF_Scm_Mercurial
return false;
}
/**
* Get a blob.
*
* @param string request_file_info
* @param null to be svn client compatible
* @return string Raw blob
*/
public function getBlob($request_file_info, $dummy=null)
public function getFile($def, $cmd_only=false)
{
return IDF_Scm::shell_exec(sprintf(Pluf::f('hg_path', 'hg').' cat -R %s -r %s %s',
$cmd = sprintf(Pluf::f('hg_path', 'hg').' cat -R %s -r %s %s',
escapeshellarg($this->repo),
$dummy,
escapeshellarg($this->repo . '/' . $request_file_info->file)));
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);
}
/**
@@ -264,17 +268,59 @@ class IDF_Scm_Mercurial
*/
public function getBranches()
{
if (isset($this->cache['branches'])) {
return $this->cache['branches'];
}
$out = array();
IDF_Scm::exec(sprintf(Pluf::f('hg_path', 'hg').' branches -R %s',
escapeshellarg($this->repo)), $out);
$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];
$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.
*
@@ -282,14 +328,18 @@ class IDF_Scm_Mercurial
* @param bool Get commit diff (false)
* @return array Changes
*/
public function getCommit($commit='tip', $getdiff=false)
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();
IDF_Scm::exec($cmd, $out);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::getCommit', $cmd, $out);
$log = array();
$change = array();
$inchange = false;
@@ -330,7 +380,8 @@ class IDF_Scm_Mercurial
{
$cmd = sprintf(Pluf::f('hg_path', 'hg').' log -R %s -l%s ', escapeshellarg($this->repo), $n, $commit);
$out = array();
IDF_Scm::exec($cmd, $out);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Mercurial::getChangeLog', $cmd, $out);
return self::parseLog($out, 6);
}
@@ -386,8 +437,8 @@ class IDF_Scm_Mercurial
continue;
}
}
$c['tree'] = $c['commit'];
$c['full_message'] = trim($c['full_message']);
$c['tree'] = !empty($c['commit']) ? trim($c['commit']) : '';
$c['full_message'] = !empty($c['full_message']) ? trim($c['full_message']) : '';
$res[] = (object) $c;
return $res;
}

View File

@@ -22,24 +22,28 @@
# ***** END LICENSE BLOCK ***** */
/**
* SVN utils.
* 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 $repo = '';
public $username = '';
public $password = '';
private $assoc = array('dir' => 'tree',
'file' => 'blob');
private $commit=array();
public function __construct($repo, $username='', $password='')
public function __construct($repo, $project=null)
{
$this->repo = $repo;
$this->username = $username;
$this->password = $password;
$this->project = $project;
$this->cache['commitmess'] = array();
}
public function isAvailable()
@@ -47,6 +51,17 @@ class IDF_Scm_Svn extends IDF_Scm
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.
@@ -78,6 +93,23 @@ class IDF_Scm_Svn extends IDF_Scm
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.
*
@@ -91,12 +123,13 @@ class IDF_Scm_Svn extends IDF_Scm
if (false !== ($rep=$conf->getVal('svn_remote_url', false))
&& !empty($rep)) {
// Remote repository
return new IDF_Scm_Svn($rep,
$conf->getVal('svn_username'),
$conf->getVal('svn_password'));
$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);
return new IDF_Scm_Svn($rep, $project);
}
}
@@ -113,7 +146,8 @@ class IDF_Scm_Svn extends IDF_Scm
escapeshellarg($this->password),
escapeshellarg($this->repo),
escapeshellarg($rev));
exec($cmd, $out, $ret);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Svn::isValidRevision', $cmd, $out, $ret);
return (0 == $ret);
}
@@ -135,9 +169,10 @@ class IDF_Scm_Svn extends IDF_Scm
$cmd = sprintf(Pluf::f('svn_path', 'svn').' info --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.$path),
escapeshellarg($this->repo.'/'.self::smartEncode($path)),
escapeshellarg($rev));
$xmlInfo = shell_exec($cmd);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$xmlInfo = self::shell_exec('IDF_Scm_Svn::testHash', $cmd);
// If exception is thrown, return false
try {
@@ -156,29 +191,27 @@ class IDF_Scm_Svn extends IDF_Scm
return 'commit';
}
public function getTree($rev='HEAD', $folder='')
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.'/'.$folder),
escapeshellarg($rev));
$xmlLs = shell_exec($cmd);
$xml = simplexml_load_string($xmlLs);
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)) ? $folder.'/' : '';
$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'];
// Get commit message
$currentReposFile = $this->repo.'/'.$folder.$file['file'];
$file['log'] = $this->getCommitMessage($currentReposFile, $rev);
$file['log'] = $this->getCommitMessage($file['rev']);
// Get the size if the type is blob
if ($file['type'] == 'blob') {
$file['size'] = (string) $entry->size;
@@ -192,66 +225,71 @@ class IDF_Scm_Svn extends IDF_Scm
/**
* Get a commit message for given file and revision.
* Get the commit message of a revision revision.
*
* @param string File
* @param string Commit ('HEAD')
*
* @return String commit message
*/
private function getCommitMessage($file, $rev='HEAD')
private function getCommitMessage($rev='HEAD')
{
if (isset($commit[$rev])) return $commit[$rev];
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($file),
escapeshellarg($this->repo),
escapeshellarg($rev));
$xmlLog = shell_exec($cmd);
$xml = simplexml_load_string($xmlLog);
$commit[$rev]=(string) $xml->logentry->msg;
return (string) $xml->logentry->msg;
$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];
}
/**
* FIXME: Need to check the case of an inexisting file.
*/
public function getPathInfo($totest, $rev='HEAD')
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.'/'.$totest),
escapeshellarg($this->repo.'/'.self::smartEncode($filename)),
escapeshellarg($rev));
$xmlInfo = shell_exec($cmd);
$xml = simplexml_load_string($xmlInfo);
$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'] = $totest;
$file['fullpath'] = $filename;
$file['hash'] = (string) $entry->repository->uuid;
$file['type'] = $this->assoc[(string) $entry['kind']];
$file['file'] = $totest;
$file['rev'] = (string) $entry->commit['revision'];
$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)
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.'/'.$def->fullpath),
escapeshellarg($this->repo.'/'.self::smartEncode($def->fullpath)),
escapeshellarg($def->rev));
return shell_exec($cmd);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
return ($cmd_only) ?
$cmd : self::shell_exec('IDF_Scm_Svn::getFile', $cmd);
}
/**
* Subversion branches are repository based.
* Subversion branches are folder based.
*
* One need to list the folder to know them.
*/
@@ -260,11 +298,13 @@ class IDF_Scm_Svn extends IDF_Scm
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'));
exec($cmd, $out, $ret);
$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) == '/') {
@@ -278,14 +318,45 @@ class IDF_Scm_Svn extends IDF_Scm
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/trunk'));
exec($cmd, $out, $ret);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
self::exec('IDF_Scm_Svn::getBranches', $cmd, $out, $ret);
if ($ret == 0) {
$res = array_merge(array('trunk' => 'trunk'), $res);
$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';
@@ -301,29 +372,43 @@ class IDF_Scm_Svn extends IDF_Scm
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 ('HEAD')
* @param string Commit
* @param bool Get commit diff (false)
* @return array Changes
*/
public function getCommit($rev='HEAD', $getdiff=false)
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($rev));
$xmlRes = shell_exec($cmd);
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($rev) : '';
$res['changes'] = ($getdiff) ? $this->getDiff($commit) : '';
$res['tree'] = '';
return (object) $res;
}
@@ -345,7 +430,8 @@ class IDF_Scm_Svn extends IDF_Scm
$cmd = sprintf(Pluf::f('svnlook_path', 'svnlook').' changed -r %s %s',
escapeshellarg($commit),
escapeshellarg($repo));
$out = shell_exec($cmd);
$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);
}
@@ -358,42 +444,45 @@ class IDF_Scm_Svn extends IDF_Scm
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo));
return shell_exec($cmd);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
return self::shell_exec('IDF_Scm_Svn::getDiff', $cmd);
}
/**
* Get latest changes.
*
* @param string Commit ('HEAD').
* @param string Revision or ('HEAD').
* @param int Number of changes (10).
*
* @return array Changes.
*/
public function getChangeLog($rev='HEAD', $n=10)
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($rev));
$xmlRes = shell_exec($cmd);
escapeshellarg($branch));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$xmlRes = self::shell_exec('IDF_Scm_Svn::getChangeLog', $cmd);
$xml = simplexml_load_string($xmlRes);
$res = array();
foreach ($xml->logentry as $entry) {
$log = array();
$log['author'] = (string) $entry->author;
$log['date'] = gmdate('Y-m-d H:i:s', strtotime((string) $entry->date));
$log['title'] = (string) $entry->msg;
$split = preg_split("[\n\r]", (string) $entry->msg, 2);
$log['title'] = $split[0];
$log['commit'] = (string) $entry['revision'];
$log['full_message'] = '';
$log['full_message'] = (isset($split[1])) ? trim($split[1]) : '';
$res[] = (object) $log;
}
return $res;
}
@@ -411,9 +500,10 @@ class IDF_Scm_Svn extends IDF_Scm
$cmd = sprintf(Pluf::f('svn_path', 'svn').' proplist --xml --username=%s --password=%s %s@%s',
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.$path),
escapeshellarg($this->repo.'/'.self::smartEncode($path)),
escapeshellarg($rev));
$xmlProps = shell_exec($cmd);
$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
@@ -446,9 +536,10 @@ class IDF_Scm_Svn extends IDF_Scm
escapeshellarg($property),
escapeshellarg($this->username),
escapeshellarg($this->password),
escapeshellarg($this->repo.'/'.$path),
escapeshellarg($this->repo.'/'.self::smartEncode($path)),
escapeshellarg($rev));
$xmlProp = shell_exec($cmd);
$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;
@@ -470,7 +561,8 @@ class IDF_Scm_Svn extends IDF_Scm
escapeshellarg($this->password),
escapeshellarg($this->repo),
escapeshellarg($rev));
$xmlInfo = shell_exec($cmd);
$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'];

View File

@@ -59,7 +59,7 @@ class IDF_Search extends Pluf_Search
$words_flat[] = $word;
}
$word_ids = self::getWordIds($words_flat);
if (in_array(null, $word_ids)) {
if (in_array(null, $word_ids) or count($word_ids) == 0) {
return array();
}
return self::mySearchDocuments($word_ids, $project, $model);
@@ -127,9 +127,21 @@ class IDF_Search extends Pluf_Search
if ($ids[$i] === null) {
$word = new Pluf_Search_Word();
$word->word = $words_flat[$i];
try {
$word->create();
$ids[$i] = $word->id;
$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;

View File

@@ -37,22 +37,26 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
$this->project = $request->project;
$this->request = $request;
$this->scm = IDF_Scm::get($request->project);
if ($wordwrap) $text = wordwrap($text, 69, "\n", true);
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('#(issues?|bugs?|tickets?)\s+(\d+)((\s+and|\s+or|,)\s+(\d+)){0,}#im',
$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\(\)]+)#im',
$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;
@@ -66,19 +70,30 @@ 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]);
$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]);
}
}
return $m[0]; // not existing issue.
}
} else {
return preg_replace_callback('/(\d+)/',
return preg_replace_callback('#(\#)?(\d+)(\#ic\d+)?#',
array($this, 'callbackIssue'),
$m[0]);
}
}
/**
* Call back for the case of multiple issues like 'issues 1, 2 and 3'.
@@ -88,12 +103,14 @@ 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]);
} else {
return $m[0]; // not existing issue.
$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.
}
/**
@@ -126,33 +143,61 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
*/
function callbackCommit($m)
{
try {
if ('commit' != $this->scm->testHash($m[0])) {
$co = $this->scm->getCommit($m[0]);
if (!$co) {
return $m[0]; // not a commit.
}
} catch (IDF_Scm_Exception $e) {
return $m[0]; // commit not found.
}
$co = $this->scm->getCommit($m[0]);
return '<a href="'
.Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit', array($this->project->shortname, $co->commit))
.'">'.$m[0].'</a>';
}
function callbackSource($m)
/**
* General call back to convert reviews to HTML links.
*
* @param array $m Single regex match.
* @return string Content with converted reviews.
*/
function callbackReviews($m)
{
$branches = $this->scm->getBranches();
if (count($branches) == 0) return $m[0];
$file = $m[2];
if ('commit' != $this->scm->testHash($branches[0], $file)) {
$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];
}
$request_file_info = $this->scm->getFileInfo($file, $branches[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 callbackSource($m)
{
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];
}
if ($request_file_info->type != 'tree') {
return $m[1].'<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Source::tree', array($this->project->shortname, $branches[0], $file)).'">'.$m[2].'</a>';
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];
}
@@ -164,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

@@ -39,8 +39,14 @@ class IDF_Template_Markdown extends Pluf_Template_Tag
// 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.
// if not the right to see the
$text = preg_replace_callback('#\[\[([A-Za-z0-9\-]+)\]\]#im',
array($this, 'callbackWikiPage'),
$text);
@@ -65,5 +71,25 @@ class IDF_Template_Markdown extends Pluf_Template_Tag
}
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

@@ -119,6 +119,7 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
'strong' => array(),
'table' => array('summary'),
'td' => array('style'),
'th' => array(),
'tr' => array(),
'ul' => array(),
);

View File

@@ -40,12 +40,48 @@ class IDF_Tests_TestGit extends UnitTestCase
}
public function testGitCache()
/**
* parse a log encoded in iso 8859-1
*/
public function testParseIsoLog()
{
$repo = substr(dirname(__FILE__), 0, -strlen('src/IDF/Tests')).'/.git';
$repo = '/home/loa/Vendors/linux-git/.git';
$git = new IDF_Scm_Git($repo);
$git->buildBlobInfoCache();
//$git->getCachedBlobInfo(array());
$log_lines = preg_split("/\015\012|\015|\012/", file_get_contents(dirname(__FILE__).'/data/git-log-iso-8859-1.txt'));
$log = IDF_Scm_Git::parseLog($log_lines);
$titles = array(
array('Quick Profiler entfernt', 'UTF-8'),
array('Anwendungsmenu Divider eingefügt', 'ISO-8859-1'),
array('Anwendungen aufäumen', 'ISO-8859-1'),
);
foreach ($log as $change) {
list($title, $senc) = array_shift($titles);
list($conv, $encoding) = IDF_Commit::toUTF8($change->title, true);
$this->assertEqual($title, $conv);
$this->assertEqual($senc, $encoding);
}
}
/**
* parse a log encoded in iso 8859-2
*/
public function testParseIsoLog2()
{
$log_lines = preg_split("/\015\012|\015|\012/", file_get_contents(dirname(__FILE__).'/data/git-log-iso-8859-2.txt'));
$log = IDF_Scm_Git::parseLog($log_lines);
$titles = array(
array('Doda³em model','ISO-8859-1'),
array('Doda³em model','ISO-8859-1'),
// The Good result is 'Dodałem model', the
// problem is that in that case, one cannot
// distinguish between latin1 and latin2. We
// will need to add a way for the project
// admin to set the priority between the
// encodings.
);
foreach ($log as $change) {
list($title, $senc) = array_shift($titles);
list($conv, $encoding) = IDF_Commit::toUTF8($change->title, true);
$this->assertEqual($title, $conv);
$this->assertEqual($senc, $encoding);
}
}
}

View File

@@ -0,0 +1,19 @@
commit 11531a9dbc64a65150f2f38fbea7cef9d478a123
Author: unknown <a@(none)>
Date: Fri Jul 3 01:44:11 2009 +0200
Quick Profiler entfernt
commit 11531a9dbc64a65150f2f38fbea7cef9d478a123
Author: unknown <a@(none)>
Date: Wed Jul 1 15:51:22 2009 +0200
Anwendungsmenu Divider eingef<65>gt
commit 11531a9dbc64a65150f2f38fbea7cef9d478a123
Author: unknown <a@(none)>
Date: Wed Jul 1 15:05:41 2009 +0200
Anwendungen auf<75>umen

View File

@@ -0,0 +1,12 @@
commit 11531a9dbc64a65150f2f38fbea7cef9d478a123
Author: unknown <a@(none)>
Date: Fri Jul 3 01:44:11 2009 +0200
Doda<64>em model
commit 11531a9dbc64a65150f2f38fbea7cef9d478a123
Author: unknown <a@(none)>
Date: Fri Jul 3 01:44:11 2009 +0200
Doda<64>em model

View File

@@ -128,7 +128,7 @@ class IDF_Timeline extends Pluf_Model
$t->model_id = $item->id;
$t->model_class = $item->_model;
$t->create();
return true;
return $t;
}
/**

View File

@@ -56,6 +56,12 @@ class IDF_Upload extends Pluf_Model
'size' => 250,
'verbose' => __('summary'),
),
'changelog' =>
array(
'type' => 'Pluf_DB_Field_Text',
'blank' => true,
'verbose' => __('changes'),
),
'file' =>
array(
'type' => 'Pluf_DB_Field_File',
@@ -183,33 +189,62 @@ class IDF_Upload extends Pluf_Model
$out .= sprintf(__('<a href="%1$s" title="View download">Download %2$d</a>, %3$s'), $url, $this->id, Pluf_esc($this->summary)).'</td>';
$out .= '</tr>';
$out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Addition of <a href="%s">download&nbsp;%d</a>'), $url, $this->id).', '.__('by').' '.$user.'</div></td></tr>';
<div class="helptext right">'.sprintf(__('Addition of <a href="%s">download&nbsp;%d</a>, by %s'), $url, $this->id, $user).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
public function feedFragment($request)
{
$base = '<entry>
<title>%%title%%</title>
<link href="%%url%%"/>
<id>%%url%%</id>
<updated>%%date%%</updated>
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
%%content%%
</div></content>
</entry>';
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::view',
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Download::view',
array($request->project->shortname,
$this->id));
$title = sprintf(__('%s: Download %d added - %s'),
Pluf_esc($request->project->name),
$this->id, Pluf_esc($this->summary));
$content = Pluf_esc($this->summary);
$request->project->name,
$this->id, $this->summary);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
return Pluf_Translation::sprintf($base,
$context = new Pluf_Template_Context_Request(
$request,
array('url' => $url,
'title' => $title,
'content' => $content,
'date' => $date));
'file' => $this,
'date' => $date)
);
$tmpl = new Pluf_Template('idf/downloads/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('downloads_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('file' => $this,
'urlfile' => $this->getAbsoluteUrl($this->get_project()),
'project' => $this->get_project(),
'tags' => $this->get_tags_list(),
));
$tmpl = new Pluf_Template('idf/downloads/download-created-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail(Pluf::f('from_email'),
$conf->getVal('downloads_notification_email'),
sprintf(__('New download - %s (%s)'),
$this->summary,
$this->get_project()->shortname));
$email->addTextMessage($text_email);
$email->sendMail();
Pluf_Translation::loadSetLocale($current_locale);
}
}

View File

@@ -183,6 +183,8 @@ class IDF_Views
* email is available in the database, send an email with a key to
* reset the password.
*
* If the user is not yet confirmed, send the confirmation key one
* more time.
*/
function passwordRecoveryAsk($request, $match)
{
@@ -190,8 +192,7 @@ class IDF_Views
if ($request->method == 'POST') {
$form = new IDF_Form_Password($request->POST);
if ($form->isValid()) {
$form->save();
$url = Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecoveryInputCode');
$url = $form->save();
return new Pluf_HTTP_Response_Redirect($url);
}
} else {

View File

@@ -39,7 +39,7 @@ class IDF_Views_Admin
public $home_precond = array('Pluf_Precondition::staffRequired');
public function home($request, $match)
{
$title = __('Administer');
$title = __('Forge Management');
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/home.html',
array(
'page_title' => $title,
@@ -64,9 +64,11 @@ class IDF_Views_Admin
$list_display = array(
'shortname' => __('Short Name'),
'name' => __('Name'),
array('id', 'IDF_Views_Admin_projectSize', __('Repository Size')),
);
$pag->configure($list_display, array(),
array('shortname'));
$pag->extra_classes = array('', '', 'right');
$pag->items_per_page = 25;
$pag->no_results_text = __('No projects were found.');
$pag->setFromRequest($request);
@@ -74,6 +76,7 @@ class IDF_Views_Admin
array(
'page_title' => $title,
'projects' => $pag,
'size' => IDF_Views_Admin_getForgeSize(),
),
$request);
}
@@ -189,13 +192,14 @@ class IDF_Views_Admin
if ($not_validated) {
$pag->forced_where = new Pluf_SQL('first_name = \'---\' AND active!='.$true);
$title = __('Not Validated User List');
$pag->action = 'IDF_Views_Admin::usersNotValidated';
} else {
$pag->forced_where = new Pluf_SQL('first_name != \'---\'');
$title = __('User List');
$pag->action = 'IDF_Views_Admin::users';
}
$pag->class = 'recent-issues';
$pag->summary = __('This table shows the users in the forge.');
$pag->action = 'IDF_Views_Admin::users';
$pag->edit_action = array('IDF_Views_Admin::userUpdate', 'id');
$pag->sort_order = array('login', 'ASC');
$list_display = array(
@@ -207,7 +211,9 @@ class IDF_Views_Admin
array('last_login', 'Pluf_Paginator_DateYMDHM', __('Last Login')),
);
$pag->extra_classes = array('', '', 'a-c', 'a-c', 'a-c', 'a-c');
$pag->configure($list_display, array(), array('login', 'last_login'));
$pag->configure($list_display,
array('login', 'last_name', 'email'),
array('login', 'last_login'));
$pag->items_per_page = 50;
$pag->no_results_text = __('No users were found.');
$pag->setFromRequest($request);
@@ -276,6 +282,38 @@ class IDF_Views_Admin
),
$request);
}
/**
* Create a new user.
*
* Only staff can add a user. The user can be added together with
* a public ssh key.
*/
public $userCreate_precond = array('Pluf_Precondition::staffRequired');
public function userCreate($request, $match)
{
$params = array(
'request' => $request,
);
if ($request->method == 'POST') {
$form = new IDF_Form_Admin_UserCreate($request->POST, $params);
if ($form->isValid()) {
$cuser = $form->save();
$request->user->setMessage(sprintf(__('The user %s has been created.'), (string) $cuser));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::users');
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$form = new IDF_Form_Admin_UserCreate(null, $params);
}
$title = __('Add User');
return Pluf_Shortcuts_RenderToResponse('idf/gadmin/users/create.html',
array(
'page_title' => $title,
'form' => $form,
),
$request);
}
}
function IDF_Views_Admin_bool($field, $item)
@@ -284,3 +322,89 @@ function IDF_Views_Admin_bool($field, $item)
$text = ($item->$field) ? __('Yes') : __('No');
return sprintf('<img src="'.Pluf::f('url_media').'/idf/img/%s.png" alt="%s" /> ', $img, $text);
}
/**
* Display the size of the project.
*
* @param string Field
* @param IDF_Project
* @return string
*/
function IDF_Views_Admin_projectSize($field, $project)
{
$size = $project->getRepositorySize();
if ($size == -1) {
return '';
}
return Pluf_Utils::prettySize($size);
}
/**
* Get a forge size.
*
* @return array Associative array with the size of each element
*/
function IDF_Views_Admin_getForgeSize()
{
$res = array();
$res['repositories'] = 0;
foreach (Pluf::factory('IDF_Project')->getList() as $prj) {
$size = $prj->getRepositorySize();
if ($size != -1) {
$res['repositories'] += $size;
}
}
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
.escapeshellarg(Pluf::f('upload_path'));
$out = explode(' ', shell_exec($cmd), 2);
$res['downloads'] = $out[0]*1024;
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
.escapeshellarg(Pluf::f('upload_issue_path'));
$out = explode(' ', shell_exec($cmd), 2);
$res['attachments'] = $out[0]*1024;
$res['database'] = IDF_Views_Admin_getForgeDbSize();
$res['total'] = $res['repositories'] + $res['downloads'] + $res['attachments'] + $res['database'];
return $res;
}
/**
* Get the database size as given by the database.
*
* @return int Database size
*/
function IDF_Views_Admin_getForgeDbSize()
{
$db = Pluf::db();
if (Pluf::f('db_engine') == 'SQLite') {
return filesize(Pluf::f('db_database'));
}
switch (Pluf::f('db_engine')) {
case 'PostgreSQL':
$sql = 'SELECT relname, pg_total_relation_size(CAST(relname AS
TEXT)) AS size FROM pg_class AS pgc, pg_namespace AS pgn
WHERE pg_table_is_visible(pgc.oid) IS TRUE AND relkind = \'r\'
AND pgc.relnamespace = pgn.oid
AND pgn.nspname NOT IN (\'information_schema\', \'pg_catalog\')';
break;
case 'MySQL':
default:
$sql = 'SHOW TABLE STATUS FROM `'.Pluf::f('db_database').'`';
break;
}
$rs = $db->select($sql);
$total = 0;
switch (Pluf::f('db_engine')) {
case 'PostgreSQL':
foreach ($rs as $table) {
$total += $table['size'];
}
break;
case 'MySQL':
default:
foreach ($rs as $table) {
$total += $table['Data_length'] + $table['Index_length'];
}
break;
}
return $total;
}

View File

@@ -231,7 +231,7 @@ class IDF_Views_Download
foreach ($st as $s) {
$v = '';
$d = '';
$_s = split('=', $s, 2);
$_s = explode('=', $s, 2);
if (count($_s) > 1) {
$v = trim($_s[0]);
$d = trim($_s[1]);

View File

@@ -55,6 +55,7 @@ class IDF_Views_Issue
$pag->action = array('IDF_Views_Issue::index', array($prj->shortname));
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
$pag->sort_reverse_order = array('modif_dtime');
$pag->sort_link_title = true;
$pag->extra_classes = array('a-c', '', 'a-c', '');
$list_display = array(
'id' => __('Id'),
@@ -62,7 +63,7 @@ class IDF_Views_Issue
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
);
$pag->configure($list_display, array(), array('status', 'modif_dtime'));
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
$pag->items_per_page = 10;
$pag->no_results_text = __('No issues were found.');
$pag->setFromRequest($request);
@@ -131,6 +132,7 @@ class IDF_Views_Issue
$pag->action = array('IDF_Views_Issue::myIssues', array($prj->shortname, $match[2]));
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
$pag->sort_reverse_order = array('modif_dtime');
$pag->sort_link_title = true;
$pag->extra_classes = array('a-c', '', 'a-c', '');
$list_display = array(
'id' => __('Id'),
@@ -138,7 +140,7 @@ class IDF_Views_Issue
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
);
$pag->configure($list_display, array(), array('status', 'modif_dtime'));
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
$pag->items_per_page = 10;
$pag->no_results_text = __('No issues were found.');
$pag->setFromRequest($request);
@@ -171,36 +173,11 @@ class IDF_Views_Issue
$params);
if (!isset($request->POST['preview']) and $form->isValid()) {
$issue = $form->save();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
array($prj->shortname));
$urlissue = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($prj->shortname, $issue->id));
$request->user->setMessage(sprintf(__('<a href="%s">Issue %d</a> has been created.'), $urlissue, $issue->id));
$to_emails = array();
if (null != $issue->get_owner() and $issue->owner != $issue->submitter) {
$to_emails[] = $issue->get_owner()->email;
}
if ('' != $request->conf->getVal('issues_notification_email', '')) {
$to_emails[] = $request->conf->getVal('issues_notification_email', '');
}
foreach ($to_emails as $oemail) {
$comments = $issue->get_comments_list(array('order' => 'id ASC'));
$context = new Pluf_Template_Context(
array(
'issue' => $issue,
'comment' => $comments[0],
'project' => $prj,
'url_base' => Pluf::f('url_base'),
)
);
$email = new Pluf_Mail(Pluf::f('from_email'), $oemail,
sprintf(__('Issue %s - %s (%s)'),
$issue->id, $issue->summary, $prj->shortname));
$tmpl = new Pluf_Template('idf/issues/issue-created-email.txt');
$email->addTextMessage($tmpl->render($context));
$email->sendMail();
}
$issue->notify($request->conf);
if ($api) return $issue;
$request->user->setMessage(sprintf(__('<a href="%s">Issue %d</a> has been created.'), $url, $issue->id));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
@@ -290,50 +267,12 @@ class IDF_Views_Issue
$request->FILES),
$params);
if (!isset($request->POST['preview']) && $form->isValid()) {
$issue = $form->save();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
array($prj->shortname));
$urlissue = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($prj->shortname, $issue->id));
$request->user->setMessage(sprintf(__('<a href="%s">Issue %d</a> has been updated.'), $urlissue, $issue->id));
// Get the list of interested person + owner + submitter
if (!Pluf_Model_InArray($issue->get_submitter(), $interested)) {
$interested[] = $issue->get_submitter();
}
if (null != $issue->get_owner() and
!Pluf_Model_InArray($issue->get_owner(), $interested)) {
$interested[] = $issue->get_owner();
}
$issue = $form->save(); // Note, should return the
// last comment
$issue->notify($request->conf, false);
$comments = $issue->get_comments_list(array('order' => 'id DESC'));
$context = new Pluf_Template_Context(
array(
'issue' => $issue,
'comments' => $comments,
'project' => $prj,
'url_base' => Pluf::f('url_base'),
)
);
$tmpl = new Pluf_Template('idf/issues/issue-updated-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail_Batch(Pluf::f('from_email'));
$to_emails = array();
foreach ($interested as $user) {
if ($user->id != $request->user->id) {
$to_emails[] = $user->email;
}
}
if ('' != $request->conf->getVal('issues_notification_email', '')) {
$to_emails[] = $request->conf->getVal('issues_notification_email');
}
foreach ($to_emails as $oemail) {
$email->setSubject(sprintf(__('Updated Issue %s - %s (%s)'),
$issue->id, $issue->summary, $prj->shortname));
$email->setTo($oemail);
$email->setReturnPath(Pluf::f('from_email'));
$email->addTextMessage($text_email);
$email->sendMail();
}
$email->close();
$url .= '#ic' . $comments[0]->id;
$request->user->setMessage(sprintf(__('<a href="%s">Issue %d</a> has been updated.'), $url, $issue->id));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
@@ -436,6 +375,7 @@ class IDF_Views_Issue
$pag->action = array('IDF_Views_Issue::listStatus', array($prj->shortname, $status));
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
$pag->sort_reverse_order = array('modif_dtime');
$pag->sort_link_title = true;
$pag->extra_classes = array('a-c', '', 'a-c', '');
$list_display = array(
'id' => __('Id'),
@@ -443,7 +383,7 @@ class IDF_Views_Issue
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
);
$pag->configure($list_display, array(), array('status', 'modif_dtime'));
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
$pag->items_per_page = 10;
$pag->no_results_text = __('No issues were found.');
$pag->setFromRequest($request);
@@ -494,6 +434,7 @@ class IDF_Views_Issue
$pag->action = array('IDF_Views_Issue::listLabel', array($prj->shortname, $tag->id, $status));
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
$pag->sort_reverse_order = array('modif_dtime');
$pag->sort_link_title = true;
$pag->extra_classes = array('a-c', '', 'a-c', '');
$list_display = array(
'id' => __('Id'),
@@ -501,7 +442,7 @@ class IDF_Views_Issue
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
);
$pag->configure($list_display, array(), array('status', 'modif_dtime'));
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
$pag->items_per_page = 10;
$pag->no_results_text = __('No issues were found.');
$pag->setFromRequest($request);
@@ -565,7 +506,7 @@ class IDF_Views_Issue
foreach ($st as $s) {
$v = '';
$d = '';
$_s = split('=', $s, 2);
$_s = explode('=', $s, 2);
if (count($_s) > 1) {
$v = trim($_s[0]);
$d = trim($_s[1]);

View File

@@ -92,6 +92,10 @@ class IDF_Views_Project
$rights[] = '\'IDF_WikiPage\'';
$rights[] = '\'IDF_WikiRevision\'';
}
if (true === IDF_Precondition::accessReview($request)) {
$rights[] = '\'IDF_Review_Comment\'';
$rights[] = '\'IDF_Review_Patch\'';
}
if (count($rights) == 0) {
$rights[] = '\'IDF_Dummy\'';
}
@@ -120,7 +124,7 @@ class IDF_Views_Project
$tags = IDF_Views_Wiki::getWikiTags($prj);
$pages = $tags[0]->get_idf_wikipage_list();
}
if (!$request->user->isAnonymous()) {
if (!$request->user->isAnonymous() and $prj->isRestricted()) {
$feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed_auth',
array($prj->shortname,
IDF_Precondition::genFeedToken($prj, $request->user)));
@@ -169,6 +173,10 @@ class IDF_Views_Project
$rights[] = '\'IDF_WikiPage\'';
$rights[] = '\'IDF_WikiRevision\'';
}
if (true === IDF_Precondition::accessReview($request)) {
$rights[] = '\'IDF_Review_Comment\'';
$rights[] = '\'IDF_Review_Patch\'';
}
if (count($rights) == 0) {
$rights[] = '\'IDF_Dummy\'';
}
@@ -177,15 +185,22 @@ class IDF_Views_Project
$params = array(
'filter' => $sql->gen(),
'order' => 'creation_dtime DESC',
'nb' => 50,
'nb' => 20,
);
$items = Pluf::factory('IDF_Timeline')->getList($params);
$set = new Pluf_Model_Set($items,
array('public_dtime' => 'public_dtime'));
$out = array();
foreach ($set as $item) {
if ($item->id) {
$out[] = $item->feedFragment($request);
}
}
if ($items->count() > 0) {
$date = Pluf_Date::gmDateToGmString($items[0]->creation_dtime);
} else {
$date = gmdate('c');
}
$out = Pluf_Template::markSafe(implode("\n", $out));
$tmpl = new Pluf_Template('idf/index.atom');
$title = __('Updates');
@@ -194,6 +209,7 @@ class IDF_Views_Project
array($prj->shortname));
$context = new Pluf_Template_Context_Request($request,
array('body' => $out,
'date' => $date,
'title' => $title,
'feedurl' => $feedurl,
'viewurl' => $viewurl));
@@ -342,7 +358,7 @@ class IDF_Views_Project
$conf->setVal($key, $val);
}
$request->user->setMessage(__('The documentation configuration has been saved.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminDownloads',
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminWiki',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
@@ -510,6 +526,7 @@ class IDF_Views_Project
'remote_svn' => $remote_svn,
'repository_access' => $prj->getRemoteAccessUrl(),
'repository_type' => $repository_type,
'repository_size' => $prj->getRepositorySize(),
'page_title' => $title,
'form' => $form,
),

View File

@@ -150,72 +150,39 @@ class IDF_Views_Review
array('files' => $diff->files,
'user' => $request->user,
'patch' => $patch,
'project' => $prj,
));
if ($form->isValid()) {
$patch = $form->save();
$review_comment = $form->save();
$review = $patch->get_review();
$urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view',
array($prj->shortname, $review->id));
$request->user->setMessage(sprintf(__('Your <a href="%s">code review %d</a> has been published.'), $urlr, $review->id));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index',
array($prj->shortname));
// Get the list of reviewers + submitter
$reviewers = $review->get_reviewers_list();
if (!Pluf_Model_InArray($review->get_submitter(), $reviewers)) {
$reviewers[] = $review->get_submitter();
}
$comments = $patch->get_filecomments_list(array('order' => 'id DESC'));
$context = new Pluf_Template_Context(
array(
'review' => $review,
'patch' => $patch,
'comments' => $comments,
'project' => $prj,
'url_base' => Pluf::f('url_base'),
)
);
$tmpl = new Pluf_Template('idf/review/review-updated-email.txt');
$text_email = $tmpl->render($context);
$email = new Pluf_Mail_Batch(Pluf::f('from_email'));
$to_emails = array();
foreach ($reviewers as $user) {
if ($user->id != $request->user->id) {
$to_emails[] = $user->email;
}
}
if ('' != $request->conf->getVal('review_notification_email', '')) {
$to_emails[] = $request->conf->getVal('review_notification_email');
}
foreach ($to_emails as $oemail) {
$email->setSubject(sprintf(__('Updated Code Review %s - %s (%s)'),
$review->id, $review->summary, $prj->shortname));
$email->setTo($oemail);
$email->setReturnPath(Pluf::f('from_email'));
$email->addTextMessage($text_email);
$email->sendMail();
}
$email->close();
$review_comment->notify($request->conf);
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$form = new IDF_Form_ReviewFileComment(null,
array('files' => $diff->files,
'user' => $request->user,
'project' => $prj,
'patch' => $patch,));
}
$scm = IDF_Scm::get($request->project);
$files = array();
$reviewers = array();
foreach ($diff->files as $filename => $def) {
$fileinfo = $scm->getFileInfo($filename, $patch->get_commit()->scm_id);
$fileinfo = $scm->getPathInfo($filename, $patch->get_commit()->scm_id);
$sql = new Pluf_SQL('cfile=%s', array($filename));
$cts = $patch->get_filecomments_list(array('filter'=>$sql->gen(),
$cts = $patch->getFileComments(array('filter'=>$sql->gen(),
'order'=>'creation_dtime ASC'));
foreach ($cts as $ct) {
$reviewers[] = $ct->get_submitter();
$reviewers[] = $ct->get_comment()->get_submitter();
}
if (count($def['chunks'])) {
$orig_file = ($fileinfo) ? $scm->getBlob($fileinfo) : '';
$orig_file = ($fileinfo) ? $scm->getFile($fileinfo) : '';
$files[$filename] = array(
$diff->fileCompare($orig_file, $def, $filename),
$form->f->{md5($filename)},
@@ -227,15 +194,19 @@ class IDF_Views_Review
}
$reviewers = Pluf_Model_RemoveDuplicates($reviewers);
return Pluf_Shortcuts_RenderToResponse('idf/review/view.html',
array_merge(
array(
'page_title' => $title,
'review' => $review,
'files' => $files,
'diff' => $diff,
'patch' => $patch,
'comments' => $patch->get_comments_list(array('sort' => 'id ASC')),
'form' => $form,
'reviewers' => $reviewers,
),
IDF_Views_Issue::autoCompleteArrays($prj)
),
$request);
}
}

View File

@@ -34,11 +34,12 @@ class IDF_Views_Source
/**
* Extension supported by the syntax highlighter.
*/
public static $supportedExtenstions = array('c', 'cc', 'cpp', 'cs', 'css',
'cyc', 'java', 'bsh', 'csh',
'sh', 'cv', 'py', 'perl', 'php',
'pl', 'pm', 'rb', 'js', 'html',
'html', 'xhtml', 'xml', 'xsl');
public static $supportedExtenstions = array(
'ascx', 'ashx', 'asmx', 'aspx', 'browser', 'bsh', 'c', 'cc',
'config', 'cpp', 'cs', 'csh', 'csproj', 'css', 'cv', 'cyc',
'html', 'html', 'java', 'js', 'master', 'perl', 'php', 'pl',
'pm', 'py', 'rb', 'sh', 'sitemap', 'skin', 'sln', 'svc', 'vala',
'vb', 'vbproj', 'wsdl', 'xhtml', 'xml', 'xsd', 'xsl', 'xslt');
/**
* Display help on how to checkout etc.
@@ -61,9 +62,12 @@ class IDF_Views_Source
public $changeLog_precond = array('IDF_Precondition::accessSource');
public function changeLog($request, $match)
{
$title = sprintf(__('%1$s %2$s Change Log'), (string) $request->project,
$this->getScmType($request));
$scm = IDF_Scm::get($request->project);
if (!$scm->isAvailable()) {
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help',
array($request->project->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
$branches = $scm->getBranches();
$commit = $match[2];
if (!$scm->isValidRevision($commit)) {
@@ -76,9 +80,11 @@ class IDF_Views_Source
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::changeLog',
array($request->project->shortname,
$branches[0]));
$scm->getMainBranch()));
return new Pluf_HTTP_Response_Redirect($url);
}
$title = sprintf(__('%1$s %2$s Change Log'), (string) $request->project,
$this->getScmType($request));
$changes = $scm->getChangeLog($commit, 25);
$rchanges = array();
// Sync with the database
@@ -87,13 +93,19 @@ class IDF_Views_Source
}
$rchanges = new Pluf_Template_ContextVars($rchanges);
$scmConf = $request->conf->getVal('scm', 'git');
return Pluf_Shortcuts_RenderToResponse('idf/source/changelog.html',
$in_branches = $scm->inBranches($commit, '');
$tags = $scm->getTags();
$in_tags = $scm->inTags($commit, '');
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/changelog.html',
array(
'page_title' => $title,
'title' => $title,
'changes' => $rchanges,
'commit' => $commit,
'branches' => $branches,
'tree_in' => $in_branches,
'tags' => $tags,
'tags_in' => $in_tags,
'scm' => $scmConf,
),
$request);
@@ -102,8 +114,6 @@ class IDF_Views_Source
public $treeBase_precond = array('IDF_Precondition::accessSource');
public function treeBase($request, $match)
{
$title = sprintf(__('%1$s %2$s Source Tree'),
$request->project, $this->getScmType($request));
$scm = IDF_Scm::get($request->project);
if (!$scm->isAvailable()) {
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help',
@@ -118,8 +128,12 @@ class IDF_Views_Source
$scm->getMainBranch()));
return new Pluf_HTTP_Response_Redirect($url);
}
$title = sprintf(__('%1$s %2$s Source Tree'),
$request->project, $this->getScmType($request));
$branches = $scm->getBranches();
$in_branches = $scm->inBranches($commit, '');
$tags = $scm->getTags();
$in_tags = $scm->inTags($commit, '');
$cache = Pluf_Cache::factory();
$key = sprintf('Project:%s::IDF_Views_Source::treeBase:%s::',
$request->project->id, $commit);
@@ -127,7 +141,6 @@ class IDF_Views_Source
$res = new Pluf_Template_ContextVars($scm->getTree($commit));
$cache->set($key, $res);
}
//$tree_in = in_array($commit, $branches);
$scmConf = $request->conf->getVal('scm', 'git');
$props = $scm->getProperties($commit);
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/tree.html',
@@ -139,6 +152,8 @@ class IDF_Views_Source
'commit' => $commit,
'tree_in' => $in_branches,
'branches' => $branches,
'tags' => $tags,
'tags_in' => $in_tags,
'props' => $props,
),
$request);
@@ -147,22 +162,18 @@ class IDF_Views_Source
public $tree_precond = array('IDF_Precondition::accessSource');
public function tree($request, $match)
{
$title = sprintf(__('%1$s %2$s Source Tree'),
$request->project, $this->getScmType($request));
$scm = IDF_Scm::get($request->project);
$commit = $match[2];
if (!$scm->isAvailable()) {
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help',
array($request->project->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
$branches = $scm->getBranches();
$commit = $match[2];
$request_file = $match[3];
$fburl = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$scm->getMainBranch()));
$request_file = $match[3];
if (substr($request_file, -1) == '/') {
$request_file = substr($request_file, 0, -1);
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::tree',
@@ -170,23 +181,21 @@ class IDF_Views_Source
$request_file));
return new Pluf_HTTP_Response_Redirect($url, 301);
}
if (!$scm->isValidRevision($commit, $request_file)) {
// Redirect to the first branch
return new Pluf_HTTP_Response_Redirect($fburl);
}
$request_file_info = $scm->getPathInfo($request_file, $commit);
if (!$request_file_info) {
// Redirect to the first branch
return new Pluf_HTTP_Response_Redirect($fburl);
}
$branches = $scm->getBranches();
$tags = $scm->getTags();
if ($request_file_info->type != 'tree') {
$info = self::getRequestedFileMimeType($request_file_info,
$commit, $scm);
if (!self::isText($info)) {
$rep = new Pluf_HTTP_Response($scm->getFile($request_file_info),
$info[0]);
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$info[1].'"';
@@ -194,6 +203,7 @@ class IDF_Views_Source
} else {
// We want to display the content of the file as text
$extra = array('branches' => $branches,
'tags' => $tags,
'commit' => $commit,
'request_file' => $request_file,
'request_file_info' => $request_file_info,
@@ -203,12 +213,21 @@ class IDF_Views_Source
}
}
$bc = self::makeBreadCrumb($request->project, $commit, $request_file_info->file);
$bc = self::makeBreadCrumb($request->project, $commit, $request_file_info->fullpath);
$title = sprintf(__('%1$s %2$s Source Tree'),
$request->project, $this->getScmType($request));
$page_title = $bc.' - '.$title;
$cobject = $scm->getCommit($commit);
if (!$cobject) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$scm->getMainBranch()));
return new Pluf_HTTP_Response_Redirect($url);
}
$in_branches = $scm->inBranches($commit, $request_file);
try {
$in_tags = $scm->inTags($commit, $request_file);
$cache = Pluf_Cache::factory();
$key = sprintf('Project:%s::IDF_Views_Source::tree:%s::%s',
$request->project->id, $commit, $request_file);
@@ -216,19 +235,12 @@ class IDF_Views_Source
$res = new Pluf_Template_ContextVars($scm->getTree($commit, $request_file));
$cache->set($key, $res);
}
} catch (Exception $e) {
throw $e;
}
// try to find the previous level if it exists.
$prev = split('/', $request_file);
$prev = explode('/', $request_file);
$l = array_pop($prev);
$previous = substr($request_file, 0, -strlen($l.' '));
$scmConf = $request->conf->getVal('scm', 'git');
$props = null;
if ($scmConf === 'svn') {
$props = $scm->getProperties($commit, $request_file);
}
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/tree.html',
array(
'page_title' => $page_title,
@@ -241,6 +253,8 @@ class IDF_Views_Source
'prev' => $previous,
'tree_in' => $in_branches,
'branches' => $branches,
'tags' => $tags,
'tags_in' => $in_tags,
'props' => $props,
),
$request);
@@ -248,12 +262,12 @@ class IDF_Views_Source
public static function makeBreadCrumb($project, $commit, $file, $sep='/')
{
$elts = split('/', $file);
$elts = explode('/', $file);
$out = array();
$stack = '';
$i = 0;
foreach ($elts as $elt) {
$stack .= ($i==0) ? $elt : '/'.$elt;
$stack .= ($i==0) ? rawurlencode($elt) : '/'.rawurlencode($elt);
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::tree',
array($project->shortname,
$commit, $stack));
@@ -268,22 +282,32 @@ class IDF_Views_Source
{
$scm = IDF_Scm::get($request->project);
$commit = $match[2];
$branches = $scm->getBranches();
if (!$scm->isValidRevision($commit)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$branches[0]));
$scm->getMainBranch()));
return new Pluf_HTTP_Response_Redirect($url);
}
$large = $scm->isCommitLarge($commit);
$cobject = $scm->getCommit($commit, !$large);
if (!$cobject) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$scm->getMainBranch()));
return new Pluf_HTTP_Response_Redirect($url);
}
$title = sprintf(__('%s Commit Details'), (string) $request->project);
$page_title = sprintf(__('%s Commit Details - %s'), (string) $request->project, $commit);
$large = $scm->isCommitLarge($commit);
$cobject = $scm->getCommit($commit, !$large);
$rcommit = IDF_Commit::getOrAdd($cobject, $request->project);
$diff = new IDF_Diff($cobject->changes);
$diff->parse();
$scmConf = $request->conf->getVal('scm', 'git');
$branches = $scm->getBranches();
$in_branches = $scm->inBranches($cobject->commit, '');
$tags = $scm->getTags();
$in_tags = $scm->inTags($cobject->commit, '');
return Pluf_Shortcuts_RenderToResponse('idf/source/commit.html',
array(
'page_title' => $page_title,
@@ -292,6 +316,9 @@ class IDF_Views_Source
'cobject' => $cobject,
'commit' => $commit,
'branches' => $branches,
'tree_in' => $in_branches,
'tags' => $tags,
'tags_in' => $in_tags,
'scm' => $scmConf,
'rcommit' => $rcommit,
'large_commit' => $large,
@@ -304,12 +331,11 @@ class IDF_Views_Source
{
$scm = IDF_Scm::get($request->project);
$commit = $match[2];
$branches = $scm->getBranches();
if (!$scm->isValidRevision($commit)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$branches[0]));
$scm->getMainBranch()));
return new Pluf_HTTP_Response_Redirect($url);
}
$cobject = $scm->getCommit($commit, true);
@@ -327,22 +353,21 @@ class IDF_Views_Source
$this->getScmType($request));
$scm = IDF_Scm::get($request->project);
$branches = $extra['branches'];
$tags = $extra['tags'];
$commit = $extra['commit'];
$request_file = $extra['request_file'];
$request_file_info = $extra['request_file_info'];
$bc = self::makeBreadCrumb($request->project, $commit, $request_file_info->file);
$bc = self::makeBreadCrumb($request->project, $commit, $request_file_info->fullpath);
$page_title = $bc.' - '.$title;
$cobject = $scm->getCommit($commit);
$tree_in = in_array($commit, $branches);
$in_branches = $scm->inBranches($commit, $request_file);
$in_tags = $scm->inTags($commit, '');
// try to find the previous level if it exists.
$prev = split('/', $request_file);
$prev = explode('/', $request_file);
$l = array_pop($prev);
$previous = substr($request_file, 0, -strlen($l.' '));
$scmConf = $request->conf->getVal('scm', 'git');
$props = null;
if ($scmConf === 'svn') {
$props = $scm->getProperties($commit, $request_file);
}
$content = self::highLight($extra['mime'], $scm->getFile($request_file_info));
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/file.html',
array(
@@ -353,10 +378,13 @@ class IDF_Views_Source
'commit' => $commit,
'cobject' => $cobject,
'fullpath' => $request_file,
'efullpath' => IDF_Scm::smartEncode($request_file),
'base' => $request_file_info->file,
'prev' => $previous,
'tree_in' => $tree_in,
'tree_in' => $in_branches,
'branches' => $branches,
'tags' => $tags,
'tags_in' => $in_tags,
'props' => $props,
),
$request);
@@ -370,14 +398,13 @@ class IDF_Views_Source
public function getFile($request, $match)
{
$scm = IDF_Scm::get($request->project);
$branches = $scm->getBranches();
$commit = $match[2];
$request_file = $match[3];
if (!$scm->isValidRevision($commit)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$branches[0]));
$scm->getMainBranch()));
return new Pluf_HTTP_Response_Redirect($url);
}
$request_file_info = $scm->getPathInfo($request_file, $commit);
@@ -385,7 +412,7 @@ class IDF_Views_Source
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$branches[0]));
$scm->getMainBranch()));
return new Pluf_HTTP_Response_Redirect($url);
}
$info = self::getRequestedFileMimeType($request_file_info,
@@ -405,12 +432,11 @@ class IDF_Views_Source
{
$commit = trim($match[2]);
$scm = IDF_Scm::get($request->project);
$branches = $scm->getBranches();
if (!$scm->isValidRevision($commit)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$branches[0]));
$scm->getMainBranch()));
return new Pluf_HTTP_Response_Redirect($url);
}
$base = $request->project->shortname.'-'.$commit;
@@ -447,7 +473,7 @@ class IDF_Views_Source
* @param string File content
* @return array Mime type found or 'application/octet-stream', basename, extension
*/
public static function getMimeTypeFromContent($file, &$filedata)
public static function getMimeTypeFromContent($file, $filedata)
{
$info = pathinfo($file);
$res = array('application/octet-stream',

View File

@@ -113,8 +113,9 @@ class IDF_Views_User
$form = new IDF_Form_UserAccount($request->POST, $params);
if ($form->isValid()) {
$user = $form->save();
$url = Pluf_HTTP_URL_urlForView('IDF_Views::index');
$request->user->setMessage(__('Your personal information have been updated.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_User::myAccount');
$request->session->setData('pluf_language', $user->language);
$request->user->setMessage(__('Your personal information has been updated.'));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
@@ -123,20 +124,35 @@ class IDF_Views_User
$form = new IDF_Form_UserAccount($data, $params);
}
$keys = $request->user->get_idf_key_list();
if ($keys->count() > 0 and strlen($keys[0]->content) > 30) {
$ssh_key = Pluf_Template::markSafe('<span class="mono">'.Pluf_esc(substr($keys[0]->content, 0, 30)).'...</span><br /><span class="helptext">'.__('Troncated for security reasons.').'</span>');
} else {
$ssh_key = __('You have not upload your public SSH key yet.');
}
return Pluf_Shortcuts_RenderToResponse('idf/user/myaccount.html',
array('page_title' => __('Your Account'),
'api_key' => $api_key,
'ext_pass' => $ext_pass,
'ssh_key' => $ssh_key,
'keys' => $keys,
'form' => $form),
$request);
}
/**
* Delete a SSH key.
*
* This is redirecting to the preferences
*/
public $deleteKey_precond = array('Pluf_Precondition::loginRequired');
public function deleteKey($request, $match)
{
$url = Pluf_HTTP_URL_urlForView('IDF_Views_User::myAccount');
if ($request->method == 'POST') {
$key = Pluf_Shortcuts_GetObjectOr404('IDF_Key', $match[1]);
if ($key->user != $request->user->id) {
return new Pluf_HTTP_Response_Forbidden($request);
}
$key->delete();
$request->user->setMessage(__('The SSH key has been deleted.'));
}
return new Pluf_HTTP_Response_Redirect($url);
}
/**
* Enter the key to change an email address.
*
@@ -191,7 +207,6 @@ class IDF_Views_User
*/
public function view($request, $match)
{
$projects = IDF_Views::getProjects($request->user);
$sql = new Pluf_SQL('login=%s', array($match[1]));
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
if (count($users) != 1 or !$users[0]->active) {
@@ -200,7 +215,6 @@ class IDF_Views_User
return Pluf_Shortcuts_RenderToResponse('idf/user/public.html',
array('page_title' => (string) $users[0],
'member' => $users[0],
'projects' => $projects,
),
$request);
}

View File

@@ -222,7 +222,7 @@ class IDF_Views_Wiki
array($prj->id, $match[2]));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
if ($pages->count() != 1) {
throw new Pluf_HTTP_Response_NotFound($request);
return new Pluf_HTTP_Response_NotFound($request);
}
$page = $pages[0];
$oldrev = false;
@@ -231,7 +231,7 @@ class IDF_Views_Wiki
$oldrev = Pluf_Shortcuts_GetObjectOr404('IDF_WikiRevision',
$request->GET['rev']);
if ($oldrev->wikipage != $page->id or $oldrev->is_head == true) {
throw new Pluf_HTTP_Response_NotFound($request);
return new Pluf_HTTP_Response_NotFound($request);
}
}
$ptags = self::getWikiTags($prj);
@@ -240,8 +240,7 @@ class IDF_Views_Wiki
$dep = Pluf_Model_InArray($dtag, $tags);
$title = $page->title;
$revision = $page->get_current_revision();
$db = $page->getDbConnection();
$false = Pluf_DB_BooleanToDb(false, $db);
$false = Pluf_DB_BooleanToDb(false, $page->getDbConnection());
$revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC',
'filter' => 'is_head='.$false));
return Pluf_Shortcuts_RenderToResponse('idf/wiki/view.html',
@@ -269,7 +268,7 @@ class IDF_Views_Wiki
$page = $oldrev->get_wikipage();
$prj->inOr404($page);
if ($oldrev->is_head == true) {
throw new Pluf_HTTP_Error404($request);
return new Pluf_HTTP_Response_NotFound($request);
}
if ($request->method == 'POST') {
$oldrev->delete();
@@ -281,8 +280,7 @@ class IDF_Views_Wiki
$title = sprintf(__('Delete Old Revision of %s'), $page->title);
$revision = $page->get_current_revision();
$db = $page->getDbConnection();
$false = Pluf_DB_BooleanToDb(false, $db);
$false = Pluf_DB_BooleanToDb(false, $page->getDbConnection());
$revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC',
'filter' => 'is_head='.$false));
return Pluf_Shortcuts_RenderToResponse('idf/wiki/delete.html',
@@ -310,7 +308,7 @@ class IDF_Views_Wiki
array($prj->id, $match[2]));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
if ($pages->count() != 1) {
throw new Pluf_HTTP_Error404($request);
return new Pluf_HTTP_Response_NotFound($request);
}
$page = $pages[0];
$title = sprintf(__('Update %s'), $page->title);
@@ -348,6 +346,46 @@ class IDF_Views_Wiki
$request);
}
/**
* Delete a Wiki page.
*/
public $delete_precond = array('IDF_Precondition::accessWiki',
'IDF_Precondition::projectMemberOrOwner');
public function delete($request, $match)
{
$prj = $request->project;
$page = Pluf_Shortcuts_GetObjectOr404('IDF_WikiPage', $match[2]);
$prj->inOr404($page);
$params = array('page' => $page);
if ($request->method == 'POST') {
$form = new IDF_Form_WikiDelete($request->POST, $params);
if ($form->isValid()) {
$form->save();
$request->user->setMessage(__('The documentation page has been deleted.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::index',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$form = new IDF_Form_WikiDelete(null, $params);
}
$title = sprintf(__('Delete Page %s'), $page->title);
$revision = $page->get_current_revision();
$false = Pluf_DB_BooleanToDb(false, $page->getDbConnection());
$revs = $page->get_revisions_list(array('order' => 'creation_dtime DESC',
'filter' => 'is_head='.$false));
return Pluf_Shortcuts_RenderToResponse('idf/wiki/deletepage.html',
array(
'page_title' => $title,
'page' => $page,
'form' => $form,
'rev' => $revision,
'revs' => $revs,
'tags' => $page->get_tags_list(),
),
$request);
}
/**
* Get the wiki tags.
*
@@ -397,7 +435,7 @@ class IDF_Views_Wiki
foreach ($st as $s) {
$v = '';
$d = '';
$_s = split('=', $s, 2);
$_s = explode('=', $s, 2);
if (count($_s) > 1) {
$v = trim($_s[0]);
$d = trim($_s[1]);

View File

@@ -146,8 +146,7 @@ class IDF_WikiPage extends Pluf_Model
function get_current_revision()
{
$db = $this->getDbConnection();
$true = Pluf_DB_BooleanToDb(true, $db);
$true = Pluf_DB_BooleanToDb(true, $this->getDbConnection());
$rev = $this->get_revisions_list(array('filter' => 'is_head='.$true,
'nb' => 1));
return ($rev->count() == 1) ? $rev[0] : null;
@@ -196,35 +195,30 @@ class IDF_WikiPage extends Pluf_Model
$user = $stag->start($this->get_submitter(), $request, '', false);
$out .= sprintf(__('<a href="%1$s" title="View page">%2$s</a>, %3$s'), $url, Pluf_esc($this->title), Pluf_esc($this->summary)).'</td>';
$out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Creation of <a href="%s">page&nbsp;%s</a>'), $url, Pluf_esc($this->title)).', '.__('by').' '.$user.'</div></td></tr>';
<div class="helptext right">'.sprintf(__('Creation of <a href="%s">page&nbsp;%s</a>, by %s'), $url, Pluf_esc($this->title), $user).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
public function feedFragment($request)
{
$base = '<entry>
<title>%%title%%</title>
<link href="%%url%%"/>
<id>%%url%%</id>
<updated>%%date%%</updated>
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
%%content%%
</div></content>
</entry>';
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
array($request->project->shortname,
$this->title));
$title = sprintf(__('%s: Documentation page %s added - %s'),
Pluf_esc($request->project->name),
Pluf_esc($this->title), Pluf_esc($this->summary));
$content = Pluf_esc($this->summary);
$request->project->name,
$this->title, $this->summary);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
return Pluf_Translation::sprintf($base,
$context = new Pluf_Template_Context_Request(
$request,
array('url' => $url,
'title' => $title,
'content' => $content,
'date' => $date));
'page' => $this,
'rev' => $this->get_current_revision(),
'create' => true,
'date' => $date)
);
$tmpl = new Pluf_Template('idf/wiki/feedfragment.xml');
return $tmpl->render($context);
}
}

View File

@@ -186,36 +186,97 @@ class IDF_WikiRevision extends Pluf_Model
}
$out .= '</td></tr>';
$out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Change of <a href="%s">%s</a>'), $url, Pluf_esc($page->title)).', '.__('by').' '.$user.'</div></td></tr>';
<div class="helptext right">'.sprintf(__('Change of <a href="%s">%s</a>, by %s'), $url, Pluf_esc($page->title), $user).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
public function feedFragment($request)
{
$base = '<entry>
<title>%%title%%</title>
<link href="%%url%%"/>
<id>%%url%%</id>
<updated>%%date%%</updated>
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
%%content%%
</div></content>
</entry>';
$page = $this->get_wikipage();
if (!$this->is_head) {
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
array($request->project->shortname,
$page->title),
array('rev' => $this->id));
} else {
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
array($request->project->shortname,
$page->title));
}
$title = sprintf(__('%s: Documentation page %s updated - %s'),
Pluf_esc($request->project->name),
Pluf_esc($page->title), Pluf_esc($page->summary));
$content = Pluf_esc($this->summary);
$request->project->name,
$page->title, $page->summary);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
return Pluf_Translation::sprintf($base,
$context = new Pluf_Template_Context_Request(
$request,
array('url' => $url,
'title' => $title,
'content' => $content,
'date' => $date));
'page' => $page,
'rev' => $this,
'create' => false,
'date' => $date)
);
$tmpl = new Pluf_Template('idf/wiki/feedfragment.xml');
return $tmpl->render($context);
}
/**
* Notification of change of a WikiPage.
*
* The content of a WikiPage is in the IDF_WikiRevision object,
* this is why we send the notificatin from there. This means that
* when the create flag is set, this is for the creation of a
* wikipage and not, for the addition of a new revision.
*
* Usage:
* <pre>
* $this->notify($conf); // Notify the creation of a wiki page
* $this->notify($conf, false); // Notify the update of the page
* </pre>
*
* @param IDF_Conf Current configuration
* @param bool Creation (true)
*/
public function notify($conf, $create=true)
{
if ('' == $conf->getVal('wiki_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(
'page' => $this->get_wikipage(),
'rev' => $this,
'project' => $this->get_wikipage()->get_project(),
'url_base' => Pluf::f('url_base'),
)
);
if ($create) {
$template = 'idf/wiki/wiki-created-email.txt';
$title = sprintf(__('New Documentation Page %s - %s (%s)'),
$this->get_wikipage()->title,
$this->get_wikipage()->summary,
$this->get_wikipage()->get_project()->shortname);
} else {
$template = 'idf/wiki/wiki-updated-email.txt';
$title = sprintf(__('Documentation Page Changed %s - %s (%s)'),
$this->get_wikipage()->title,
$this->get_wikipage()->summary,
$this->get_wikipage()->get_project()->shortname);
}
$tmpl = new Pluf_Template($template);
$text_email = $tmpl->render($context);
$email = new Pluf_Mail(Pluf::f('from_email'),
$conf->getVal('wiki_notification_email'),
$title);
$email->addTextMessage($text_email);
$email->sendMail();
Pluf_Translation::loadSetLocale($current_locale);
}
}

View File

@@ -165,9 +165,17 @@ $cfg['db_database'] = 'website'; # put absolute path to the db if you
# $cfg['idf_extra_upload_ext'] = ' ext1 ext2';
#
# By default, the size of the downloads is limited to 2MB.
# The php.ini upload_max_filesize configuration setting will
# always have precedence.
# $cfg['max_upload_size'] = 2097152; // Size in bytes
# -- From this point you should not need to update anything. --
#
# Time zone
# http://www.php.net/manual/en/timezones.php
#
# $cfg['time_zone'] = 'Europe/Berlin';
$cfg['pear_path'] = '/usr/share/php';
$cfg['login_success_url'] = $cfg['url_base'].$cfg['idf_base'];

View File

@@ -0,0 +1,38 @@
<?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 file define the paths to where the Pluf and InDefero libraries
* are installed. If you followed the recommended layout of the files
* in the installation procedure, they should work out of the box.
*
* PLUF_PATH: path to the folder containing the Pluf.php file.
* IDF_PATH: path to the folder containing the IDF folder.
*/
define('PLUF_PATH', dirname(__FILE__).'/../../../../pluf/src');
define('IDF_PATH', dirname(__FILE__).'/../..');
set_include_path(get_include_path()
.PATH_SEPARATOR.PLUF_PATH
.PATH_SEPARATOR.IDF_PATH
);

View File

@@ -26,33 +26,28 @@ $base = Pluf::f('idf_base');
$ctl[] = array('regex' => '#^/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'index');
$ctl[] = array('regex' => '#^/login/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'login',
'name' => 'login_view');
$ctl[] = array('regex' => '#^/preferences/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_User',
'method' => 'myAccount');
$ctl[] = array('regex' => '#^/dashboard/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_User',
'method' => 'dashboard',
'name' => 'idf_dashboard');
$ctl[] = array('regex' => '#^/dashboard/submitted/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_User',
'method' => 'dashboard',
'params' => false,
@@ -60,105 +55,88 @@ $ctl[] = array('regex' => '#^/dashboard/submitted/$#',
$ctl[] = array('regex' => '#^/u/(.*)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_User',
'method' => 'view');
$ctl[] = array('regex' => '#^/logout/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'logout');
$ctl[] = array('regex' => '#^/help/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'faq');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'home');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/timeline/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'timeline');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'timelineFeed',
'name' => 'idf_project_timeline_feed');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/token/(.*)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'timelineFeed',
'name' => 'idf_project_timeline_feed_auth');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'index');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/search/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'search');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'view');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/(\d+)/star/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'star');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/status/(\w+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'listStatus');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/label/(\d+)/(\w+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'listLabel');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/create/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'create');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/my/(\w+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'myIssues');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/attachment/(\d+)/(.*)$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'getAttachment');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/view/attachment/(\d+)/(.*)$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Issue',
'method' => 'viewAttachment');
@@ -166,61 +144,51 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/view/attachment/(\d+)/(.*)$#',
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/help/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source',
'method' => 'help');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/([^/]+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source',
'method' => 'treeBase');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/([^/]+)/(.*)$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source',
'method' => 'tree');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changes/([^/]+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source',
'method' => 'changeLog');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/commit/([^/]+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source',
'method' => 'commit');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/ddiff/([^/]+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source',
'method' => 'downloadDiff');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/download/([^/]+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source',
'method' => 'download');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/file/([^/]+)/(.*)$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source',
'method' => 'getFile');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/treerev/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source_Svn',
'method' => 'treeRev');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changesrev/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Source_Svn',
'method' => 'changelogRev');
@@ -228,43 +196,41 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changesrev/$#',
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Wiki',
'method' => 'index');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/create/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Wiki',
'method' => 'create');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/search/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Wiki',
'method' => 'search');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/label/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Wiki',
'method' => 'listLabel');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/update/(.*)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Wiki',
'method' => 'update');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/delrev/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Wiki',
'method' => 'deleteRev');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/delete/(\d+)/$#',
'base' => $base,
'model' => 'IDF_Views_Wiki',
'method' => 'delete');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/page/(.*)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Wiki',
'method' => 'view');
@@ -272,37 +238,31 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/page/(.*)/$#',
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'index');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/label/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'listLabel');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'view');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/get/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'download');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/create/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'submit');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/delete/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Download',
'method' => 'delete');
@@ -310,25 +270,21 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/(\d+)/delete/$#',
$ctl[] = array('regex' => '#^/p/([\-\w]+)/review/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Review',
'method' => 'index');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/review/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Review',
'method' => 'view');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/review/create/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Review',
'method' => 'create');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/review/getpatch/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Review',
'method' => 'getPatch');
@@ -337,43 +293,36 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/review/getpatch/(\d+)/$#',
$ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'admin');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/issues/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'adminIssues');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/downloads/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'adminDownloads');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/wiki/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'adminWiki');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/source/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'adminSource');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/members/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'adminMembers');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/tabs/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'adminTabs');
@@ -381,19 +330,16 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/tabs/$#',
$ctl[] = array('regex' => '#^/help/api/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'faqApi');
$ctl[] = array('regex' => '#^/api/p/([\-\w]+)/issues/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Api',
'method' => 'issuesIndex');
$ctl[] = array('regex' => '#^/api/p/([\-\w]+)/issues/create/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Api',
'method' => 'issueCreate');
@@ -401,43 +347,41 @@ $ctl[] = array('regex' => '#^/api/p/([\-\w]+)/issues/create/$#',
$ctl[] = array('regex' => '#^/admin/projects/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Admin',
'method' => 'projects');
$ctl[] = array('regex' => '#^/admin/projects/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Admin',
'method' => 'projectUpdate');
$ctl[] = array('regex' => '#^/admin/projects/create/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Admin',
'method' => 'projectCreate');
$ctl[] = array('regex' => '#^/admin/projects/(\d+)/delete/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Admin',
'method' => 'projectDelete');
$ctl[] = array('regex' => '#^/admin/users/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Admin',
'method' => 'users');
$ctl[] = array('regex' => '#^/admin/users/create/$#',
'base' => $base,
'model' => 'IDF_Views_Admin',
'method' => 'userCreate');
$ctl[] = array('regex' => '#^/admin/users/notvalid/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Admin',
'method' => 'usersNotValidated');
$ctl[] = array('regex' => '#^/admin/users/(\d+)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Admin',
'method' => 'userUpdate');
@@ -445,53 +389,48 @@ $ctl[] = array('regex' => '#^/admin/users/(\d+)/$#',
$ctl[] = array('regex' => '#^/register/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'register');
$ctl[] = array('regex' => '#^/register/k/(.*)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'registerConfirmation');
$ctl[] = array('regex' => '#^/register/ik/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'registerInputKey');
$ctl[] = array('regex' => '#^/password/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'passwordRecoveryAsk');
$ctl[] = array('regex' => '#^/password/ik/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'passwordRecoveryInputCode');
$ctl[] = array('regex' => '#^/password/k/(.*)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'passwordRecovery');
$ctl[] = array('regex' => '#^/preferences/email/ik/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_User',
'method' => 'changeEmailInputKey');
$ctl[] = array('regex' => '#^/preferences/email/ak/(.*)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_User',
'method' => 'changeEmailDo');
$ctl[] = array('regex' => '#^/preferences/key/(\d+)/delete/$#',
'base' => $base,
'model' => 'IDF_Views_User',
'method' => 'deleteKey');
return $ctl;

3514
src/IDF/locale/de/idf.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3500
src/IDF/locale/ru/idf.po Normal file

File diff suppressed because it is too large Load Diff

3495
src/IDF/locale/sl/idf.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,8 @@ $m['IDF_WikiRevision'] = array('relate_to' => array('IDF_WikiPage', 'Pluf_User')
$m['IDF_Review'] = array('relate_to' => array('IDF_Project', 'Pluf_User', 'IDF_Tag'),
'relate_to_many' => array('IDF_Tag', 'Pluf_User'));
$m['IDF_Review_Patch'] = array('relate_to' => array('IDF_Review', 'Pluf_User'));
$m['IDF_Review_FileComment'] = array('relate_to' => array('IDF_Review_Patch', 'Pluf_User'));
$m['IDF_Review_Comment'] = array('relate_to' => array('IDF_Review_Patch', 'Pluf_User'));
$m['IDF_Review_FileComment'] = array('relate_to' => array('IDF_Review_Comment', 'Pluf_User'));
$m['IDF_Key'] = array('relate_to' => array('Pluf_User'));
$m['IDF_Conf'] = array('relate_to' => array('IDF_Project'));
$m['IDF_Commit'] = array('relate_to' => array('IDF_Project', 'Pluf_User'));
@@ -54,6 +55,10 @@ Pluf_Signal::connect('IDF_Project::created',
array('IDF_Plugin_SyncSvn', 'entry'));
Pluf_Signal::connect('Pluf_User::passwordUpdated',
array('IDF_Plugin_SyncSvn', 'entry'));
Pluf_Signal::connect('IDF_Project::preDelete',
array('IDF_Plugin_SyncSvn', 'entry'));
Pluf_Signal::connect('svnpostcommit.php::run',
array('IDF_Plugin_SyncSvn', 'entry'));
#
# Mercurial synchronization
@@ -63,6 +68,8 @@ Pluf_Signal::connect('IDF_Project::created',
array('IDF_Plugin_SyncMercurial', 'entry'));
Pluf_Signal::connect('Pluf_User::passwordUpdated',
array('IDF_Plugin_SyncMercurial', 'entry'));
Pluf_Signal::connect('hgchangegroup.php::run',
array('IDF_Plugin_SyncMercurial', 'entry'));
#
# Git synchronization
@@ -72,5 +79,10 @@ Pluf_Signal::connect('IDF_Key::postSave',
array('IDF_Plugin_SyncGit', 'entry'));
Pluf_Signal::connect('IDF_Project::created',
array('IDF_Plugin_SyncGit', 'entry'));
Pluf_Signal::connect('IDF_Key::preDelete',
array('IDF_Plugin_SyncGit', 'entry'));
Pluf_Signal::connect('gitpostupdate.php::run',
array('IDF_Plugin_SyncGit', 'entry'));
return $m;

View File

@@ -20,7 +20,12 @@
<th>{trans 'Repository access:'}</th>
<td>{$repository_access}
</td>
</tr>{if $remote_svn}
</tr>{if $repository_size != -1}
<tr>
<th>{trans 'Repository size:'}</th>
<td>{$repository_size|size}
</td>
</tr>{/if}{if $remote_svn}
<tr>
<th>{$form.f.svn_username.labelTag}:</th>
<td>{if $form.f.svn_username.errors}{$form.f.svn_username.fieldErrors}{/if}

View File

@@ -53,6 +53,15 @@
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td colspan="2" class="helptext">
{blocktrans}
Only project members and admins have write access to the source.<br />
If you restrict the access to the source, anonymous access is<br />
not provided and the users must authenticate themselves with their<br />
password or SSH key.{/blocktrans}
</td></tr>
<tr>
<th><strong>{$form.f.review_access_rights.labelTag}:</strong></th>
<td>{if $form.f.review_access_rights.errors}{$form.f.review_access_rights.fieldErrors}{/if}
{$form.f.review_access_rights|unsafe}

View File

@@ -49,7 +49,7 @@
{if $hasSourceAccess} <a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}"{block tabsource}{/block}>{trans 'Source'}</a>{/if}
{if $hasReviewAccess} <a href="{url 'IDF_Views_Review::index', array($project.shortname)}"{block tabreview}{/block}>{trans 'Code Review'}</a>{/if}
{if $isOwner}
<a href="{url 'IDF_Views_Project::admin', array($project.shortname)}"{block tabadmin}{/block}>{trans 'Administer'}</a>{/if}{/if}
<a href="{url 'IDF_Views_Project::admin', array($project.shortname)}"{block tabadmin}{/block}>{trans 'Project Management'}</a>{/if}{/if}
</div>
{block subtabs}{if $user.isAnonymous()} | {aurl 'url', 'IDF_Views::login'}{blocktrans}<a href="{$url}">Sign in or create your account</a> to create issues or add comments{/blocktrans}{/if}{/block}
</div>
@@ -73,15 +73,18 @@
{include 'idf/js-hotkeys.html'}
{block javascript}{/block}
{if $project}
<script type="text/javascript">{literal}
<!-- //
<script type="text/javascript" charset="utf-8">{literal}
//<![CDATA[
$(document).ready(function(){
var frag = location.hash;
if (frag.length > 3 && frag.substring(0, 3) == '#ic') {
if ($('#preview').length) {
location.hash = '#preview';
}
else if (frag.length > 3 && frag.substring(0, 3) == '#ic') {
$(frag).addClass("issue-comment-focus");
}
});
// -->{/literal}
//]]>{/literal}
</script>{/if}
</body>
</html>

View File

@@ -35,7 +35,7 @@
<div id="hd">
<p class="top"><a href="#title" accesskey="2"></a>
{if !$user.isAnonymous()}{aurl 'url', 'idf_dashboard'}{blocktrans}Welcome, <strong><a class="userw" href="{$url}">{$user}</a></strong>.{/blocktrans} <a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a>{else}<a href="{url 'IDF_Views::login'}">{trans 'Sign in or create your account'}</a>{/if}
| <a href="{url 'IDF_Views::index'}">{trans 'Project List'}</a> {if $isAdmin}| <a href="{url 'IDF_Views_Admin::projects'}">{trans 'Administer'}</a>{/if}
| <a href="{url 'IDF_Views::index'}">{trans 'Project List'}</a> {if $isAdmin}| <a href="{url 'IDF_Views_Admin::projects'}">{trans 'Forge Management'}</a>{/if}
| <a href="{url 'IDF_Views::faq'}" title="{trans 'Help and accessibility features'}">{trans 'Help'}</a>
</p>
<h1 id="title" class="title">{block title}{$page_title}{/block}</h1>

View File

@@ -37,7 +37,7 @@
<p class="top"><a href="#title" accesskey="2"></a>
{if !$user.isAnonymous()}{aurl 'url', 'idf_dashboard'}{blocktrans}Welcome, <strong><a class="userw" href="{$url}">{$user}</a></strong>.{/blocktrans} <a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a>{else}<a href="{url 'IDF_Views::login'}">{trans 'Sign in or create your account'}</a>{/if}
{if $project} | <a href="{url 'IDF_Views::index'}">{trans 'Project List'}</a>{/if}
{if $isAdmin}| <a href="{url 'IDF_Views_Admin::projects'}">{trans 'Administer'}</a>{/if}
{if $isAdmin}| <a href="{url 'IDF_Views_Admin::projects'}">{trans 'Forge Management'}</a>{/if}
| <a href="{url 'IDF_Views::faq'}" title="{trans 'Help and accessibility features'}">{trans 'Help'}</a>
</p>
<div id="header">
@@ -50,7 +50,7 @@
{if $hasSourceAccess} <a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}"{block tabsource}{/block}>{trans 'Source'}</a>{/if}
{if $hasReviewAccess} <a href="{url 'IDF_Views_Review::index', array($project.shortname)}"{block tabreview}{/block}>{trans 'Code Review'}</a>{/if}
{if $isOwner}
<a href="{url 'IDF_Views_Project::admin', array($project.shortname)}"{block tabadmin}{/block}>{trans 'Administer'}</a>{/if}{/if}
<a href="{url 'IDF_Views_Project::admin', array($project.shortname)}"{block tabadmin}{/block}>{trans 'Project Management'}</a>{/if}{/if}
</div>
{block subtabs}{if $user.isAnonymous()} | {aurl 'url', 'IDF_Views::login'}{blocktrans}<a href="{$url}">Sign in or create your account</a> to create issues or add comments{/blocktrans}{/if}{/block}
</div>
@@ -75,15 +75,18 @@
{include 'idf/js-hotkeys.html'}
{block javascript}{/block}
{if $project}
<script type="text/javascript">{literal}
<!-- //
<script type="text/javascript" charset="utf-8">{literal}
//<![CDATA[
$(document).ready(function(){
var frag = location.hash;
if (frag.length > 3 && frag.substring(0, 3) == '#ic') {
if ($('#preview').length) {
location.hash = '#preview';
}
else if (frag.length > 3 && frag.substring(0, 3) == '#ic') {
$(frag).addClass("issue-comment-focus");
}
});
// -->{/literal}
//]]>{/literal}
</script>{/if}
</body>
</html>

View File

@@ -10,3 +10,8 @@
{foreach $tags as $tag} {$tag.class|safe}:{$tag.name|safe}
{/foreach}{/if}
{trans 'Download:'} {$urlfile}
{if $file.changelog}
{trans 'Description:'}
{$file.changelog}
{/if}

View File

@@ -0,0 +1,14 @@
<entry>
<title>{$title}</title>
<link href="{$url}"/>
<id>{$url}</id>
<updated>{$date}</updated>
<author>{$file.get_submitter()}</author>
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
<p>{$file} - {$file.filesize|ssize}</p>
{if $file.changelog}
<h2>{trans 'Details'}</h2>
{markdown $file.changelog, $request}
{/if}
</div></content>
</entry>

View File

@@ -19,6 +19,12 @@
</td>
</tr>
<tr>
<th>{$form.f.changelog.labelTag}:</th>
<td>{if $form.f.changelog.errors}{$form.f.changelog.fieldErrors}{/if}
{$form.f.changelog|unsafe}
</td>
</tr>
<tr>
<th><strong>{$form.f.file.labelTag}:</strong></th>
<td>{if $form.f.file.errors}{$form.f.file.fieldErrors}{/if}
{$form.f.file|unsafe}
@@ -50,6 +56,8 @@
<p>{blocktrans}Each file must have a distinct name and file contents
cannot be changed, so be sure to include release numbers in each file
name.{/blocktrans}</p>
{assign $url = 'http://daringfireball.net/projects/markdown/syntax'}
<p>{blocktrans}You can use the <a href="{$url}">Markdown syntax</a> for the description.{/blocktrans}</p>
</div>
{/block}
{block javascript}

View File

@@ -6,7 +6,11 @@
{if $deprecated}<p class="smaller">{blocktrans}<strong>Attention!</strong> This file is marked as deprecated, download it only if you are sure you need this specific version.{/blocktrans}</p>{/if}
<a href="{url 'IDF_Views_Download::download', array($project.shortname, $file.id)}">{$file}</a> - {$file.filesize|size}
</div>
{if $file.changelog}
<h2 class="changes">{trans 'Changes'}</h2>
{markdown $file.changelog, $request}
{/if}
{if $form}
{if $form.errors}
@@ -19,7 +23,7 @@
{/if}
<form method="post" enctype="multipart/form-data" action=".">
<table class="form" summary="">
<table class="form download" summary="">
<tr>
<th><strong>{$form.f.summary.labelTag}:</strong></th>
<td>{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if}
@@ -27,6 +31,12 @@
</td>
</tr>
<tr>
<th>{$form.f.changelog.labelTag}:</th>
<td>{if $form.f.changelog.errors}{$form.f.changelog.fieldErrors}{/if}
{$form.f.changelog|unsafe}
</td>
</tr>
<tr>
<th>{$form.f.label1.labelTag}:</th>
<td>
{if $form.f.label1.errors}{$form.f.label1.fieldErrors}{/if}{$form.f.label1|unsafe}
@@ -39,7 +49,8 @@
</tr>
<tr>
<td>&nbsp;</td>{aurl 'url', 'IDF_Views_Download::delete', array($project.shortname, $file.id)}
<td><input type="submit" value="{trans 'Update File'}" name="submit" /> | <a href="{url 'IDF_Views_Download::index', array($project.shortname)}">{trans 'Cancel'}</a> <span class="dellink"><a href="{$url}" title="{trans 'Remove this file'}"><img src="{media '/idf/img/trash.png'}" style="vertical-align: text-bottom;" alt="{trans 'Trash'}" /></a> <a href="{$url}" title="{trans 'Remove this file'}">{trans 'Delete this file'}</a></span>
<td>{* float left is a fix for Firefox < 3.5 *}
<span style="float: left;"><input type="submit" value="{trans 'Update File'}" name="submit" /> | <a href="{url 'IDF_Views_Download::index', array($project.shortname)}">{trans 'Cancel'}</a></span> <span class="dellink"><a href="{$url}" title="{trans 'Remove this file'}"><img src="{media '/idf/img/trash.png'}" style="vertical-align: text-bottom;" alt="{trans 'Trash'}" /></a> <a href="{$url}" title="{trans 'Remove this file'}">{trans 'Delete this file'}</a></span>
</td>
</tr>
</table>

View File

@@ -36,6 +36,7 @@
<p class="top"><a href="#title" accesskey="2"></a>
{aurl 'url', 'IDF_Views_User::dashboard'}{blocktrans}Welcome, <strong><a class="userw" href="{$url}">{$user}</a></strong>.{/blocktrans} <a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a>
| <a href="{url 'IDF_Views::index'}">{trans 'Project List'}</a>
| <a href="{url 'IDF_Views_Admin::projects'}">{trans 'Forge Management'}</a>
| <a href="{url 'IDF_Views::faq'}" title="{trans 'Help and accessibility features'}">{trans 'Help'}</a>
</p>
<div id="header">

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