# HG changeset patch # User Chris Cannam # Date 1299154313 0 # Node ID 73ff0e6a11b156dcaa5b00b3386b03bef56a8310 # Parent eeebe205a0562c9814c0b2c39e1cc019f43cf0df# Parent 7cec015f07ce4a03f4928f1dcf11dc26da6369ac * Merge from branch cannam-pre-20110113-merge diff -r 7cec015f07ce -r 73ff0e6a11b1 .gitignore --- a/.gitignore Tue Feb 22 16:48:15 2011 +0000 +++ b/.gitignore Thu Mar 03 12:11:53 2011 +0000 @@ -1,4 +1,7 @@ +/.project +/.loadpath /config/additional_environment.rb +/config/configuration.yml /config/database.yml /config/email.yml /config/initializers/session_store.rb @@ -7,6 +10,8 @@ /db/*.sqlite3 /db/schema.rb /files/* +/lib/redmine/scm/adapters/mercurial/redminehelper.pyc +/lib/redmine/scm/adapters/mercurial/redminehelper.pyo /log/*.log* /log/mongrel_debug /public/dispatch.* diff -r 7cec015f07ce -r 73ff0e6a11b1 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Thu Mar 03 12:11:53 2011 +0000 @@ -0,0 +1,26 @@ +syntax: glob + +.project +.loadpath +config/additional_environment.rb +config/configuration.yml +config/database.yml +config/email.yml +config/initializers/session_store.rb +coverage +db/*.db +db/*.sqlite3 +db/schema.rb +files/* +lib/redmine/scm/adapters/mercurial/redminehelper.pyc +lib/redmine/scm/adapters/mercurial/redminehelper.pyo +log/*.log* +log/mongrel_debug +public/dispatch.* +public/plugin_assets +tmp/cache/* +tmp/sessions/* +tmp/sockets/* +tmp/test/* +vendor/rails +*.rbc diff -r 7cec015f07ce -r 73ff0e6a11b1 .svn/all-wcprops --- a/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000 +++ b/.svn/all-wcprops Thu Mar 03 12:11:53 2011 +0000 @@ -1,7 +1,13 @@ K 25 svn:wc:ra_dav:version-url V 24 -/svn/!svn/ver/4411/trunk +/svn/!svn/ver/4993/trunk +END +.hgignore +K 25 +svn:wc:ra_dav:version-url +V 34 +/svn/!svn/ver/4834/trunk/.hgignore END Rakefile K 25 @@ -19,5 +25,5 @@ K 25 svn:wc:ra_dav:version-url V 35 -/svn/!svn/ver/3768/trunk/.gitignore +/svn/!svn/ver/4834/trunk/.gitignore END diff -r 7cec015f07ce -r 73ff0e6a11b1 .svn/dir-prop-base --- a/.svn/dir-prop-base Tue Feb 22 16:48:15 2011 +0000 +++ b/.svn/dir-prop-base Thu Mar 03 12:11:53 2011 +0000 @@ -4,7 +4,9 @@ e93f8b46-1217-0410-a6f0-8f06a7374b81:/trunk:1751 K 10 svn:ignore -V 9 +V 28 coverage +.project +.loadpath END diff -r 7cec015f07ce -r 73ff0e6a11b1 .svn/entries --- a/.svn/entries Tue Feb 22 16:48:15 2011 +0000 +++ b/.svn/entries Thu Mar 03 12:11:53 2011 +0000 @@ -1,15 +1,15 @@ 10 dir -4411 +4993 http://redmine.rubyforge.org/svn/trunk http://redmine.rubyforge.org/svn -2010-11-17T18:27:38.712585Z -4411 -jplang +2011-03-03T05:51:46.224821Z +4993 +tmaruyama has-props @@ -29,6 +29,18 @@ test dir +.hgignore +file + + + + + +970b3ffe21e2c668737cf5abf0d0ac48 +2011-02-15T11:04:52.942730Z +4834 +tmaruyama + app dir @@ -41,7 +53,7 @@ -2010-09-23T14:37:44.367737Z +2011-03-03T11:05:14.000000Z bbf560d44f092d22a30d3a562436ad8c 2006-12-05T20:45:04.842118Z 67 @@ -69,13 +81,16 @@ 307 +extra +dir + README.rdoc file -2010-09-23T14:37:44.367737Z +2011-03-03T11:05:14.000000Z 67c937b1f1d0603e69f322de34bbfe04 2010-07-18T15:49:24.341728Z 3849 @@ -103,9 +118,6 @@ 208 -extra -dir - db dir @@ -133,11 +145,11 @@ -2010-09-23T14:37:44.363818Z -201a803b90bbd2a1624c4ce1dd260098 -2010-06-09T22:01:21.132822Z -3768 -edavis10 +2011-03-03T11:40:18.000000Z +84dbba0b6ddcd80d28c62a3f8e344bc4 +2011-02-15T11:04:52.942730Z +4834 +tmaruyama @@ -159,7 +171,7 @@ -322 +477 lib dir diff -r 7cec015f07ce -r 73ff0e6a11b1 .svn/text-base/.gitignore.svn-base --- a/.svn/text-base/.gitignore.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/.svn/text-base/.gitignore.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -1,4 +1,7 @@ +/.project +/.loadpath /config/additional_environment.rb +/config/configuration.yml /config/database.yml /config/email.yml /config/initializers/session_store.rb @@ -7,6 +10,8 @@ /db/*.sqlite3 /db/schema.rb /files/* +/lib/redmine/scm/adapters/mercurial/redminehelper.pyc +/lib/redmine/scm/adapters/mercurial/redminehelper.pyo /log/*.log* /log/mongrel_debug /public/dispatch.* diff -r 7cec015f07ce -r 73ff0e6a11b1 .svn/text-base/.hgignore.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/text-base/.hgignore.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -0,0 +1,27 @@ +syntax: glob + +.project +.loadpath +config/additional_environment.rb +config/configuration.yml +config/database.yml +config/email.yml +config/initializers/session_store.rb +coverage +db/*.db +db/*.sqlite3 +db/schema.rb +files/* +lib/redmine/scm/adapters/mercurial/redminehelper.pyc +lib/redmine/scm/adapters/mercurial/redminehelper.pyo +log/*.log* +log/mongrel_debug +public/dispatch.* +public/plugin_assets +tmp/* +tmp/cache/* +tmp/sessions/* +tmp/sockets/* +tmp/test/* +vendor/rails +*.rbc diff -r 7cec015f07ce -r 73ff0e6a11b1 .svn/tmp/.hgignore.tmp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/tmp/.hgignore.tmp Thu Mar 03 12:11:53 2011 +0000 @@ -0,0 +1,26 @@ +syntax: glob + +.project +.loadpath +config/additional_environment.rb +config/configuration.yml +config/database.yml +config/email.yml +config/initializers/session_store.rb +coverage +db/*.db +db/*.sqlite3 +db/schema.rb +files/* +lib/redmine/scm/adapters/mercurial/redminehelper.pyc +lib/redmine/scm/adapters/mercurial/redminehelper.pyo +log/*.log* +log/mongrel_debug +public/dispatch.* +public/plugin_assets +tmp/cache/* +tmp/sessions/* +tmp/sockets/* +tmp/test/* +vendor/rails +*.rbc diff -r 7cec015f07ce -r 73ff0e6a11b1 app/.svn/all-wcprops --- a/app/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000 +++ b/app/.svn/all-wcprops Thu Mar 03 12:11:53 2011 +0000 @@ -1,5 +1,5 @@ K 25 svn:wc:ra_dav:version-url V 28 -/svn/!svn/ver/4411/trunk/app +/svn/!svn/ver/4990/trunk/app END diff -r 7cec015f07ce -r 73ff0e6a11b1 app/.svn/entries --- a/app/.svn/entries Tue Feb 22 16:48:15 2011 +0000 +++ b/app/.svn/entries Thu Mar 03 12:11:53 2011 +0000 @@ -1,15 +1,15 @@ 10 dir -4411 +4993 http://redmine.rubyforge.org/svn/trunk/app http://redmine.rubyforge.org/svn -2010-11-17T18:27:38.712585Z -4411 -jplang +2011-03-03T03:30:10.954225Z +4990 +tmaruyama diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/all-wcprops --- a/app/controllers/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/all-wcprops Thu Mar 03 12:11:53 2011 +0000 @@ -1,13 +1,13 @@ K 25 svn:wc:ra_dav:version-url V 40 -/svn/!svn/ver/4411/trunk/app/controllers +/svn/!svn/ver/4954/trunk/app/controllers END issues_controller.rb K 25 svn:wc:ra_dav:version-url V 61 -/svn/!svn/ver/4411/trunk/app/controllers/issues_controller.rb +/svn/!svn/ver/4741/trunk/app/controllers/issues_controller.rb END queries_controller.rb K 25 @@ -37,13 +37,13 @@ K 25 svn:wc:ra_dav:version-url V 69 -/svn/!svn/ver/3945/trunk/app/controllers/auto_completes_controller.rb +/svn/!svn/ver/4503/trunk/app/controllers/auto_completes_controller.rb END my_controller.rb K 25 svn:wc:ra_dav:version-url V 57 -/svn/!svn/ver/4224/trunk/app/controllers/my_controller.rb +/svn/!svn/ver/4497/trunk/app/controllers/my_controller.rb END comments_controller.rb K 25 @@ -57,29 +57,23 @@ V 62 /svn/!svn/ver/3784/trunk/app/controllers/members_controller.rb END +context_menus_controller.rb +K 25 +svn:wc:ra_dav:version-url +V 68 +/svn/!svn/ver/4580/trunk/app/controllers/context_menus_controller.rb +END welcome_controller.rb K 25 svn:wc:ra_dav:version-url V 62 /svn/!svn/ver/2941/trunk/app/controllers/welcome_controller.rb END -context_menus_controller.rb -K 25 -svn:wc:ra_dav:version-url -V 68 -/svn/!svn/ver/4242/trunk/app/controllers/context_menus_controller.rb -END journals_controller.rb K 25 svn:wc:ra_dav:version-url V 63 -/svn/!svn/ver/4034/trunk/app/controllers/journals_controller.rb -END -workflows_controller.rb -K 25 -svn:wc:ra_dav:version-url -V 64 -/svn/!svn/ver/3536/trunk/app/controllers/workflows_controller.rb +/svn/!svn/ver/4954/trunk/app/controllers/journals_controller.rb END time_entry_reports_controller.rb K 25 @@ -87,6 +81,12 @@ V 73 /svn/!svn/ver/4232/trunk/app/controllers/time_entry_reports_controller.rb END +workflows_controller.rb +K 25 +svn:wc:ra_dav:version-url +V 64 +/svn/!svn/ver/4895/trunk/app/controllers/workflows_controller.rb +END reports_controller.rb K 25 svn:wc:ra_dav:version-url @@ -99,29 +99,23 @@ V 75 /svn/!svn/ver/4075/trunk/app/controllers/project_enumerations_controller.rb END -settings_controller.rb -K 25 -svn:wc:ra_dav:version-url -V 63 -/svn/!svn/ver/4220/trunk/app/controllers/settings_controller.rb -END -timelog_controller.rb -K 25 -svn:wc:ra_dav:version-url -V 62 -/svn/!svn/ver/4410/trunk/app/controllers/timelog_controller.rb -END custom_fields_controller.rb K 25 svn:wc:ra_dav:version-url V 68 /svn/!svn/ver/3627/trunk/app/controllers/custom_fields_controller.rb END -users_controller.rb +settings_controller.rb K 25 svn:wc:ra_dav:version-url -V 60 -/svn/!svn/ver/4382/trunk/app/controllers/users_controller.rb +V 63 +/svn/!svn/ver/4432/trunk/app/controllers/settings_controller.rb +END +timelog_controller.rb +K 25 +svn:wc:ra_dav:version-url +V 62 +/svn/!svn/ver/4511/trunk/app/controllers/timelog_controller.rb END issue_moves_controller.rb K 25 @@ -129,6 +123,12 @@ V 66 /svn/!svn/ver/4292/trunk/app/controllers/issue_moves_controller.rb END +users_controller.rb +K 25 +svn:wc:ra_dav:version-url +V 60 +/svn/!svn/ver/4729/trunk/app/controllers/users_controller.rb +END files_controller.rb K 25 svn:wc:ra_dav:version-url @@ -139,7 +139,13 @@ K 25 svn:wc:ra_dav:version-url V 66 -/svn/!svn/ver/4286/trunk/app/controllers/application_controller.rb +/svn/!svn/ver/4573/trunk/app/controllers/application_controller.rb +END +previews_controller.rb +K 25 +svn:wc:ra_dav:version-url +V 63 +/svn/!svn/ver/4174/trunk/app/controllers/previews_controller.rb END ldap_auth_sources_controller.rb K 25 @@ -153,12 +159,6 @@ V 67 /svn/!svn/ver/3744/trunk/app/controllers/auth_sources_controller.rb END -previews_controller.rb -K 25 -svn:wc:ra_dav:version-url -V 63 -/svn/!svn/ver/4174/trunk/app/controllers/previews_controller.rb -END search_controller.rb K 25 svn:wc:ra_dav:version-url @@ -175,7 +175,7 @@ K 25 svn:wc:ra_dav:version-url V 70 -/svn/!svn/ver/3591/trunk/app/controllers/issue_relations_controller.rb +/svn/!svn/ver/4764/trunk/app/controllers/issue_relations_controller.rb END versions_controller.rb K 25 @@ -199,7 +199,7 @@ K 25 svn:wc:ra_dav:version-url V 59 -/svn/!svn/ver/4174/trunk/app/controllers/news_controller.rb +/svn/!svn/ver/4505/trunk/app/controllers/news_controller.rb END trackers_controller.rb K 25 @@ -235,7 +235,7 @@ K 25 svn:wc:ra_dav:version-url V 67 -/svn/!svn/ver/3680/trunk/app/controllers/repositories_controller.rb +/svn/!svn/ver/4860/trunk/app/controllers/repositories_controller.rb END admin_controller.rb K 25 @@ -247,13 +247,13 @@ K 25 svn:wc:ra_dav:version-url V 63 -/svn/!svn/ver/4397/trunk/app/controllers/projects_controller.rb +/svn/!svn/ver/4647/trunk/app/controllers/projects_controller.rb END account_controller.rb K 25 svn:wc:ra_dav:version-url V 62 -/svn/!svn/ver/3906/trunk/app/controllers/account_controller.rb +/svn/!svn/ver/4757/trunk/app/controllers/account_controller.rb END calendars_controller.rb K 25 @@ -283,13 +283,13 @@ K 25 svn:wc:ra_dav:version-url V 59 -/svn/!svn/ver/4303/trunk/app/controllers/wiki_controller.rb +/svn/!svn/ver/4429/trunk/app/controllers/wiki_controller.rb END activities_controller.rb K 25 svn:wc:ra_dav:version-url V 65 -/svn/!svn/ver/4047/trunk/app/controllers/activities_controller.rb +/svn/!svn/ver/4579/trunk/app/controllers/activities_controller.rb END enumerations_controller.rb K 25 diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/entries --- a/app/controllers/.svn/entries Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/entries Thu Mar 03 12:11:53 2011 +0000 @@ -1,14 +1,14 @@ 10 dir -4411 +4993 http://redmine.rubyforge.org/svn/trunk/app/controllers http://redmine.rubyforge.org/svn -2010-11-17T18:27:38.712585Z -4411 +2011-02-27T13:34:41.060565Z +4954 jplang @@ -32,10 +32,10 @@ -2010-11-19T13:04:46.996826Z -e6c77385e49176b593367b44e7b33f3e -2010-11-17T18:27:38.712585Z -4411 +2011-03-03T11:05:10.000000Z +b2f39d4816a34c8fdbe451324ff298e4 +2011-01-22T13:28:20.816262Z +4741 jplang has-props @@ -58,7 +58,7 @@ -12834 +12253 queries_controller.rb file @@ -66,7 +66,7 @@ -2010-09-23T14:37:44.399747Z +2011-03-03T11:05:10.000000Z 7ad6758cfd160c4b6ef6d71be06e087c 2010-04-19T15:08:28.751734Z 3684 @@ -100,7 +100,7 @@ -2010-09-23T14:37:44.399747Z +2011-03-03T11:05:10.000000Z 7a73aba15fcd447531d1552de01706d9 2009-12-20T09:44:28.044710Z 3201 @@ -134,7 +134,7 @@ -2010-09-23T14:37:44.395735Z +2011-03-03T11:05:10.000000Z 612f3e7497e14c728934e83fbe5778c4 2009-12-20T09:45:04.782778Z 3202 @@ -168,7 +168,7 @@ -2010-09-23T14:37:44.399747Z +2011-03-03T11:05:10.000000Z 3ee494bd8196306eab8913bd462b44da 2010-06-20T16:08:15.578906Z 3798 @@ -202,33 +202,33 @@ -2010-09-23T14:37:44.391741Z -8cd8e21323a4500a05c26e4ebdde1812 -2010-08-17T15:03:58.074505Z -3945 -edavis10 - - - - - - - - - - - - - - - - - - - - - -665 +2011-03-03T11:05:10.000000Z +edbdb443ff7cc8f299f7772b3532eca1 +2010-12-12T16:06:43.892596Z +4503 +jplang + + + + + + + + + + + + + + + + + + + + + +772 my_controller.rb file @@ -236,11 +236,11 @@ -2010-11-19T13:04:46.996826Z -b465d7aeca2d6b4cc11159ae9bc7349e -2010-09-28T22:13:11.637219Z -4224 -edavis10 +2011-03-03T11:05:10.000000Z +40ea055e0f9f9aa83680751fdab5a0a5 +2010-12-12T14:25:23.262992Z +4497 +jplang has-props @@ -262,7 +262,7 @@ -6021 +5768 comments_controller.rb file @@ -270,7 +270,7 @@ -2010-09-24T12:48:26.271687Z +2011-03-03T11:05:10.000000Z c2289eb9b654117a101c072955d4c705 2010-09-23T15:20:19.085385Z 4172 @@ -304,7 +304,7 @@ -2010-09-23T14:37:44.395735Z +2011-03-03T11:05:10.000000Z 20e245fa61dc0dcb2f105a8f0e6af98c 2010-06-19T19:51:43.174421Z 3784 @@ -332,13 +332,47 @@ 3447 +context_menus_controller.rb +file + + + + +2011-03-03T11:05:10.000000Z +596c3ab10f8cf4687f6d8b163e7a2243 +2010-12-29T19:55:52.079810Z +4580 +jplang + + + + + + + + + + + + + + + + + + + + + +1847 + welcome_controller.rb file -2010-09-23T14:37:44.403745Z +2011-03-03T11:05:10.000000Z 95ad1acf7766861a4dbd0aebd9c847de 2009-10-21T03:21:31.657956Z 2941 @@ -366,51 +400,17 @@ 1091 -context_menus_controller.rb -file - - - - -2010-11-19T13:04:46.996826Z -2db29b69c3f0130e470a0a11dfc1f28b -2010-10-08T03:09:51.863032Z -4242 -jbbarth - - - - - - - - - - - - - - - - - - - - - -1821 - journals_controller.rb file -2010-09-23T14:37:44.395735Z -deb481ad0f862b9305b79f292d59fb51 -2010-08-23T15:04:36.844654Z -4034 -edavis10 +2011-03-03T11:40:18.000000Z +0610cb98929633e94c97f1060f4f1566 +2011-02-27T13:34:41.060565Z +4954 +jplang has-props @@ -432,41 +432,7 @@ -3439 - -workflows_controller.rb -file - - - - -2010-09-23T14:37:44.403745Z -5052c701a12ad27e88950dd4a745b0f8 -2010-03-04T05:33:45.236699Z -3536 -edavis10 -has-props - - - - - - - - - - - - - - - - - - - - -3346 +4081 time_entry_reports_controller.rb file @@ -474,7 +440,7 @@ -2010-11-19T13:04:46.996826Z +2011-03-03T11:05:10.000000Z dadcb57d89dc3c90c5fa099b143c07f8 2010-10-05T16:07:17.015270Z 4232 @@ -502,13 +468,47 @@ 9308 +workflows_controller.rb +file + + + + +2011-03-03T11:40:18.000000Z +73cdba17212ad890eea7b7c7ee140d7e +2011-02-20T15:38:07.840581Z +4895 +jplang +has-props + + + + + + + + + + + + + + + + + + + + +4041 + reports_controller.rb file -2010-09-23T14:37:44.399747Z +2011-03-03T11:05:10.000000Z 8f7e6e7308912a6169a700b0c88174a0 2010-04-27T16:43:52.584075Z 3692 @@ -542,7 +542,7 @@ -2010-09-24T12:48:26.271687Z +2011-03-03T11:05:10.000000Z 828b364ba2850ceddb3caa566bf10124 2010-09-10T16:00:49.687515Z 4075 @@ -570,47 +570,13 @@ 856 -timelog_controller.rb -file - - - - -2010-11-19T13:04:46.996826Z -7984019db8d0beb1d7ff158390d4ad68 -2010-11-16T20:27:45.364396Z -4410 -jplang -has-props - - - - - - - - - - - - - - - - - - - - -8623 - custom_fields_controller.rb file -2010-09-23T14:37:44.391741Z +2011-03-03T11:05:10.000000Z 00272455ac5be11da48f92c4a443c538 2010-04-03T11:54:24.331654Z 3627 @@ -644,11 +610,11 @@ -2010-11-19T13:04:46.996826Z -4328cabdf111342d147b245bc61bb75e -2010-09-28T20:19:55.214366Z -4220 -edavis10 +2011-03-03T11:05:10.000000Z +98c4c9f33f038160a44752d229926c32 +2010-11-27T14:06:11.754120Z +4432 +jplang has-props @@ -670,7 +636,41 @@ -2239 +2271 + +timelog_controller.rb +file + + + + +2011-03-03T11:05:10.000000Z +3e6698f208a263105c7b4a59c60b26b3 +2010-12-14T18:29:24.891563Z +4511 +jplang +has-props + + + + + + + + + + + + + + + + + + + + +10484 issue_moves_controller.rb file @@ -678,7 +678,7 @@ -2010-11-19T13:04:47.000764Z +2011-03-03T11:05:10.000000Z cacd6ffe0ce299e2ede9d45b7ef8af42 2010-10-25T18:44:46.868009Z 4292 @@ -712,10 +712,10 @@ -2010-11-19T13:04:47.000764Z -3af0a81ba74621dc21d877a579b39d9c -2010-11-07T14:15:01.891476Z -4382 +2011-03-03T11:05:10.000000Z +7860374d98cda03916d9b06d9ec8d370 +2011-01-16T15:23:11.666065Z +4729 jplang has-props @@ -738,7 +738,7 @@ -7565 +8474 files_controller.rb file @@ -746,7 +746,7 @@ -2010-11-19T13:04:47.000764Z +2011-03-03T11:05:10.000000Z f0246e8f8dde5ea7b93e4dc2162181fc 2010-09-26T08:07:41.604064Z 4177 @@ -780,10 +780,10 @@ -2010-11-19T13:04:47.000764Z -8f7d4c9d306ae9d59ea0e064466454d1 -2010-10-23T11:07:04.019894Z -4286 +2011-03-03T11:05:10.000000Z +218c497f1c4c6d7b41cdac214e595156 +2010-12-23T14:49:14.339855Z +4573 jplang has-props @@ -806,7 +806,7 @@ -13669 +15545 previews_controller.rb file @@ -814,7 +814,7 @@ -2010-11-19T13:04:47.000764Z +2011-03-03T11:05:10.000000Z c5ae4fae18159e70fd81c2487bb1daa1 2010-09-24T16:26:46.819682Z 4174 @@ -842,13 +842,47 @@ 957 +ldap_auth_sources_controller.rb +file + + + + +2011-03-03T11:05:10.000000Z +5f7b9cb2e9c8a60db58ea0833cf481c5 +2010-05-23T03:16:31.304135Z +3744 +edavis10 + + + + + + + + + + + + + + + + + + + + + +917 + auth_sources_controller.rb file -2010-09-23T14:37:44.391741Z +2011-03-03T11:05:10.000000Z 237280766d09418161536250d514a7bf 2010-05-23T03:16:31.304135Z 3744 @@ -876,47 +910,13 @@ 2542 -ldap_auth_sources_controller.rb -file - - - - -2010-09-23T14:37:44.395735Z -5f7b9cb2e9c8a60db58ea0833cf481c5 -2010-05-23T03:16:31.304135Z -3744 -edavis10 - - - - - - - - - - - - - - - - - - - - - -917 - search_controller.rb file -2010-09-23T14:37:44.399747Z +2011-03-03T11:05:10.000000Z 0015154998f830227b636ee423f007f3 2010-06-20T20:01:32.722003Z 3806 @@ -950,7 +950,7 @@ -2010-09-23T14:37:44.395735Z +2011-03-03T11:05:10.000000Z f3f4378ec15e1401ead0dcac4e9cb059 2010-03-03T17:05:00.967826Z 3528 @@ -978,13 +978,47 @@ 5824 +issue_relations_controller.rb +file + + + + +2011-03-03T11:05:10.000000Z +8c5249f85056c74e0ab06b5dfa9fed72 +2011-01-27T21:38:47.430923Z +4764 +jplang +has-props + + + + + + + + + + + + + + + + + + + + +2450 + versions_controller.rb file -2010-11-19T13:04:47.000764Z +2011-03-03T11:05:10.000000Z a98fb262a20669600a85c0f72202c5e6 2010-11-01T13:13:32.982466Z 4354 @@ -1012,47 +1046,13 @@ 6241 -issue_relations_controller.rb -file - - - - -2010-09-23T14:37:44.395735Z -c57448661f2eaab81c76f23d7f51e0ab -2010-03-16T15:17:47.586688Z -3591 -edavis10 -has-props - - - - - - - - - - - - - - - - - - - - -2220 - boards_controller.rb file -2010-09-23T14:37:44.391741Z +2011-03-03T11:05:10.000000Z 52638c82da929081099076d5aa0f9212 2010-08-16T23:39:27.396462Z 3942 @@ -1086,7 +1086,7 @@ -2010-09-23T14:37:44.391741Z +2011-03-03T11:05:10.000000Z 21c0e5d894d1429ac39f3253ae021594 2010-01-05T18:16:03.565347Z 3281 @@ -1120,11 +1120,11 @@ -2010-11-19T13:04:47.000764Z -d9e36fcf69f18973b274648b7b70dccf -2010-09-24T16:26:46.819682Z -4174 -edavis10 +2011-03-03T11:05:10.000000Z +cb358f888a6a92cc99bef83af92cd766 +2010-12-12T17:00:52.100205Z +4505 +jplang has-props @@ -1146,7 +1146,7 @@ -3221 +3369 trackers_controller.rb file @@ -1154,7 +1154,7 @@ -2010-09-23T14:37:44.399747Z +2011-03-03T11:05:10.000000Z a5e793eb94e501be4c1a39acc65cc568 2010-04-03T11:54:24.331654Z 3627 @@ -1188,7 +1188,7 @@ -2010-09-23T14:37:44.395735Z +2011-03-03T11:05:10.000000Z 77209ec52ddcefafb0bfbb76195167ec 2010-08-10T22:37:00.826946Z 3934 @@ -1222,7 +1222,7 @@ -2010-09-23T14:37:44.395735Z +2011-03-03T11:05:10.000000Z f0fdda9126d71c0b2d4638de50ab1097 2010-02-12T19:15:39.389877Z 3416 @@ -1256,7 +1256,7 @@ -2010-09-23T14:37:44.399747Z +2011-03-03T11:05:10.000000Z 28f2e44219870c62c9844354b78af6e1 2010-04-03T11:54:24.331654Z 3627 @@ -1290,7 +1290,7 @@ -2010-09-23T14:37:44.403745Z +2011-03-03T11:05:10.000000Z 7ae79a9d22586240a01af73834b718a3 2010-02-05T16:57:02.094258Z 3370 @@ -1324,11 +1324,11 @@ -2010-09-23T14:37:44.399747Z -a90fd2992bc17c6cee999369521194c4 -2010-04-17T12:51:46.860438Z -3680 -jplang +2011-03-03T11:40:18.000000Z +5511ecf5c7190efe44398994a222b486 +2011-02-17T14:17:04.791825Z +4860 +tmaruyama has-props @@ -1350,7 +1350,7 @@ -10750 +11539 admin_controller.rb file @@ -1358,7 +1358,7 @@ -2010-09-23T14:37:44.391741Z +2011-03-03T11:05:10.000000Z ed65a11326fe54914ba3a750a2b6f943 2009-12-19T20:33:24.113306Z 3200 @@ -1392,10 +1392,10 @@ -2010-11-19T13:04:47.000764Z -b0f584d53a742f6c89b157030f9d6a8d -2010-11-13T10:05:43.644565Z -4397 +2011-03-03T11:05:10.000000Z +c07d79c1bd100dd8972ea408c4fe549f +2011-01-06T20:57:17.003359Z +4647 jplang has-props @@ -1418,7 +1418,7 @@ -10011 +10103 account_controller.rb file @@ -1426,11 +1426,11 @@ -2010-09-23T14:37:44.391741Z -d0ff6be1c749b765d9d64bef64c27537 -2010-08-03T15:26:50.842290Z -3906 -edavis10 +2011-03-03T11:05:10.000000Z +7b162cad903bb22948f7040a48509256 +2011-01-23T11:40:07.678287Z +4757 +jplang has-props @@ -1452,7 +1452,7 @@ -8962 +9350 calendars_controller.rb file @@ -1460,7 +1460,7 @@ -2010-11-19T13:04:47.000764Z +2011-03-03T11:05:10.000000Z 0be20cab6d5ea49f428a90cf3afef3e3 2010-11-07T22:38:10.728638Z 4388 @@ -1494,7 +1494,7 @@ -2010-09-23T14:37:44.395735Z +2011-03-03T11:05:10.000000Z 119ceb4a27aa554d03867f46cb142d0e 2010-03-17T15:41:58.766740Z 3597 @@ -1528,7 +1528,7 @@ -2010-11-19T13:04:47.000764Z +2011-03-03T11:05:10.000000Z 81dbee1745da99b9ea499f3fbe1d3944 2010-11-07T22:38:10.728638Z 4388 @@ -1562,7 +1562,7 @@ -2010-09-23T14:37:44.391741Z +2011-03-03T11:05:10.000000Z c2c68bf5fa3ed22a8f55c8414d1efce9 2010-03-17T15:41:58.766740Z 3597 @@ -1596,11 +1596,11 @@ -2010-11-19T13:04:47.004764Z -0af823002ab9c7e8f59b3d12cf6267e9 -2010-10-28T21:25:38.778234Z -4303 -edavis10 +2011-03-03T11:05:10.000000Z +ccc57886f63cb0efb0149b601dd2ac43 +2010-11-27T10:34:44.408431Z +4429 +jplang has-props @@ -1622,7 +1622,7 @@ -10145 +10185 activities_controller.rb file @@ -1630,33 +1630,33 @@ -2010-09-24T12:48:26.275788Z -88f55d118861ffaca0992fa5ec55d51f -2010-08-27T14:05:54.014502Z -4047 -edavis10 - - - - - - - - - - - - - - - - - - - - - -1952 +2011-03-03T11:05:10.000000Z +ad33818a03b408419be4e616e8c88b5c +2010-12-29T19:37:42.740543Z +4579 +jplang + + + + + + + + + + + + + + + + + + + + + +2037 enumerations_controller.rb file @@ -1664,7 +1664,7 @@ -2010-09-23T14:37:44.391741Z +2011-03-03T11:05:10.000000Z 1caaedc7bb0a3085878dc94446c70ab0 2010-03-02T20:03:09.514061Z 3524 diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/account_controller.rb.svn-base --- a/app/controllers/.svn/text-base/account_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/account_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -203,12 +203,24 @@ self.logged_user = user # generate a key and set cookie if autologin if params[:autologin] && Setting.autologin? - token = Token.create(:user => user, :action => 'autologin') - cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now } + set_autologin_cookie(user) end call_hook(:controller_account_success_authentication_after, {:user => user }) redirect_back_or_default :controller => 'my', :action => 'page' end + + def set_autologin_cookie(user) + token = Token.create(:user => user, :action => 'autologin') + cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin' + cookie_options = { + :value => token.value, + :expires => 1.year.from_now, + :path => (Redmine::Configuration['autologin_cookie_path'] || '/'), + :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false), + :httponly => true + } + cookies[cookie_name] = cookie_options + end # Onthefly creation failed, display the registration form to fill/fix attributes def onthefly_creation_failed(user, auth_source_options = { }) diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/activities_controller.rb.svn-base --- a/app/controllers/.svn/text-base/activities_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/activities_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -23,7 +23,7 @@ events = @activity.events(@date_from, @date_to) - if events.empty? || stale?(:etag => [events.first, User.current]) + if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, User.current, current_language]) respond_to do |format| format.html { @events_by_day = events.group_by(&:event_date) diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/application_controller.rb.svn-base --- a/app/controllers/.svn/text-base/application_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/application_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -22,7 +22,7 @@ include Redmine::I18n layout 'base' - exempt_from_layout 'builder' + exempt_from_layout 'builder', 'rsb' # Remove broken cookie after upgrade from 0.8.x (#4292) # See https://rails.lighthouseapp.com/projects/8994/tickets/3360 @@ -71,10 +71,10 @@ elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action]) # RSS key authentication does not start a session User.find_by_rss_key(params[:key]) - elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format]) - if params[:key].present? && accept_key_auth_actions.include?(params[:action]) + elsif Setting.rest_api_enabled? && api_request? + if (key = api_key_from_request) && accept_key_auth_actions.include?(params[:action]) # Use API key - User.find_by_api_key(params[:key]) + User.find_by_api_key(key) else # HTTP Basic, either username/password or API key/random authenticate_with_http_basic do |username, password| @@ -349,6 +349,30 @@ per_page end + # Returns offset and limit used to retrieve objects + # for an API response based on offset, limit and page parameters + def api_offset_and_limit(options=params) + if options[:offset].present? + offset = options[:offset].to_i + if offset < 0 + offset = 0 + end + end + limit = options[:limit].to_i + if limit < 1 + limit = 25 + elsif limit > 100 + limit = 100 + end + if offset.nil? && options[:page].present? + offset = (options[:page].to_i - 1) * limit + offset = 0 if offset < 0 + end + offset ||= 0 + + [offset, limit] + end + # qvalues http header parser # code taken from webrick def parse_qvalues(value) @@ -378,6 +402,15 @@ def api_request? %w(xml json).include? params[:format] end + + # Returns the API key present in the request + def api_key_from_request + if params[:key].present? + params[:key] + elsif request.headers["X-Redmine-API-Key"].present? + request.headers["X-Redmine-API-Key"] + end + end # Renders a warning flash if obj has unsaved attachments def render_attachment_warning_if_needed(obj) @@ -413,5 +446,37 @@ { attribute => error } end.to_json end + + # Renders API response on validation failure + def render_validation_errors(object) + options = { :status => :unprocessable_entity, :layout => false } + options.merge!(case params[:format] + when 'xml'; { :xml => object.errors } + when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance + else + raise "Unknown format #{params[:format]} in #render_validation_errors" + end + ) + render options + end + # Overrides #default_template so that the api template + # is used automatically if it exists + def default_template(action_name = self.action_name) + if api_request? + begin + return self.view_paths.find_template(default_template_name(action_name), 'api') + rescue ::ActionView::MissingTemplate + # the api template was not found + # fallback to the default behaviour + end + end + super + end + + # Overrides #pick_layout so that #render with no arguments + # doesn't use the layout for api requests + def pick_layout(*args) + api_request? ? nil : super + end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/auto_completes_controller.rb.svn-base --- a/app/controllers/.svn/text-base/auto_completes_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/auto_completes_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -4,12 +4,14 @@ def issues @issues = [] q = params[:q].to_s + query = (params[:scope] == "all" && Setting.cross_project_issue_relations?) ? Issue : @project.issues if q.match(/^\d+$/) - @issues << @project.issues.visible.find_by_id(q.to_i) + @issues << query.visible.find_by_id(q.to_i) end unless q.blank? - @issues += @project.issues.visible.find(:all, :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%"], :limit => 10) + @issues += query.visible.find(:all, :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%"], :limit => 10) end + @issues.compact! render :layout => false end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/context_menus_controller.rb.svn-base --- a/app/controllers/.svn/text-base/context_menus_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/context_menus_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -2,7 +2,8 @@ helper :watchers def issues - @issues = Issue.find_all_by_id(params[:ids], :include => :project) + @issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project) + if (@issues.size == 1) @issue = @issues.first @allowed_statuses = @issue.new_statuses_allowed_to(User.current) diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/issue_relations_controller.rb.svn-base --- a/app/controllers/.svn/text-base/issue_relations_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/issue_relations_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -28,6 +28,7 @@ respond_to do |format| format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } format.js do + @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } render :update do |page| page.replace_html "relations", :partial => 'issues/relations' if @relation.errors.empty? @@ -47,7 +48,10 @@ end respond_to do |format| format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } - format.js { render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'} } + format.js { + @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } + render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'} + } end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/issues_controller.rb.svn-base --- a/app/controllers/.svn/text-base/issues_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/issues_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -44,6 +44,8 @@ include AttachmentsHelper helper :queries include QueriesHelper + helper :repositories + include RepositoriesHelper helper :sort include SortHelper include IssuesHelper @@ -65,27 +67,29 @@ sort_update(@query.sortable_columns) if @query.valid? - limit = case params[:format] + case params[:format] when 'csv', 'pdf' - Setting.issues_export_limit.to_i + @limit = Setting.issues_export_limit.to_i when 'atom' - Setting.feeds_limit.to_i + @limit = Setting.feeds_limit.to_i + when 'xml', 'json' + @offset, @limit = api_offset_and_limit else - per_page_option + @limit = per_page_option end @issue_count = @query.issue_count - @issue_pages = Paginator.new self, @issue_count, limit, params['page'] + @issue_pages = Paginator.new self, @issue_count, @limit, params['page'] + @offset ||= @issue_pages.current.offset @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version], :order => sort_clause, - :offset => @issue_pages.current.offset, - :limit => limit) + :offset => @offset, + :limit => @limit) @issue_count_by_group = @query.issue_count_by_group respond_to do |format| format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? } - format.xml { render :layout => false } - format.json { render :text => @issues.to_json, :layout => false } + format.api format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') } format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') } @@ -104,14 +108,14 @@ @journals.reverse! if User.current.wants_comments_in_reverse_order? @changesets = @issue.changesets.visible.all @changesets.reverse! if User.current.wants_comments_in_reverse_order? + @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } @allowed_statuses = @issue.new_statuses_allowed_to(User.current) @edit_allowed = User.current.allowed_to?(:edit_issues, @project) @priorities = IssuePriority.all @time_entry = TimeEntry.new respond_to do |format| format.html { render :template => 'issues/show.rhtml' } - format.xml { render :layout => false } - format.json { render :text => @issue.to_json, :layout => false } + format.api format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' } format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } end @@ -138,15 +142,13 @@ redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } : { :action => 'show', :id => @issue }) } - format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) } - format.json { render :text => @issue.to_json, :status => :created, :location => url_for(:controller => 'issues', :action => 'show'), :layout => false } + format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) } end return else respond_to do |format| format.html { render :action => 'new' } - format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return } - format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false } + format.api { render_validation_errors(@issue) } end end end @@ -171,8 +173,7 @@ respond_to do |format| format.html { redirect_back_or_default({:action => 'show', :id => @issue}) } - format.xml { head :ok } - format.json { head :ok } + format.api { head :ok } end else render_attachment_warning_if_needed(@issue) @@ -181,8 +182,7 @@ respond_to do |format| format.html { render :action => 'edit' } - format.xml { render :xml => @issue.errors, :status => :unprocessable_entity } - format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false } + format.api { render_validation_errors(@issue) } end end end @@ -232,17 +232,14 @@ TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues]) end else - unless params[:format] == 'xml' || params[:format] == 'json' - # display the destroy form if it's a user request - return - end + # display the destroy form if it's a user request + return unless api_request? end end @issues.each(&:destroy) respond_to do |format| format.html { redirect_back_or_default(:action => 'index', :project_id => @project) } - format.xml { head :ok } - format.json { head :ok } + format.api { head :ok } end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/journals_controller.rb.svn-base --- a/app/controllers/.svn/text-base/journals_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/journals_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,11 +16,13 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class JournalsController < ApplicationController - before_filter :find_journal, :only => [:edit] + before_filter :find_journal, :only => [:edit, :diff] before_filter :find_issue, :only => [:new] before_filter :find_optional_project, :only => [:index] + before_filter :authorize, :only => [:new, :edit, :diff] accept_key_auth :index - + menu_item :issues + helper :issues helper :queries include QueriesHelper @@ -42,6 +44,17 @@ render_404 end + def diff + @issue = @journal.issue + if params[:detail_id].present? + @detail = @journal.details.find_by_id(params[:detail_id]) + else + @detail = @journal.details.detect {|d| d.prop_key == 'description'} + end + (render_404; return false) unless @issue && @detail + @diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value) + end + def new journal = Journal.find(params[:journal_id]) if params[:journal_id] if journal @@ -66,6 +79,7 @@ end def edit + (render_403; return false) unless @journal.editable_by?(User.current) if request.post? @journal.update_attributes(:notes => params[:notes]) if params[:notes] @journal.destroy if @journal.details.empty? && @journal.notes.blank? @@ -74,13 +88,21 @@ format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id } format.js { render :action => 'update' } end + else + respond_to do |format| + format.html { + # TODO: implement non-JS journal update + render :nothing => true + } + format.js + end end end -private + private + def find_journal @journal = Journal.find(params[:id]) - (render_403; return false) unless @journal.editable_by?(User.current) @project = @journal.journalized.project rescue ActiveRecord::RecordNotFound render_404 diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/my_controller.rb.svn-base --- a/app/controllers/.svn/text-base/my_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/my_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -19,6 +19,7 @@ before_filter :require_login helper :issues + helper :users helper :custom_fields BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues, @@ -53,21 +54,18 @@ @user = User.current @pref = @user.pref if request.post? - @user.attributes = params[:user] - @user.mail_notification = params[:notification_option] || 'only_my_events' + @user.safe_attributes = params[:user] @user.pref.attributes = params[:pref] @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') if @user.save @user.pref.save - @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : []) + @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) set_language_if_valid @user.language flash[:notice] = l(:notice_account_updated) redirect_to :action => 'account' return end end - @notification_options = @user.valid_notification_options - @notification_option = @user.mail_notification #? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected') end # Manage user's password diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/news_controller.rb.svn-base --- a/app/controllers/.svn/text-base/news_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/news_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -26,15 +26,26 @@ accept_key_auth :index def index - @news_pages, @newss = paginate :news, - :per_page => 10, - :conditions => Project.allowed_to_condition(User.current, :view_news, :project => @project), - :include => [:author, :project], - :order => "#{News.table_name}.created_on DESC" + case params[:format] + when 'xml', 'json' + @offset, @limit = api_offset_and_limit + else + @limit = 10 + end + + scope = @project ? @project.news.visible : News.visible + + @news_count = scope.count + @news_pages = Paginator.new self, @news_count, @limit, params['page'] + @offset ||= @news_pages.current.offset + @newss = scope.all(:include => [:author, :project], + :order => "#{News.table_name}.created_on DESC", + :offset => @offset, + :limit => @limit) + respond_to do |format| format.html { render :layout => false if request.xhr? } - format.xml { render :xml => @newss.to_xml } - format.json { render :json => @newss.to_json } + format.api format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") } end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/projects_controller.rb.svn-base --- a/app/controllers/.svn/text-base/projects_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/projects_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -24,7 +24,7 @@ before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy] before_filter :authorize_global, :only => [:new, :create] before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] - accept_key_auth :index + accept_key_auth :index, :show, :create, :update, :destroy after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller| if controller.request.post? @@ -32,9 +32,6 @@ end end - # TODO: convert to PUT only - verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed } - helper :sort include SortHelper helper :custom_fields @@ -52,8 +49,10 @@ format.html { @projects = Project.visible.find(:all, :order => 'lft') } - format.xml { - @projects = Project.visible.find(:all, :order => 'lft') + format.api { + @offset, @limit = api_offset_and_limit + @project_count = Project.visible.count + @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => 'lft') } format.atom { projects = Project.visible.find(:all, :order => 'created_on DESC', @@ -67,19 +66,15 @@ @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @trackers = Tracker.all @project = Project.new(params[:project]) - - @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers? - @project.trackers = Tracker.all - @project.is_public = Setting.default_projects_public? - @project.enabled_module_names = Setting.default_projects_modules end + verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed } def create @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @trackers = Tracker.all - @project = Project.new(params[:project]) + @project = Project.new + @project.safe_attributes = params[:project] - @project.enabled_module_names = params[:enabled_modules] if validate_parent_id && @project.save @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') # Add current user as a project member if he is not admin @@ -93,12 +88,12 @@ flash[:notice] = l(:notice_successful_create) redirect_to :controller => 'projects', :action => 'settings', :id => @project } - format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } + format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } end else respond_to do |format| format.html { render :action => 'new' } - format.xml { render :xml => @project.errors, :status => :unprocessable_entity } + format.api { render_validation_errors(@project) } end end @@ -120,18 +115,19 @@ end else Mailer.with_deliveries(params[:notifications] == '1') do - @project = Project.new(params[:project]) + @project = Project.new + @project.safe_attributes = params[:project] @project.enabled_module_names = params[:enabled_modules] if validate_parent_id && @project.copy(@source_project, :only => params[:only]) @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'projects', :action => 'settings' + redirect_to :controller => 'projects', :action => 'settings', :id => @project elsif !@project.new_record? # Project was created # But some objects were not copied due to validation failures # (eg. issues from disabled trackers) # TODO: inform about that - redirect_to :controller => 'projects', :action => 'settings' + redirect_to :controller => 'projects', :action => 'settings', :id => @project end end end @@ -169,7 +165,7 @@ respond_to do |format| format.html - format.xml + format.api end end @@ -185,8 +181,10 @@ def edit end + # TODO: convert to PUT only + verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed } def update - @project.attributes = params[:project] + @project.safe_attributes = params[:project] if validate_parent_id && @project.save @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') respond_to do |format| @@ -194,7 +192,7 @@ flash[:notice] = l(:notice_successful_update) redirect_to :action => 'settings', :id => @project } - format.xml { head :ok } + format.api { head :ok } end else respond_to do |format| @@ -202,13 +200,14 @@ settings render :action => 'settings' } - format.xml { render :xml => @project.errors, :status => :unprocessable_entity } + format.api { render_validation_errors(@project) } end end end - + + verify :method => :post, :only => :modules, :render => {:nothing => true, :status => :method_not_allowed } def modules - @project.enabled_module_names = params[:enabled_modules] + @project.enabled_module_names = params[:enabled_module_names] flash[:notice] = l(:notice_successful_update) redirect_to :action => 'settings', :id => @project, :tab => 'modules' end @@ -233,11 +232,11 @@ if request.get? # display confirmation view else - if params[:format] == 'xml' || params[:confirm] + if api_request? || params[:confirm] @project_to_destroy.destroy respond_to do |format| format.html { redirect_to :controller => 'admin', :action => 'projects' } - format.xml { head :ok } + format.api { head :ok } end end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/repositories_controller.rb.svn-base --- a/app/controllers/.svn/text-base/repositories_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/repositories_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -67,13 +67,13 @@ redirect_to :action => 'committers', :id => @project end end - + def destroy @repository.destroy redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository' end - - def show + + def show @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? @entries = @repository.entries(@path, @rev) @@ -88,30 +88,31 @@ end alias_method :browse, :show - + def changes @entry = @repository.entry(@path, @rev) (show_error_not_found; return) unless @entry @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i) @properties = @repository.properties(@path, @rev) + @changeset = @repository.find_changeset_by_name(@rev) end - + def revisions @changeset_count = @repository.changesets.count @changeset_pages = Paginator.new self, @changeset_count, - per_page_option, - params['page'] + per_page_option, + params['page'] @changesets = @repository.changesets.find(:all, - :limit => @changeset_pages.items_per_page, - :offset => @changeset_pages.current.offset, - :include => [:user, :repository]) + :limit => @changeset_pages.items_per_page, + :offset => @changeset_pages.current.offset, + :include => [:user, :repository]) respond_to do |format| format.html { render :layout => false if request.xhr? } format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") } end end - + def entry @entry = @repository.entry(@path, @rev) (show_error_not_found; return) unless @entry @@ -121,24 +122,28 @@ @content = @repository.cat(@path, @rev) (show_error_not_found; return) unless @content - if 'raw' == params[:format] || @content.is_binary_data? || (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte) + if 'raw' == params[:format] || @content.is_binary_data? || + (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte) # Force the download - send_data @content, :filename => @path.split('/').last + send_data @content, :filename => filename_for_content_disposition(@path.split('/').last) else # Prevent empty lines when displaying a file with Windows style eol @content.gsub!("\r\n", "\n") + @changeset = @repository.find_changeset_by_name(@rev) end end - + def annotate @entry = @repository.entry(@path, @rev) (show_error_not_found; return) unless @entry - + @annotate = @repository.scm.annotate(@path, @rev) (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty? + @changeset = @repository.find_changeset_by_name(@rev) end - + def revision + raise ChangesetNotFound if @rev.blank? @changeset = @repository.find_changeset_by_name(@rev) raise ChangesetNotFound unless @changeset @@ -149,7 +154,7 @@ rescue ChangesetNotFound show_error_not_found end - + def diff if params[:format] == 'diff' @diff = @repository.diff(@path, @rev, @rev_to) @@ -174,14 +179,18 @@ @diff = @repository.diff(@path, @rev, @rev_to) show_error_not_found unless @diff end + + @changeset = @repository.find_changeset_by_name(@rev) + @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil + @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to) end end - - def stats + + def stats end - + def graph - data = nil + data = nil case params[:graph] when "commits_per_month" data = graph_commits_per_month(@repository) @@ -196,7 +205,10 @@ end end -private + private + + REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i + def find_repository @project = Project.find(params[:id]) @repository = @project.repository @@ -205,6 +217,12 @@ @path ||= '' @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip @rev_to = params[:rev_to] + + unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE) + if @repository.branches.blank? + raise InvalidRevisionParam + end + end rescue ActiveRecord::RecordNotFound render_404 rescue InvalidRevisionParam @@ -212,7 +230,7 @@ end def show_error_not_found - render_error l(:error_scm_not_found) + render_error :message => l(:error_scm_not_found), :status => 404 end # Handler for Redmine::Scm::Adapters::CommandFailed exception diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/settings_controller.rb.svn-base --- a/app/controllers/.svn/text-base/settings_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/settings_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -44,6 +44,8 @@ @guessed_host_and_path = request.host_with_port.dup @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank? + + Redmine::Themes.rescan end def plugin diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/timelog_controller.rb.svn-base --- a/app/controllers/.svn/text-base/timelog_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/timelog_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2010 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,10 +18,11 @@ class TimelogController < ApplicationController menu_item :issues before_filter :find_project, :only => [:new, :create] - before_filter :find_time_entry, :only => [:edit, :update, :destroy] + before_filter :find_time_entry, :only => [:show, :edit, :update, :destroy] before_filter :authorize, :except => [:index] before_filter :find_optional_project, :only => [:index] - + accept_key_auth :index, :show, :create, :update, :destroy + helper :sort include SortHelper helper :issues @@ -66,6 +67,16 @@ render :layout => !request.xhr? } + format.api { + @entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions) + @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] + @entries = TimeEntry.find(:all, + :include => [:project, :activity, :user, {:issue => :tracker}], + :conditions => cond.conditions, + :order => sort_clause, + :limit => @entry_pages.items_per_page, + :offset => @entry_pages.current.offset) + } format.atom { entries = TimeEntry.find(:all, :include => [:project, :activity, :user, {:issue => :tracker}], @@ -85,6 +96,14 @@ end end end + + def show + respond_to do |format| + # TODO: Implement html response + format.html { render :nothing => true, :status => 406 } + format.api + end + end def new @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) @@ -102,10 +121,18 @@ call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) if @time_entry.save - flash[:notice] = l(:notice_successful_update) - redirect_back_or_default :action => 'index', :project_id => @time_entry.project + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_back_or_default :action => 'index', :project_id => @time_entry.project + } + format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) } + end else - render :action => 'edit' + respond_to do |format| + format.html { render :action => 'edit' } + format.api { render_validation_errors(@time_entry) } + end end end @@ -122,21 +149,40 @@ call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) if @time_entry.save - flash[:notice] = l(:notice_successful_update) - redirect_back_or_default :action => 'index', :project_id => @time_entry.project + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_back_or_default :action => 'index', :project_id => @time_entry.project + } + format.api { head :ok } + end else - render :action => 'edit' + respond_to do |format| + format.html { render :action => 'edit' } + format.api { render_validation_errors(@time_entry) } + end end end verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed } def destroy if @time_entry.destroy && @time_entry.destroyed? - flash[:notice] = l(:notice_successful_delete) + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_delete) + redirect_to :back + } + format.api { head :ok } + end else - flash[:error] = l(:notice_unable_delete_time_entry) + respond_to do |format| + format.html { + flash[:error] = l(:notice_unable_delete_time_entry) + redirect_to :back + } + format.api { render_validation_errors(@time_entry) } + end end - redirect_to :back rescue ::ActionController::RedirectBackError redirect_to :action => 'index', :project_id => @time_entry.project end @@ -154,11 +200,11 @@ end def find_project - if params[:issue_id] - @issue = Issue.find(params[:issue_id]) + if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present? + @issue = Issue.find(issue_id) @project = @issue.project - elsif params[:project_id] - @project = Project.find(params[:project_id]) + elsif (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present? + @project = Project.find(project_id) else render_404 return false diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/users_controller.rb.svn-base --- a/app/controllers/.svn/text-base/users_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/users_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2009 Jean-Philippe Lang +# Copyright (C) 2006-2010 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,6 +19,8 @@ layout 'admin' before_filter :require_admin, :except => :show + before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership] + accept_key_auth :index, :show, :create, :update, :destroy helper :sort include SortHelper @@ -29,6 +31,13 @@ sort_init 'login', 'asc' sort_update %w(login firstname lastname mail admin created_on last_login_on) + case params[:format] + when 'xml', 'json' + @offset, @limit = api_offset_and_limit + else + @limit = per_page_option + end + @status = params[:status] ? params[:status].to_i : 1 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status]) @@ -38,20 +47,21 @@ end @user_count = User.count(:conditions => c.conditions) - @user_pages = Paginator.new self, @user_count, - per_page_option, - params['page'] - @users = User.find :all,:order => sort_clause, + @user_pages = Paginator.new self, @user_count, @limit, params['page'] + @offset ||= @user_pages.current.offset + @users = User.find :all, + :order => sort_clause, :conditions => c.conditions, - :limit => @user_pages.items_per_page, - :offset => @user_pages.current.offset + :limit => @limit, + :offset => @offset - render :layout => !request.xhr? + respond_to do |format| + format.html { render :layout => !request.xhr? } + format.api + end end def show - @user = User.find(params[:id]) - # show projects based on current user visibility @memberships = @user.memberships.all(:conditions => Project.visible_by(User.current)) @@ -64,104 +74,119 @@ return end end - render :layout => 'base' - - rescue ActiveRecord::RecordNotFound - render_404 + + respond_to do |format| + format.html { render :layout => 'base' } + format.api + end end def new - @notification_options = User::MAIL_NOTIFICATION_OPTIONS - @notification_option = Setting.default_notification_option - - @user = User.new(:language => Setting.default_language) + @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) @auth_sources = AuthSource.find(:all) end verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed } def create - @notification_options = User::MAIL_NOTIFICATION_OPTIONS - @notification_option = Setting.default_notification_option - - @user = User.new(params[:user]) + @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) + @user.safe_attributes = params[:user] @user.admin = params[:user][:admin] || false @user.login = params[:user][:login] - @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id + @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id # TODO: Similar to My#account - @user.mail_notification = params[:notification_option] || 'only_my_events' @user.pref.attributes = params[:pref] @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') if @user.save @user.pref.save - @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : []) + @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) - Mailer.deliver_account_information(@user, params[:password]) if params[:send_information] - flash[:notice] = l(:notice_successful_create) - redirect_to(params[:continue] ? {:controller => 'users', :action => 'new'} : - {:controller => 'users', :action => 'edit', :id => @user}) - return + Mailer.deliver_account_information(@user, params[:user][:password]) if params[:send_information] + + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_create) + redirect_to(params[:continue] ? + {:controller => 'users', :action => 'new'} : + {:controller => 'users', :action => 'edit', :id => @user} + ) + } + format.api { render :action => 'show', :status => :created, :location => user_url(@user) } + end else @auth_sources = AuthSource.find(:all) - @notification_option = @user.mail_notification + # Clear password input + @user.password = @user.password_confirmation = nil - render :action => 'new' + respond_to do |format| + format.html { render :action => 'new' } + format.api { render_validation_errors(@user) } + end end end def edit - @user = User.find(params[:id]) - @notification_options = @user.valid_notification_options - @notification_option = @user.mail_notification - @auth_sources = AuthSource.find(:all) @membership ||= Member.new end verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed } def update - @user = User.find(params[:id]) - @notification_options = @user.valid_notification_options - @notification_option = @user.mail_notification - @user.admin = params[:user][:admin] if params[:user][:admin] @user.login = params[:user][:login] if params[:user][:login] - if params[:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?) - @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] + if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?) + @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] end - @user.group_ids = params[:user][:group_ids] if params[:user][:group_ids] - @user.attributes = params[:user] + @user.safe_attributes = params[:user] # Was the account actived ? (do it before User#save clears the change) was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE]) # TODO: Similar to My#account - @user.mail_notification = params[:notification_option] || 'only_my_events' @user.pref.attributes = params[:pref] @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') if @user.save @user.pref.save - @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : []) + @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) if was_activated Mailer.deliver_account_activated(@user) - elsif @user.active? && params[:send_information] && !params[:password].blank? && @user.auth_source_id.nil? - Mailer.deliver_account_information(@user, params[:password]) + elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil? + Mailer.deliver_account_information(@user, params[:user][:password]) end - flash[:notice] = l(:notice_successful_update) - redirect_to :back + + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_to :back + } + format.api { head :ok } + end else @auth_sources = AuthSource.find(:all) @membership ||= Member.new + # Clear password input + @user.password = @user.password_confirmation = nil - render :action => :edit + respond_to do |format| + format.html { render :action => :edit } + format.api { render_validation_errors(@user) } + end end rescue ::ActionController::RedirectBackError redirect_to :controller => 'users', :action => 'edit', :id => @user end + verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed } + def destroy + @user.destroy + respond_to do |format| + format.html { redirect_to(users_url) } + format.api { head :ok } + end + end + def edit_membership - @user = User.find(params[:id]) @membership = Member.edit_membership(params[:membership_id], params[:membership], @user) @membership.save if request.post? respond_to do |format| @@ -184,7 +209,6 @@ end def destroy_membership - @user = User.find(params[:id]) @membership = Member.find(params[:membership_id]) if request.post? && @membership.deletable? @membership.destroy @@ -194,4 +218,17 @@ format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} } end end + + private + + def find_user + if params[:id] == 'current' + require_login || return + @user = User.current + else + @user = User.find(params[:id]) + end + rescue ActiveRecord::RecordNotFound + render_404 + end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/wiki_controller.rb.svn-base --- a/app/controllers/.svn/text-base/wiki_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/wiki_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -125,6 +125,8 @@ render_attachment_warning_if_needed(@page) call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) redirect_to :action => 'show', :project_id => @project, :id => @page.title + else + render :action => 'edit' end rescue ActiveRecord::StaleObjectError diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/.svn/text-base/workflows_controller.rb.svn-base --- a/app/controllers/.svn/text-base/workflows_controller.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/.svn/text-base/workflows_controller.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -32,14 +32,17 @@ if request.post? Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) - (params[:issue_status] || []).each { |old, news| - news.each { |new| - @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new) + (params[:issue_status] || []).each { |status_id, transitions| + transitions.each { |new_status_id, options| + author = options.is_a?(Array) && options.include?('author') && !options.include?('always') + assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always') + @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee) } } if @role.save flash[:notice] = l(:notice_successful_update) redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker + return end end @@ -48,6 +51,14 @@ @statuses = @tracker.issue_statuses end @statuses ||= IssueStatus.find(:all, :order => 'position') + + if @tracker && @role && @statuses.any? + workflows = Workflow.all(:conditions => {:role_id => @role.id, :tracker_id => @tracker.id}) + @workflows = {} + @workflows['always'] = workflows.select {|w| !w.author && !w.assignee} + @workflows['author'] = workflows.select {|w| w.author} + @workflows['assignee'] = workflows.select {|w| w.assignee} + end end def copy diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/account_controller.rb --- a/app/controllers/account_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/account_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -216,12 +216,24 @@ self.logged_user = user # generate a key and set cookie if autologin if params[:autologin] && Setting.autologin? - token = Token.create(:user => user, :action => 'autologin') - cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now } + set_autologin_cookie(user) end call_hook(:controller_account_success_authentication_after, {:user => user }) redirect_back_or_default :controller => 'my', :action => 'page' end + + def set_autologin_cookie(user) + token = Token.create(:user => user, :action => 'autologin') + cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin' + cookie_options = { + :value => token.value, + :expires => 1.year.from_now, + :path => (Redmine::Configuration['autologin_cookie_path'] || '/'), + :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false), + :httponly => true + } + cookies[cookie_name] = cookie_options + end # Onthefly creation failed, display the registration form to fill/fix attributes def onthefly_creation_failed(user, auth_source_options = { }) diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/activities_controller.rb --- a/app/controllers/activities_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/activities_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -23,7 +23,7 @@ events = @activity.events(@date_from, @date_to) - if events.empty? || stale?(:etag => [events.first, User.current]) + if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, User.current, current_language]) respond_to do |format| format.html { @events_by_day = events.group_by(&:event_date) diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/application_controller.rb --- a/app/controllers/application_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/application_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -22,7 +22,7 @@ include Redmine::I18n layout 'base' - exempt_from_layout 'builder' + exempt_from_layout 'builder', 'rsb' # Remove broken cookie after upgrade from 0.8.x (#4292) # See https://rails.lighthouseapp.com/projects/8994/tickets/3360 @@ -71,10 +71,10 @@ elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action]) # RSS key authentication does not start a session User.find_by_rss_key(params[:key]) - elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format]) - if params[:key].present? && accept_key_auth_actions.include?(params[:action]) + elsif Setting.rest_api_enabled? && api_request? + if (key = api_key_from_request) && accept_key_auth_actions.include?(params[:action]) # Use API key - User.find_by_api_key(params[:key]) + User.find_by_api_key(key) else # HTTP Basic, either username/password or API key/random authenticate_with_http_basic do |username, password| @@ -355,6 +355,30 @@ per_page end + # Returns offset and limit used to retrieve objects + # for an API response based on offset, limit and page parameters + def api_offset_and_limit(options=params) + if options[:offset].present? + offset = options[:offset].to_i + if offset < 0 + offset = 0 + end + end + limit = options[:limit].to_i + if limit < 1 + limit = 25 + elsif limit > 100 + limit = 100 + end + if offset.nil? && options[:page].present? + offset = (options[:page].to_i - 1) * limit + offset = 0 if offset < 0 + end + offset ||= 0 + + [offset, limit] + end + # qvalues http header parser # code taken from webrick def parse_qvalues(value) @@ -384,6 +408,15 @@ def api_request? %w(xml json).include? params[:format] end + + # Returns the API key present in the request + def api_key_from_request + if params[:key].present? + params[:key] + elsif request.headers["X-Redmine-API-Key"].present? + request.headers["X-Redmine-API-Key"] + end + end # Renders a warning flash if obj has unsaved attachments def render_attachment_warning_if_needed(obj) @@ -419,5 +452,37 @@ { attribute => error } end.to_json end + + # Renders API response on validation failure + def render_validation_errors(object) + options = { :status => :unprocessable_entity, :layout => false } + options.merge!(case params[:format] + when 'xml'; { :xml => object.errors } + when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance + else + raise "Unknown format #{params[:format]} in #render_validation_errors" + end + ) + render options + end + # Overrides #default_template so that the api template + # is used automatically if it exists + def default_template(action_name = self.action_name) + if api_request? + begin + return self.view_paths.find_template(default_template_name(action_name), 'api') + rescue ::ActionView::MissingTemplate + # the api template was not found + # fallback to the default behaviour + end + end + super + end + + # Overrides #pick_layout so that #render with no arguments + # doesn't use the layout for api requests + def pick_layout(*args) + api_request? ? nil : super + end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/auto_completes_controller.rb --- a/app/controllers/auto_completes_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/auto_completes_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -4,12 +4,14 @@ def issues @issues = [] q = params[:q].to_s + query = (params[:scope] == "all" && Setting.cross_project_issue_relations?) ? Issue : @project.issues if q.match(/^\d+$/) - @issues << @project.issues.visible.find_by_id(q.to_i) + @issues << query.visible.find_by_id(q.to_i) end unless q.blank? - @issues += @project.issues.visible.find(:all, :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%"], :limit => 10) + @issues += query.visible.find(:all, :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%"], :limit => 10) end + @issues.compact! render :layout => false end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/context_menus_controller.rb --- a/app/controllers/context_menus_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/context_menus_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -2,7 +2,8 @@ helper :watchers def issues - @issues = Issue.find_all_by_id(params[:ids], :include => :project) + @issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project) + if (@issues.size == 1) @issue = @issues.first @allowed_statuses = @issue.new_statuses_allowed_to(User.current) diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/issue_relations_controller.rb --- a/app/controllers/issue_relations_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/issue_relations_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -28,6 +28,7 @@ respond_to do |format| format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } format.js do + @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } render :update do |page| page.replace_html "relations", :partial => 'issues/relations' if @relation.errors.empty? @@ -47,7 +48,10 @@ end respond_to do |format| format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } - format.js { render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'} } + format.js { + @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } + render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'} + } end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/issues_controller.rb --- a/app/controllers/issues_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/issues_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -44,6 +44,8 @@ include AttachmentsHelper helper :queries include QueriesHelper + helper :repositories + include RepositoriesHelper helper :sort include SortHelper include IssuesHelper @@ -65,27 +67,29 @@ sort_update(@query.sortable_columns) if @query.valid? - limit = case params[:format] + case params[:format] when 'csv', 'pdf' - Setting.issues_export_limit.to_i + @limit = Setting.issues_export_limit.to_i when 'atom' - Setting.feeds_limit.to_i + @limit = Setting.feeds_limit.to_i + when 'xml', 'json' + @offset, @limit = api_offset_and_limit else - per_page_option + @limit = per_page_option end @issue_count = @query.issue_count - @issue_pages = Paginator.new self, @issue_count, limit, params['page'] + @issue_pages = Paginator.new self, @issue_count, @limit, params['page'] + @offset ||= @issue_pages.current.offset @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version], :order => sort_clause, - :offset => @issue_pages.current.offset, - :limit => limit) + :offset => @offset, + :limit => @limit) @issue_count_by_group = @query.issue_count_by_group respond_to do |format| format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? } - format.xml { render :layout => false } - format.json { render :text => @issues.to_json, :layout => false } + format.api format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') } format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') } @@ -104,14 +108,14 @@ @journals.reverse! if User.current.wants_comments_in_reverse_order? @changesets = @issue.changesets.visible.all @changesets.reverse! if User.current.wants_comments_in_reverse_order? + @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } @allowed_statuses = @issue.new_statuses_allowed_to(User.current) @edit_allowed = User.current.allowed_to?(:edit_issues, @project) @priorities = IssuePriority.all @time_entry = TimeEntry.new respond_to do |format| format.html { render :template => 'issues/show.rhtml' } - format.xml { render :layout => false } - format.json { render :text => @issue.to_json, :layout => false } + format.api format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' } format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } end @@ -147,15 +151,13 @@ redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } : { :action => 'show', :id => @issue }) } - format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) } - format.json { render :text => @issue.to_json, :status => :created, :location => url_for(:controller => 'issues', :action => 'show'), :layout => false } + format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) } end return else respond_to do |format| format.html { render :action => 'new' } - format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return } - format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false } + format.api { render_validation_errors(@issue) } end end end @@ -180,8 +182,7 @@ respond_to do |format| format.html { redirect_back_or_default({:action => 'show', :id => @issue}) } - format.xml { head :ok } - format.json { head :ok } + format.api { head :ok } end else render_attachment_warning_if_needed(@issue) @@ -190,8 +191,7 @@ respond_to do |format| format.html { render :action => 'edit' } - format.xml { render :xml => @issue.errors, :status => :unprocessable_entity } - format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false } + format.api { render_validation_errors(@issue) } end end end @@ -241,17 +241,14 @@ TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues]) end else - unless params[:format] == 'xml' || params[:format] == 'json' - # display the destroy form if it's a user request - return - end + # display the destroy form if it's a user request + return unless api_request? end end @issues.each(&:destroy) respond_to do |format| format.html { redirect_back_or_default(:action => 'index', :project_id => @project) } - format.xml { head :ok } - format.json { head :ok } + format.api { head :ok } end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/journals_controller.rb --- a/app/controllers/journals_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/journals_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -16,11 +16,13 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class JournalsController < ApplicationController - before_filter :find_journal, :only => [:edit] + before_filter :find_journal, :only => [:edit, :diff] before_filter :find_issue, :only => [:new] before_filter :find_optional_project, :only => [:index] + before_filter :authorize, :only => [:new, :edit, :diff] accept_key_auth :index - + menu_item :issues + helper :issues helper :queries include QueriesHelper @@ -42,6 +44,17 @@ render_404 end + def diff + @issue = @journal.issue + if params[:detail_id].present? + @detail = @journal.details.find_by_id(params[:detail_id]) + else + @detail = @journal.details.detect {|d| d.prop_key == 'description'} + end + (render_404; return false) unless @issue && @detail + @diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value) + end + def new journal = Journal.find(params[:journal_id]) if params[:journal_id] if journal @@ -66,6 +79,7 @@ end def edit + (render_403; return false) unless @journal.editable_by?(User.current) if request.post? @journal.update_attributes(:notes => params[:notes]) if params[:notes] @journal.destroy if @journal.details.empty? && @journal.notes.blank? @@ -74,13 +88,21 @@ format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id } format.js { render :action => 'update' } end + else + respond_to do |format| + format.html { + # TODO: implement non-JS journal update + render :nothing => true + } + format.js + end end end -private + private + def find_journal @journal = Journal.find(params[:id]) - (render_403; return false) unless @journal.editable_by?(User.current) @project = @journal.journalized.project rescue ActiveRecord::RecordNotFound render_404 diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/my_controller.rb --- a/app/controllers/my_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/my_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -19,6 +19,7 @@ before_filter :require_login helper :issues + helper :users helper :custom_fields BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues, @@ -62,8 +63,7 @@ end if request.post? - @user.attributes = params[:user] - @user.mail_notification = params[:notification_option] || 'only_my_events' + @user.safe_attributes = params[:user] @user.pref.attributes = params[:pref] @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') @@ -88,15 +88,13 @@ if @user.save @user.pref.save - @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : []) + @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) set_language_if_valid @user.language flash[:notice] = l(:notice_account_updated) redirect_to :action => 'account' return end end - @notification_options = @user.valid_notification_options - @notification_option = @user.mail_notification #? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected') end # Manage user's password diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/news_controller.rb --- a/app/controllers/news_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/news_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -26,15 +26,26 @@ accept_key_auth :index def index - @news_pages, @newss = paginate :news, - :per_page => 10, - :conditions => Project.allowed_to_condition(User.current, :view_news, :project => @project), - :include => [:author, :project], - :order => "#{News.table_name}.created_on DESC" + case params[:format] + when 'xml', 'json' + @offset, @limit = api_offset_and_limit + else + @limit = 10 + end + + scope = @project ? @project.news.visible : News.visible + + @news_count = scope.count + @news_pages = Paginator.new self, @news_count, @limit, params['page'] + @offset ||= @news_pages.current.offset + @newss = scope.all(:include => [:author, :project], + :order => "#{News.table_name}.created_on DESC", + :offset => @offset, + :limit => @limit) + respond_to do |format| format.html { render :layout => false if request.xhr? } - format.xml { render :xml => @newss.to_xml } - format.json { render :json => @newss.to_json } + format.api format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") } end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/projects_controller.rb --- a/app/controllers/projects_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/projects_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -24,7 +24,7 @@ before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy] before_filter :authorize_global, :only => [:new, :create] before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] - accept_key_auth :index + accept_key_auth :index, :show, :create, :update, :destroy after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller| if controller.request.post? @@ -32,9 +32,6 @@ end end - # TODO: convert to PUT only - verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed } - helper :sort include SortHelper helper :custom_fields @@ -63,8 +60,10 @@ end render :template => 'projects/index.rhtml', :layout => !request.xhr? } - format.xml { - @projects = Project.visible.find(:all, :order => 'lft') + format.api { + @offset, @limit = api_offset_and_limit + @project_count = Project.visible.count + @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => 'lft') } format.atom { projects = Project.visible.find(:all, :order => 'created_on DESC', @@ -78,19 +77,15 @@ @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @trackers = Tracker.all @project = Project.new(params[:project]) - - @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers? - @project.trackers = Tracker.all - @project.is_public = Setting.default_projects_public? - @project.enabled_module_names = Setting.default_projects_modules end + verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed } def create @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @trackers = Tracker.all - @project = Project.new(params[:project]) + @project = Project.new + @project.safe_attributes = params[:project] - @project.enabled_module_names = params[:enabled_modules] if validate_parent_id && @project.save @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') # Add current user as a project member if he is not admin @@ -104,12 +99,12 @@ flash[:notice] = l(:notice_successful_create) redirect_to :controller => 'projects', :action => 'settings', :id => @project } - format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } + format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } end else respond_to do |format| format.html { render :action => 'new' } - format.xml { render :xml => @project.errors, :status => :unprocessable_entity } + format.api { render_validation_errors(@project) } end end @@ -131,18 +126,19 @@ end else Mailer.with_deliveries(params[:notifications] == '1') do - @project = Project.new(params[:project]) + @project = Project.new + @project.safe_attributes = params[:project] @project.enabled_module_names = params[:enabled_modules] if validate_parent_id && @project.copy(@source_project, :only => params[:only]) @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'projects', :action => 'settings' + redirect_to :controller => 'projects', :action => 'settings', :id => @project elsif !@project.new_record? # Project was created # But some objects were not copied due to validation failures # (eg. issues from disabled trackers) # TODO: inform about that - redirect_to :controller => 'projects', :action => 'settings' + redirect_to :controller => 'projects', :action => 'settings', :id => @project end end end @@ -180,7 +176,7 @@ respond_to do |format| format.html - format.xml + format.api end end @@ -196,8 +192,10 @@ def edit end + # TODO: convert to PUT only + verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed } def update - @project.attributes = params[:project] + @project.safe_attributes = params[:project] if validate_parent_id && @project.save @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') respond_to do |format| @@ -205,7 +203,7 @@ flash[:notice] = l(:notice_successful_update) redirect_to :action => 'settings', :id => @project } - format.xml { head :ok } + format.api { head :ok } end else respond_to do |format| @@ -213,13 +211,14 @@ settings render :action => 'settings' } - format.xml { render :xml => @project.errors, :status => :unprocessable_entity } + format.api { render_validation_errors(@project) } end end end - + + verify :method => :post, :only => :modules, :render => {:nothing => true, :status => :method_not_allowed } def modules - @project.enabled_module_names = params[:enabled_modules] + @project.enabled_module_names = params[:enabled_module_names] flash[:notice] = l(:notice_successful_update) redirect_to :action => 'settings', :id => @project, :tab => 'modules' end @@ -244,11 +243,11 @@ if request.get? # display confirmation view else - if params[:format] == 'xml' || params[:confirm] + if api_request? || params[:confirm] @project_to_destroy.destroy respond_to do |format| format.html { redirect_to :controller => 'admin', :action => 'projects' } - format.xml { head :ok } + format.api { head :ok } end end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/repositories_controller.rb --- a/app/controllers/repositories_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/repositories_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -67,13 +67,13 @@ redirect_to :action => 'committers', :id => @project end end - + def destroy @repository.destroy redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository' end - - def show + + def show @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? @entries = @repository.entries(@path, @rev) @@ -88,30 +88,31 @@ end alias_method :browse, :show - + def changes @entry = @repository.entry(@path, @rev) (show_error_not_found; return) unless @entry @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i) @properties = @repository.properties(@path, @rev) + @changeset = @repository.find_changeset_by_name(@rev) end - + def revisions @changeset_count = @repository.changesets.count @changeset_pages = Paginator.new self, @changeset_count, - per_page_option, - params['page'] + per_page_option, + params['page'] @changesets = @repository.changesets.find(:all, - :limit => @changeset_pages.items_per_page, - :offset => @changeset_pages.current.offset, - :include => [:user, :repository]) + :limit => @changeset_pages.items_per_page, + :offset => @changeset_pages.current.offset, + :include => [:user, :repository]) respond_to do |format| format.html { render :layout => false if request.xhr? } format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") } end end - + def entry @entry = @repository.entry(@path, @rev) (show_error_not_found; return) unless @entry @@ -121,24 +122,28 @@ @content = @repository.cat(@path, @rev) (show_error_not_found; return) unless @content - if 'raw' == params[:format] || @content.is_binary_data? || (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte) + if 'raw' == params[:format] || @content.is_binary_data? || + (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte) # Force the download - send_data @content, :filename => @path.split('/').last + send_data @content, :filename => filename_for_content_disposition(@path.split('/').last) else # Prevent empty lines when displaying a file with Windows style eol @content.gsub!("\r\n", "\n") + @changeset = @repository.find_changeset_by_name(@rev) end end - + def annotate @entry = @repository.entry(@path, @rev) (show_error_not_found; return) unless @entry - + @annotate = @repository.scm.annotate(@path, @rev) (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty? + @changeset = @repository.find_changeset_by_name(@rev) end - + def revision + raise ChangesetNotFound if @rev.blank? @changeset = @repository.find_changeset_by_name(@rev) raise ChangesetNotFound unless @changeset @@ -149,7 +154,7 @@ rescue ChangesetNotFound show_error_not_found end - + def diff if params[:format] == 'diff' @diff = @repository.diff(@path, @rev, @rev_to) @@ -174,14 +179,18 @@ @diff = @repository.diff(@path, @rev, @rev_to) show_error_not_found unless @diff end + + @changeset = @repository.find_changeset_by_name(@rev) + @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil + @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to) end end - - def stats + + def stats end - + def graph - data = nil + data = nil case params[:graph] when "commits_per_month" data = graph_commits_per_month(@repository) @@ -196,7 +205,10 @@ end end -private + private + + REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i + def find_repository @project = Project.find(params[:id]) @repository = @project.repository @@ -205,6 +217,12 @@ @path ||= '' @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip @rev_to = params[:rev_to] + + unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE) + if @repository.branches.blank? + raise InvalidRevisionParam + end + end rescue ActiveRecord::RecordNotFound render_404 rescue InvalidRevisionParam @@ -212,7 +230,7 @@ end def show_error_not_found - render_error l(:error_scm_not_found) + render_error :message => l(:error_scm_not_found), :status => 404 end # Handler for Redmine::Scm::Adapters::CommandFailed exception diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/settings_controller.rb --- a/app/controllers/settings_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/settings_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -44,6 +44,8 @@ @guessed_host_and_path = request.host_with_port.dup @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank? + + Redmine::Themes.rescan end def plugin diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/timelog_controller.rb --- a/app/controllers/timelog_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/timelog_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2010 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -18,10 +18,11 @@ class TimelogController < ApplicationController menu_item :issues before_filter :find_project, :only => [:new, :create] - before_filter :find_time_entry, :only => [:edit, :update, :destroy] + before_filter :find_time_entry, :only => [:show, :edit, :update, :destroy] before_filter :authorize, :except => [:index] before_filter :find_optional_project, :only => [:index] - + accept_key_auth :index, :show, :create, :update, :destroy + helper :sort include SortHelper helper :issues @@ -66,6 +67,16 @@ render :layout => !request.xhr? } + format.api { + @entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions) + @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] + @entries = TimeEntry.find(:all, + :include => [:project, :activity, :user, {:issue => :tracker}], + :conditions => cond.conditions, + :order => sort_clause, + :limit => @entry_pages.items_per_page, + :offset => @entry_pages.current.offset) + } format.atom { entries = TimeEntry.find(:all, :include => [:project, :activity, :user, {:issue => :tracker}], @@ -85,6 +96,14 @@ end end end + + def show + respond_to do |format| + # TODO: Implement html response + format.html { render :nothing => true, :status => 406 } + format.api + end + end def new @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) @@ -102,10 +121,18 @@ call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) if @time_entry.save - flash[:notice] = l(:notice_successful_update) - redirect_back_or_default :action => 'index', :project_id => @time_entry.project + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_back_or_default :action => 'index', :project_id => @time_entry.project + } + format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) } + end else - render :action => 'edit' + respond_to do |format| + format.html { render :action => 'edit' } + format.api { render_validation_errors(@time_entry) } + end end end @@ -122,21 +149,40 @@ call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) if @time_entry.save - flash[:notice] = l(:notice_successful_update) - redirect_back_or_default :action => 'index', :project_id => @time_entry.project + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_back_or_default :action => 'index', :project_id => @time_entry.project + } + format.api { head :ok } + end else - render :action => 'edit' + respond_to do |format| + format.html { render :action => 'edit' } + format.api { render_validation_errors(@time_entry) } + end end end verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed } def destroy if @time_entry.destroy && @time_entry.destroyed? - flash[:notice] = l(:notice_successful_delete) + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_delete) + redirect_to :back + } + format.api { head :ok } + end else - flash[:error] = l(:notice_unable_delete_time_entry) + respond_to do |format| + format.html { + flash[:error] = l(:notice_unable_delete_time_entry) + redirect_to :back + } + format.api { render_validation_errors(@time_entry) } + end end - redirect_to :back rescue ::ActionController::RedirectBackError redirect_to :action => 'index', :project_id => @time_entry.project end @@ -154,11 +200,11 @@ end def find_project - if params[:issue_id] - @issue = Issue.find(params[:issue_id]) + if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present? + @issue = Issue.find(issue_id) @project = @issue.project - elsif params[:project_id] - @project = Project.find(params[:project_id]) + elsif (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present? + @project = Project.find(project_id) else render_404 return false diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/users_controller.rb --- a/app/controllers/users_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/users_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2009 Jean-Philippe Lang +# Copyright (C) 2006-2010 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,6 +19,8 @@ layout 'admin' before_filter :require_admin, :except => :show + before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership] + accept_key_auth :index, :show, :create, :update, :destroy helper :sort include SortHelper @@ -29,6 +31,13 @@ sort_init 'login', 'asc' sort_update %w(login firstname lastname mail admin created_on last_login_on) + case params[:format] + when 'xml', 'json' + @offset, @limit = api_offset_and_limit + else + @limit = per_page_option + end + @status = params[:status] ? params[:status].to_i : 1 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status]) @@ -38,19 +47,21 @@ end @user_count = User.count(:conditions => c.conditions) - @user_pages = Paginator.new self, @user_count, - per_page_option, - params['page'] - @users = User.find :all,:order => sort_clause, + @user_pages = Paginator.new self, @user_count, @limit, params['page'] + @offset ||= @user_pages.current.offset + @users = User.find :all, + :order => sort_clause, :conditions => c.conditions, - :limit => @user_pages.items_per_page, - :offset => @user_pages.current.offset + :limit => @limit, + :offset => @offset - render :layout => !request.xhr? + respond_to do |format| + format.html { render :layout => !request.xhr? } + format.api + end end def show - @user = User.find(params[:id]) if @user.ssamr_user_detail != nil @description = @user.ssamr_user_detail.description @@ -77,17 +88,15 @@ return end end - render :layout => 'base' - - rescue ActiveRecord::RecordNotFound - render_404 + + respond_to do |format| + format.html { render :layout => 'base' } + format.api + end end def new - @notification_options = User::MAIL_NOTIFICATION_OPTIONS - @notification_option = Setting.default_notification_option - - @user = User.new(:language => Setting.default_language) + @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) @auth_sources = AuthSource.find(:all) @ssamr_user_details = SsamrUserDetail.new @@ -95,16 +104,14 @@ verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed } def create - @notification_options = User::MAIL_NOTIFICATION_OPTIONS - @notification_option = Setting.default_notification_option - + @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) + @user.safe_attributes = params[:user] @user = User.new(params[:user]) @user.admin = params[:user][:admin] || false @user.login = params[:user][:login] - @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id + @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id # TODO: Similar to My#account - @user.mail_notification = params[:notification_option] || 'only_my_events' @user.pref.attributes = params[:pref] @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') @@ -118,25 +125,32 @@ @ssamr_user_details.save! - @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : []) - Mailer.deliver_account_information(@user, params[:password]) if params[:send_information] - flash[:notice] = l(:notice_successful_create) - redirect_to(params[:continue] ? {:controller => 'users', :action => 'new'} : - {:controller => 'users', :action => 'edit', :id => @user}) - return + Mailer.deliver_account_information(@user, params[:user][:password]) if params[:send_information] + + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_create) + redirect_to(params[:continue] ? + {:controller => 'users', :action => 'new'} : + {:controller => 'users', :action => 'edit', :id => @user} + ) + } + format.api { render :action => 'show', :status => :created, :location => user_url(@user) } + end else @auth_sources = AuthSource.find(:all) - @notification_option = @user.mail_notification + # Clear password input + @user.password = @user.password_confirmation = nil - render :action => 'new' + respond_to do |format| + format.html { render :action => 'new' } + format.api { render_validation_errors(@user) } + end end end def edit - @user = User.find(params[:id]) - @notification_options = @user.valid_notification_options - @notification_option = @user.mail_notification @ssamr_user_details = @user.ssamr_user_detail @@ -152,22 +166,15 @@ verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed } def update - @user = User.find(params[:id]) - - @notification_options = @user.valid_notification_options - @notification_option = @user.mail_notification - @user.admin = params[:user][:admin] if params[:user][:admin] @user.login = params[:user][:login] if params[:user][:login] - if params[:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?) - @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] + if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?) + @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] end - @user.group_ids = params[:user][:group_ids] if params[:user][:group_ids] - @user.attributes = params[:user] + @user.safe_attributes = params[:user] # Was the account actived ? (do it before User#save clears the change) was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE]) # TODO: Similar to My#account - @user.mail_notification = params[:notification_option] || 'only_my_events' @user.pref.attributes = params[:pref] @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') @@ -193,27 +200,46 @@ if @user.save @user.pref.save - @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : []) + @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) if was_activated Mailer.deliver_account_activated(@user) - elsif @user.active? && params[:send_information] && !params[:password].blank? && @user.auth_source_id.nil? - Mailer.deliver_account_information(@user, params[:password]) + elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil? + Mailer.deliver_account_information(@user, params[:user][:password]) end - flash[:notice] = l(:notice_successful_update) - redirect_to :back + + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_to :back + } + format.api { head :ok } + end else @auth_sources = AuthSource.find(:all) @membership ||= Member.new + # Clear password input + @user.password = @user.password_confirmation = nil - render :action => :edit + respond_to do |format| + format.html { render :action => :edit } + format.api { render_validation_errors(@user) } + end end rescue ::ActionController::RedirectBackError redirect_to :controller => 'users', :action => 'edit', :id => @user end + verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed } + def destroy + @user.destroy + respond_to do |format| + format.html { redirect_to(users_url) } + format.api { head :ok } + end + end + def edit_membership - @user = User.find(params[:id]) @membership = Member.edit_membership(params[:membership_id], params[:membership], @user) @membership.save if request.post? respond_to do |format| @@ -236,7 +262,6 @@ end def destroy_membership - @user = User.find(params[:id]) @membership = Member.find(params[:membership_id]) if request.post? && @membership.deletable? @membership.destroy @@ -246,4 +271,17 @@ format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} } end end + + private + + def find_user + if params[:id] == 'current' + require_login || return + @user = User.current + else + @user = User.find(params[:id]) + end + rescue ActiveRecord::RecordNotFound + render_404 + end end diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/wiki_controller.rb --- a/app/controllers/wiki_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/wiki_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -125,6 +125,8 @@ render_attachment_warning_if_needed(@page) call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) redirect_to :action => 'show', :project_id => @project, :id => @page.title + else + render :action => 'edit' end rescue ActiveRecord::StaleObjectError diff -r 7cec015f07ce -r 73ff0e6a11b1 app/controllers/workflows_controller.rb --- a/app/controllers/workflows_controller.rb Tue Feb 22 16:48:15 2011 +0000 +++ b/app/controllers/workflows_controller.rb Thu Mar 03 12:11:53 2011 +0000 @@ -32,14 +32,17 @@ if request.post? Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) - (params[:issue_status] || []).each { |old, news| - news.each { |new| - @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new) + (params[:issue_status] || []).each { |status_id, transitions| + transitions.each { |new_status_id, options| + author = options.is_a?(Array) && options.include?('author') && !options.include?('always') + assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always') + @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee) } } if @role.save flash[:notice] = l(:notice_successful_update) redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker + return end end @@ -48,6 +51,14 @@ @statuses = @tracker.issue_statuses end @statuses ||= IssueStatus.find(:all, :order => 'position') + + if @tracker && @role && @statuses.any? + workflows = Workflow.all(:conditions => {:role_id => @role.id, :tracker_id => @tracker.id}) + @workflows = {} + @workflows['always'] = workflows.select {|w| !w.author && !w.assignee} + @workflows['author'] = workflows.select {|w| w.author} + @workflows['assignee'] = workflows.select {|w| w.assignee} + end end def copy diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/.svn/all-wcprops --- a/app/helpers/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000 +++ b/app/helpers/.svn/all-wcprops Thu Mar 03 12:11:53 2011 +0000 @@ -1,7 +1,7 @@ K 25 svn:wc:ra_dav:version-url V 36 -/svn/!svn/ver/4391/trunk/app/helpers +/svn/!svn/ver/4990/trunk/app/helpers END trackers_helper.rb K 25 @@ -37,7 +37,7 @@ K 25 svn:wc:ra_dav:version-url V 59 -/svn/!svn/ver/3659/trunk/app/helpers/repositories_helper.rb +/svn/!svn/ver/4990/trunk/app/helpers/repositories_helper.rb END admin_helper.rb K 25 @@ -79,7 +79,7 @@ K 25 svn:wc:ra_dav:version-url V 51 -/svn/!svn/ver/4375/trunk/app/helpers/wiki_helper.rb +/svn/!svn/ver/4952/trunk/app/helpers/wiki_helper.rb END enumerations_helper.rb K 25 @@ -91,7 +91,13 @@ K 25 svn:wc:ra_dav:version-url V 53 -/svn/!svn/ver/4281/trunk/app/helpers/issues_helper.rb +/svn/!svn/ver/4954/trunk/app/helpers/issues_helper.rb +END +queries_helper.rb +K 25 +svn:wc:ra_dav:version-url +V 54 +/svn/!svn/ver/4387/trunk/app/helpers/queries_helper.rb END gantt_helper.rb K 25 @@ -99,12 +105,6 @@ V 52 /svn/!svn/ver/4283/trunk/app/helpers/gantt_helper.rb END -queries_helper.rb -K 25 -svn:wc:ra_dav:version-url -V 54 -/svn/!svn/ver/4387/trunk/app/helpers/queries_helper.rb -END mail_handler_helper.rb K 25 svn:wc:ra_dav:version-url @@ -135,29 +135,29 @@ V 53 /svn/!svn/ver/333/trunk/app/helpers/welcome_helper.rb END +journals_helper.rb +K 25 +svn:wc:ra_dav:version-url +V 55 +/svn/!svn/ver/4062/trunk/app/helpers/journals_helper.rb +END workflows_helper.rb K 25 svn:wc:ra_dav:version-url V 56 /svn/!svn/ver/1914/trunk/app/helpers/workflows_helper.rb END -journals_helper.rb -K 25 -svn:wc:ra_dav:version-url -V 55 -/svn/!svn/ver/4062/trunk/app/helpers/journals_helper.rb -END reports_helper.rb K 25 svn:wc:ra_dav:version-url V 53 /svn/!svn/ver/629/trunk/app/helpers/reports_helper.rb END -custom_fields_helper.rb +timelog_helper.rb K 25 svn:wc:ra_dav:version-url -V 60 -/svn/!svn/ver/3675/trunk/app/helpers/custom_fields_helper.rb +V 54 +/svn/!svn/ver/3708/trunk/app/helpers/timelog_helper.rb END settings_helper.rb K 25 @@ -165,17 +165,11 @@ V 55 /svn/!svn/ver/4222/trunk/app/helpers/settings_helper.rb END -timelog_helper.rb +custom_fields_helper.rb K 25 svn:wc:ra_dav:version-url -V 54 -/svn/!svn/ver/3708/trunk/app/helpers/timelog_helper.rb -END -users_helper.rb -K 25 -svn:wc:ra_dav:version-url -V 52 -/svn/!svn/ver/4230/trunk/app/helpers/users_helper.rb +V 60 +/svn/!svn/ver/4480/trunk/app/helpers/custom_fields_helper.rb END issue_moves_helper.rb K 25 @@ -183,11 +177,17 @@ V 58 /svn/!svn/ver/3936/trunk/app/helpers/issue_moves_helper.rb END +users_helper.rb +K 25 +svn:wc:ra_dav:version-url +V 52 +/svn/!svn/ver/4497/trunk/app/helpers/users_helper.rb +END application_helper.rb K 25 svn:wc:ra_dav:version-url V 58 -/svn/!svn/ver/4391/trunk/app/helpers/application_helper.rb +/svn/!svn/ver/4900/trunk/app/helpers/application_helper.rb END auth_sources_helper.rb K 25 @@ -205,7 +205,13 @@ K 25 svn:wc:ra_dav:version-url V 55 -/svn/!svn/ver/3373/trunk/app/helpers/messages_helper.rb +/svn/!svn/ver/4760/trunk/app/helpers/messages_helper.rb +END +issue_relations_helper.rb +K 25 +svn:wc:ra_dav:version-url +V 61 +/svn/!svn/ver/506/trunk/app/helpers/issue_relations_helper.rb END versions_helper.rb K 25 @@ -213,12 +219,6 @@ V 54 /svn/!svn/ver/955/trunk/app/helpers/versions_helper.rb END -issue_relations_helper.rb -K 25 -svn:wc:ra_dav:version-url -V 61 -/svn/!svn/ver/506/trunk/app/helpers/issue_relations_helper.rb -END boards_helper.rb K 25 svn:wc:ra_dav:version-url @@ -229,7 +229,7 @@ K 25 svn:wc:ra_dav:version-url V 58 -/svn/!svn/ver/2116/trunk/app/helpers/attachments_helper.rb +/svn/!svn/ver/4605/trunk/app/helpers/attachments_helper.rb END news_helper.rb K 25 diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/.svn/entries --- a/app/helpers/.svn/entries Tue Feb 22 16:48:15 2011 +0000 +++ b/app/helpers/.svn/entries Thu Mar 03 12:11:53 2011 +0000 @@ -1,15 +1,15 @@ 10 dir -4411 +4993 http://redmine.rubyforge.org/svn/trunk/app/helpers http://redmine.rubyforge.org/svn -2010-11-11T13:39:14.764400Z -4391 -jplang +2011-03-03T03:30:10.954225Z +4990 +tmaruyama @@ -32,7 +32,7 @@ -2010-09-23T14:37:44.431779Z +2011-03-03T11:05:09.000000Z e1b19c2c81df384b2026001d1d9605fd 2007-03-12T17:59:02.654744Z 333 @@ -66,7 +66,7 @@ -2010-09-23T14:37:44.427784Z +2011-03-03T11:05:09.000000Z ab0af291fb143223852a6edddc64b74b 2009-09-12T08:36:46.650954Z 2869 @@ -100,7 +100,7 @@ -2010-09-23T14:37:44.427784Z +2011-03-03T11:05:09.000000Z 0cd3093356b7034ee950ca327fc7aabc 2007-03-12T17:59:02.654744Z 333 @@ -134,7 +134,7 @@ -2010-09-23T14:37:44.431779Z +2011-03-03T11:05:09.000000Z 69869e026dc6cbacc618214e4bb13b1b 2007-03-12T17:59:02.654744Z 333 @@ -168,7 +168,7 @@ -2010-09-23T14:37:44.431779Z +2011-03-03T11:05:09.000000Z 491ead5828edb57adcd7333943656c94 2010-03-27T16:55:20.312262Z 3618 @@ -202,11 +202,11 @@ -2010-09-24T11:56:52.900009Z -d617582e4bc6a3eedbc4f37f8fa3d509 -2010-04-11T15:18:49.769279Z -3659 -jplang +2011-03-03T11:40:18.000000Z +824b766582f7177458a587dcfcf980f5 +2011-03-03T03:30:10.954225Z +4990 +tmaruyama has-props @@ -228,7 +228,7 @@ -7654 +9702 admin_helper.rb file @@ -236,7 +236,7 @@ -2010-09-24T12:48:25.903794Z +2011-03-03T11:05:09.000000Z 258ebc9ad13b3111585e5118ec607d1b 2010-09-10T23:07:10.817821Z 4080 @@ -270,7 +270,7 @@ -2010-09-23T14:37:44.427784Z +2011-03-03T11:05:09.000000Z 19efbd2b30f44f233d5140437e2707a0 2010-08-08T07:07:20.961363Z 3924 @@ -304,7 +304,7 @@ -2010-09-23T14:37:44.423776Z +2011-03-03T11:05:09.000000Z 42d20ee00a3c45e3411634dbb09e2447 2007-03-12T17:59:02.654744Z 333 @@ -338,7 +338,7 @@ -2010-09-24T12:48:25.903794Z +2011-03-03T11:05:09.000000Z c8780c65337211f34097d74a8c3c9f6e 2010-08-26T16:37:26.575205Z 4046 @@ -372,7 +372,7 @@ -2010-09-23T14:37:44.427784Z +2011-03-03T11:05:09.000000Z 1257aafe6669c839ab46dac2b2732ae0 2007-03-12T17:59:02.654744Z 333 @@ -406,7 +406,7 @@ -2010-09-23T14:37:44.423776Z +2011-03-03T11:05:09.000000Z ceb0fce7a4c271df3ae182c29caf9c03 2007-03-12T17:59:02.654744Z 333 @@ -440,10 +440,10 @@ -2010-11-19T13:04:46.900732Z -3e6762604b5e9a201b7a7bb2edc1f78f -2010-11-06T14:30:32.528294Z -4375 +2011-03-03T11:40:18.000000Z +44832549db555bb9e047a67ae330f34a +2011-02-27T12:35:31.414622Z +4952 jplang has-props @@ -466,7 +466,7 @@ -2297 +1315 enumerations_helper.rb file @@ -474,7 +474,7 @@ -2010-09-23T14:37:44.427784Z +2011-03-03T11:05:09.000000Z fc5ec20dd0ca3ce1d954b219417eff70 2007-03-12T17:59:02.654744Z 333 @@ -508,10 +508,10 @@ -2010-11-19T13:04:46.900732Z -b5569bf5b4ba507a522f4e371505ae0c -2010-10-22T22:29:32.969473Z -4281 +2011-03-03T11:40:18.000000Z +9dfadb196899bf96fdff7c7c34914e8c +2011-02-27T13:34:41.060565Z +4954 jplang has-props @@ -534,41 +534,7 @@ -10075 - -gantt_helper.rb -file - - - - -2010-11-19T13:04:46.900732Z -dfac5226f31c3c232d7dcc9416172071 -2010-10-23T09:08:55.877887Z -4283 -jplang - - - - - - - - - - - - - - - - - - - - - -1988 +11599 queries_helper.rb file @@ -576,7 +542,7 @@ -2010-11-19T13:04:46.900732Z +2011-03-03T11:05:09.000000Z 6b997d023cc081b101be1949eb18f893 2010-11-07T15:38:51.908839Z 4387 @@ -604,13 +570,47 @@ 3722 +gantt_helper.rb +file + + + + +2011-03-03T11:05:09.000000Z +dfac5226f31c3c232d7dcc9416172071 +2010-10-23T09:08:55.877887Z +4283 +jplang + + + + + + + + + + + + + + + + + + + + + +1988 + mail_handler_helper.rb file -2010-09-23T14:37:44.427784Z +2011-03-03T11:05:09.000000Z c79faf6237abac40fabce0eacbfbb4cb 2008-06-25T19:25:28.386590Z 1584 @@ -644,7 +644,7 @@ -2010-09-23T14:37:44.431779Z +2011-03-03T11:05:09.000000Z fc71d3a83fe0178e0c6d9519c7a62dde 2010-03-13T17:45:41.194736Z 3579 @@ -678,7 +678,7 @@ -2010-09-23T14:37:44.427784Z +2011-03-03T11:05:09.000000Z d7134d76eff99aef107d8930903b10e0 2007-03-12T17:59:02.654744Z 333 @@ -712,7 +712,7 @@ -2010-09-23T14:37:44.427784Z +2011-03-03T11:05:09.000000Z cc94b2fd5c6a87acac13bbe6e91bd39a 2007-03-12T17:59:02.654744Z 333 @@ -746,7 +746,7 @@ -2010-09-23T14:37:44.431779Z +2011-03-03T11:05:09.000000Z 2d03eb3683f312ec26736c696319ec76 2007-03-12T17:59:02.654744Z 333 @@ -774,13 +774,47 @@ 817 +journals_helper.rb +file + + + + +2011-03-03T11:05:09.000000Z +b64edde44bceed7fb0911f87dba971aa +2010-09-05T22:57:20.669640Z +4062 +edavis10 +has-props + + + + + + + + + + + + + + + + + + + + +2266 + workflows_helper.rb file -2010-09-23T14:37:44.431779Z +2011-03-03T11:05:09.000000Z 4f7bcbe149363ecb18080e3ce6c3f761 2008-09-28T12:03:17.584169Z 1914 @@ -808,47 +842,13 @@ 824 -journals_helper.rb -file - - - - -2010-09-24T12:48:25.903794Z -b64edde44bceed7fb0911f87dba971aa -2010-09-05T22:57:20.669640Z -4062 -edavis10 -has-props - - - - - - - - - - - - - - - - - - - - -2266 - reports_helper.rb file -2010-09-23T14:37:44.431779Z +2011-03-03T11:05:09.000000Z 9daafcfffa933849b3ee09fe5fd7b0cc 2007-08-13T17:00:59.400243Z 629 @@ -876,17 +876,17 @@ 1265 -custom_fields_helper.rb +timelog_helper.rb file -2010-09-23T14:37:44.423776Z -97753365c8eb76a948d7417d5db31079 -2010-04-16T15:34:05.970144Z -3675 -edavis10 +2011-03-03T11:05:09.000000Z +1888fd2d4a1be5fa0d6ad8a63238e2b4 +2010-04-30T12:18:11.536180Z +3708 +jplang has-props @@ -908,7 +908,7 @@ -5322 +6636 settings_helper.rb file @@ -916,7 +916,7 @@ -2010-11-19T13:04:46.900732Z +2011-03-03T11:05:09.000000Z 868d0dc19e332ae2c817321637a8e31f 2010-09-28T21:09:06.467392Z 4222 @@ -944,16 +944,16 @@ 3858 -timelog_helper.rb +custom_fields_helper.rb file -2010-09-23T14:37:44.431779Z -1888fd2d4a1be5fa0d6ad8a63238e2b4 -2010-04-30T12:18:11.536180Z -3708 +2011-03-03T11:05:09.000000Z +19a8f1c143030c42136fe167f167db15 +2010-12-10T10:12:19.202442Z +4480 jplang has-props @@ -976,7 +976,7 @@ -6636 +5702 issue_moves_helper.rb file @@ -984,7 +984,7 @@ -2010-09-23T14:37:44.427784Z +2011-03-03T11:05:09.000000Z 66e99bd3af036c92a773a91e92a725f9 2010-08-11T14:42:10.119704Z 3936 @@ -1018,11 +1018,11 @@ -2010-11-19T13:04:46.900732Z -01b097606863e1e2938e7222d07fc2b7 -2010-09-30T18:22:46.611444Z -4230 -edavis10 +2011-03-03T11:05:09.000000Z +1bedb4287035a6f89074da0b37640c28 +2010-12-12T14:25:23.262992Z +4497 +jplang has-props @@ -1044,7 +1044,7 @@ -2644 +2767 application_helper.rb file @@ -1052,10 +1052,10 @@ -2010-11-19T13:04:46.900732Z -a9ac6edf6104b93a669ba1b0705f4fc6 -2010-11-11T13:39:14.764400Z -4391 +2011-03-03T11:40:18.000000Z +9d39fe6e2dd4535f7a006a77150961bc +2011-02-21T09:53:29.844413Z +4900 jplang has-props @@ -1078,7 +1078,7 @@ -34105 +36020 auth_sources_helper.rb file @@ -1086,7 +1086,7 @@ -2010-09-23T14:37:44.423776Z +2011-03-03T11:05:09.000000Z 19759b6e665f9063140da8ac473d9e31 2007-03-12T17:59:02.654744Z 333 @@ -1120,7 +1120,7 @@ -2010-11-19T13:04:46.900732Z +2011-03-03T11:05:09.000000Z dedb3c62af67e076703aa2f60c97fb2b 2010-11-01T12:55:15.292443Z 4353 @@ -1154,10 +1154,10 @@ -2010-09-23T14:37:44.427784Z -70db854f3fd0a38d203a65c44d93bb13 -2010-02-06T12:54:13.653502Z -3373 +2011-03-03T11:05:09.000000Z +d083581ae234853db09e79e4e88d6ad3 +2011-01-23T17:02:10.030897Z +4760 jplang has-props @@ -1180,7 +1180,41 @@ -1364 +823 + +issue_relations_helper.rb +file + + + + +2011-03-03T11:05:09.000000Z +fef3a97e6644b418b44f9551aa65af95 +2007-05-05T13:22:27.245135Z +506 +jplang +has-props + + + + + + + + + + + + + + + + + + + + +1017 versions_helper.rb file @@ -1188,7 +1222,7 @@ -2010-09-23T14:37:44.431779Z +2011-03-03T11:05:09.000000Z fd18226266f3e53f6af000ac07a13313 2007-12-07T10:26:07.864320Z 955 @@ -1216,47 +1250,13 @@ 2170 -issue_relations_helper.rb -file - - - - -2010-09-23T14:37:44.427784Z -fef3a97e6644b418b44f9551aa65af95 -2007-05-05T13:22:27.245135Z -506 -jplang -has-props - - - - - - - - - - - - - - - - - - - - -1017 - boards_helper.rb file -2010-09-23T14:37:44.423776Z +2011-03-03T11:05:09.000000Z 0ca8f33522734ee0417089366037a7cb 2007-05-13T17:09:56.765659Z 529 @@ -1290,10 +1290,10 @@ -2010-09-23T14:37:44.423776Z -6e15ada8c772d8daf8062cec2885ad9c -2008-12-09T16:54:46.963649Z -2116 +2011-03-03T11:05:09.000000Z +58bc2391be7fedbf3e55ef07454cb04c +2011-01-01T19:12:35.568700Z +4605 jplang has-props @@ -1316,7 +1316,7 @@ -1367 +1743 news_helper.rb file @@ -1324,7 +1324,7 @@ -2010-09-23T14:37:44.427784Z +2011-03-03T11:05:09.000000Z d808f6794a2fa7cd059ed56d5c8d1b28 2007-03-12T17:59:02.654744Z 333 diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/.svn/text-base/application_helper.rb.svn-base --- a/app/helpers/.svn/text-base/application_helper.rb.svn-base Tue Feb 22 16:48:15 2011 +0000 +++ b/app/helpers/.svn/text-base/application_helper.rb.svn-base Thu Mar 03 12:11:53 2011 +0000 @@ -104,8 +104,24 @@ # * :text - Link text (default to the formatted revision) def link_to_revision(revision, project, options={}) text = options.delete(:text) || format_revision(revision) + rev = revision.respond_to?(:identifier) ? revision.identifier : revision - link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision)) + link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev}, + :title => l(:label_revision_id, format_revision(revision))) + end + + # Generates a link to a message + def link_to_message(message, options={}, html_options = nil) + link_to( + h(truncate(message.subject, :length => 60)), + { :controller => 'messages', :action => 'show', + :board_id => message.board_id, + :id => message.root, + :r => (message.parent_id && message.id), + :anchor => (message.parent_id ? "message-#{message.id}" : nil) + }.merge(options), + html_options + ) end # Generates a link to a project if active @@ -449,12 +465,19 @@ only_path = options.delete(:only_path) == false ? false : true text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) } - - parse_non_pre_blocks(text) do |text| + + @parsed_headings = [] + text = parse_non_pre_blocks(text) do |text| [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name| send method_name, text, project, obj, attr, only_path, options end end + + if @parsed_headings.any? + replace_toc(text, @parsed_headings) + end + + text end def parse_non_pre_blocks(text) @@ -579,16 +602,26 @@ # source:some/file#L120 -> Link to line 120 of the file # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 # export:some/file -> Force the download of the file - # Forum messages: + # Forum messages: # message#1218 -> Link to message with id 1218 + # + # Links can refer other objects from other projects, using project identifier: + # identifier:r52 + # identifier:document:"Some document" + # identifier:version:1.0.0 + # identifier:source:some/file def parse_redmine_links(text, project, obj, attr, only_path, options) - text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m| - leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8 + text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m| + leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 link = nil + if project_identifier + project = Project.visible.find_by_identifier(project_identifier) + end if esc.nil? if prefix.nil? && sep == 'r' - if project && (changeset = project.changesets.find_by_revision(identifier)) - link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, + # project.changesets.visible raises an SQL error because of a double join on repositories + if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier)) + link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, :class => 'changeset', :title => truncate_single_line(changeset.comments, :length => 100)) end @@ -602,24 +635,18 @@ :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") end when 'document' - if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current)) + if document = Document.visible.find_by_id(oid) link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, :class => 'document' end when 'version' - if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current)) + if version = Version.visible.find_by_id(oid) link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, :class => 'version' end when 'message' - if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current)) - link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path, - :controller => 'messages', - :action => 'show', - :board_id => message.board, - :id => message.root, - :anchor => (message.parent ? "message-#{message.id}" : nil)}, - :class => 'message' + if message = Message.visible.find_by_id(oid, :include => :parent) + link = link_to_message(message, {:only_path => only_path}, :class => 'message') end when 'project' if p = Project.visible.find_by_id(oid) @@ -631,26 +658,26 @@ name = identifier.gsub(%r{^"(.*)"$}, "\\1") case prefix when 'document' - if project && document = project.documents.find_by_title(name) + if project && document = project.documents.visible.find_by_title(name) link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, :class => 'document' end when 'version' - if project && version = project.versions.find_by_name(name) + if project && version = project.versions.visible.find_by_name(name) link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, :class => 'version' end when 'commit' - if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"])) - link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, + if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) + link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, :class => 'changeset', :title => truncate_single_line(changeset.comments, :length => 100) end when 'source', 'export' - if project && project.repository + if project && project.repository && User.current.allowed_to?(:browse_repository, project) name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} path, rev, anchor = $1, $3, $5 - link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, + link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :path => to_path_param(path), :rev => rev, :anchor => anchor, @@ -670,25 +697,30 @@ end end end - leading + (link || "#{prefix}#{sep}#{identifier}") + leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}") end end - TOC_RE = /
\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
HEADING_RE = / \{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
+
+ # Renders the TOC with given headings
+ def replace_toc(text, headings)
text.gsub!(TOC_RE) do
if headings.empty?
''
@@ -862,11 +894,42 @@
''
end
end
+
+ # Returns the javascript tags that are included in the html layout head
+ def javascript_heads
+ tags = javascript_include_tag(:defaults)
+ unless User.current.pref.warn_on_leaving_unsaved == '0'
+ tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
+ end
+ tags
+ end
def favicon
""
end
+
+ # Returns true if arg is expected in the API response
+ def include_in_api_response?(arg)
+ unless @included_in_api_response
+ param = params[:include]
+ @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
+ @included_in_api_response.collect!(&:strip)
+ end
+ @included_in_api_response.include?(arg.to_s)
+ end
+ # Returns options or nil if nometa param or X-Redmine-Nometa header
+ # was set in the request
+ def api_meta(options)
+ if params[:nometa].present? || request.headers['X-Redmine-Nometa']
+ # compatibility mode for activeresource clients that raise
+ # an error when unserializing an array with attributes
+ nil
+ else
+ options
+ end
+ end
+
private
def wiki_helper
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/.svn/text-base/attachments_helper.rb.svn-base
--- a/app/helpers/.svn/text-base/attachments_helper.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/.svn/text-base/attachments_helper.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -29,6 +29,18 @@
end
def to_utf8(str)
- str
+ if str.respond_to?(:force_encoding)
+ str.force_encoding('UTF-8')
+ return str if str.valid_encoding?
+ else
+ return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
+ end
+
+ begin
+ Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
+ rescue Iconv::InvalidEncoding
+ # "UTF-8//IGNORE" is not supported on some OS
+ str
+ end
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/.svn/text-base/custom_fields_helper.rb.svn-base
--- a/app/helpers/.svn/text-base/custom_fields_helper.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/.svn/text-base/custom_fields_helper.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -104,4 +104,15 @@
def custom_field_formats_for_select
Redmine::CustomFieldFormat.as_select
end
+
+ # Renders the custom_values in api views
+ def render_api_custom_values(custom_values, api)
+ api.array :custom_fields do
+ custom_values.each do |custom_value|
+ api.custom_field :id => custom_value.custom_field_id, :name => custom_value.custom_field.name do
+ api.value custom_value.value
+ end
+ end
+ end unless custom_values.empty?
+ end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/.svn/text-base/issues_helper.rb.svn-base
--- a/app/helpers/.svn/text-base/issues_helper.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/.svn/text-base/issues_helper.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -106,13 +106,32 @@
# Project specific queries and global queries
visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
@sidebar_queries = Query.find(:all,
- :select => 'id, name',
+ :select => 'id, name, is_public',
:order => "name ASC",
:conditions => visible.conditions)
end
@sidebar_queries
end
+ def query_links(title, queries)
+ # links to #index on issues/show
+ url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
+
+ content_tag('h3', title) +
+ queries.collect {|query|
+ link_to(h(query.name), url_params.merge(:query_id => query))
+ }.join(' \{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
HEADING_RE = / \{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
+
+ # Renders the TOC with given headings
+ def replace_toc(text, headings)
text.gsub!(TOC_RE) do
if headings.empty?
''
@@ -871,11 +901,42 @@
''
end
end
+
+ # Returns the javascript tags that are included in the html layout head
+ def javascript_heads
+ tags = javascript_include_tag(:defaults)
+ unless User.current.pref.warn_on_leaving_unsaved == '0'
+ tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
+ end
+ tags
+ end
def favicon
""
end
+
+ # Returns true if arg is expected in the API response
+ def include_in_api_response?(arg)
+ unless @included_in_api_response
+ param = params[:include]
+ @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
+ @included_in_api_response.collect!(&:strip)
+ end
+ @included_in_api_response.include?(arg.to_s)
+ end
+ # Returns options or nil if nometa param or X-Redmine-Nometa header
+ # was set in the request
+ def api_meta(options)
+ if params[:nometa].present? || request.headers['X-Redmine-Nometa']
+ # compatibility mode for activeresource clients that raise
+ # an error when unserializing an array with attributes
+ nil
+ else
+ options
+ end
+ end
+
private
def wiki_helper
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/attachments_helper.rb
--- a/app/helpers/attachments_helper.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/attachments_helper.rb Thu Mar 03 12:11:53 2011 +0000
@@ -29,6 +29,18 @@
end
def to_utf8(str)
- str
+ if str.respond_to?(:force_encoding)
+ str.force_encoding('UTF-8')
+ return str if str.valid_encoding?
+ else
+ return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
+ end
+
+ begin
+ Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
+ rescue Iconv::InvalidEncoding
+ # "UTF-8//IGNORE" is not supported on some OS
+ str
+ end
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/custom_fields_helper.rb
--- a/app/helpers/custom_fields_helper.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/custom_fields_helper.rb Thu Mar 03 12:11:53 2011 +0000
@@ -104,4 +104,15 @@
def custom_field_formats_for_select
Redmine::CustomFieldFormat.as_select
end
+
+ # Renders the custom_values in api views
+ def render_api_custom_values(custom_values, api)
+ api.array :custom_fields do
+ custom_values.each do |custom_value|
+ api.custom_field :id => custom_value.custom_field_id, :name => custom_value.custom_field.name do
+ api.value custom_value.value
+ end
+ end
+ end unless custom_values.empty?
+ end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/issues_helper.rb
--- a/app/helpers/issues_helper.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/issues_helper.rb Thu Mar 03 12:11:53 2011 +0000
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -106,13 +106,32 @@
# Project specific queries and global queries
visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
@sidebar_queries = Query.find(:all,
- :select => 'id, name',
+ :select => 'id, name, is_public',
:order => "name ASC",
:conditions => visible.conditions)
end
@sidebar_queries
end
+ def query_links(title, queries)
+ # links to #index on issues/show
+ url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
+
+ content_tag('h3', title) +
+ queries.collect {|query|
+ link_to(h(query.name), url_params.merge(:query_id => query))
+ }.join('
')
+ end
+
+ def render_sidebar_queries
+ out = ''
+ queries = sidebar_queries.select {|q| !q.is_public?}
+ out << query_links(l(:label_my_queries), queries) if queries.any?
+ queries = sidebar_queries.select {|q| q.is_public?}
+ out << query_links(l(:label_query_plural), queries) if queries.any?
+ out
+ end
+
def show_detail(detail, no_html=false)
case detail.property
when 'attr'
@@ -164,7 +183,16 @@
end
end
- if !detail.value.blank?
+ if detail.property == 'attr' && detail.prop_key == 'description'
+ s = l(:text_journal_changed_no_detail, :label => label)
+ unless no_html
+ diff_link = link_to 'diff',
+ {:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id},
+ :title => l(:label_view_diff)
+ s << " (#{ diff_link })"
+ end
+ s
+ elsif !detail.value.blank?
case detail.property
when 'attr', 'cf'
if !detail.old_value.blank?
@@ -189,6 +217,20 @@
end
end
+ # Renders issue children recursively
+ def render_api_issue_children(issue, api)
+ return if issue.leaf?
+ api.array :children do
+ issue.children.each do |child|
+ api.issue(:id => child.id) do
+ api.tracker(:id => child.tracker_id, :name => child.tracker.name) unless child.tracker.nil?
+ api.subject child.subject
+ render_api_issue_children(child, api)
+ end
+ end
+ end
+ end
+
def issues_to_csv(issues, project = nil)
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
decimal_separator = l(:general_csv_decimal_separator)
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/.svn/text-base/messages_helper.rb.svn-base
--- a/app/helpers/.svn/text-base/messages_helper.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/.svn/text-base/messages_helper.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -16,14 +16,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MessagesHelper
-
- def link_to_message(message)
- return '' unless message
- link_to h(truncate(message.subject, :length => 60)), :controller => 'messages',
- :action => 'show',
- :board_id => message.board_id,
- :id => message.root,
- :r => (message.parent_id && message.id),
- :anchor => (message.parent_id ? "message-#{message.id}" : nil)
- end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/.svn/text-base/repositories_helper.rb.svn-base
--- a/app/helpers/.svn/text-base/repositories_helper.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/.svn/text-base/repositories_helper.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -18,16 +18,20 @@
require 'iconv'
module RepositoriesHelper
- def format_revision(txt)
- txt.to_s[0,8]
+ def format_revision(revision)
+ if revision.respond_to? :format_identifier
+ revision.format_identifier
+ else
+ revision.to_s
+ end
end
-
+
def truncate_at_line_break(text, length = 255)
if text
text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')
end
end
-
+
def render_properties(properties)
unless properties.nil? || properties.empty?
content = ''
@@ -37,7 +41,7 @@
content_tag('ul', content, :class => 'properties')
end
end
-
+
def render_changeset_changes
changes = @changeset.changes.find(:all, :limit => 1000, :order => 'path').collect do |change|
case change.action
@@ -71,10 +75,10 @@
render_changes_tree(tree[:s])
end
-
+
def render_changes_tree(tree)
return '' if tree.nil?
-
+
output = ''
output << ''
tree.keys.sort.each do |file|
@@ -87,7 +91,7 @@
:action => 'show',
:id => @project,
:path => path_param,
- :rev => @changeset.revision)
+ :rev => @changeset.identifier)
output << "
'
output
end
-
+
def to_utf8(str)
- return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
+ return str if str.blank?
+ if str.respond_to?(:force_encoding)
+ str.force_encoding('UTF-8')
+ else
+ # TODO:
+ # Japanese Shift_JIS(CP932) is not compatible with ASCII.
+ # UTF-7 and Japanese ISO-2022-JP are 7bits clean.
+ return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
+ end
+
@encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
@encodings.each do |encoding|
begin
@@ -122,67 +135,118 @@
# do nothing here and try the next encoding
end
end
+ str = replace_invalid_utf8(str)
+ end
+
+ def replace_invalid_utf8(str)
+ if str.respond_to?(:force_encoding)
+ str.force_encoding('UTF-8')
+ if ! str.valid_encoding?
+ str = str.encode("US-ASCII", :invalid => :replace,
+ :undef => :replace, :replace => '?').encode("UTF-8")
+ end
+ end
str
end
-
- def repository_field_tags(form, repository)
+
+ def repository_field_tags(form, repository)
method = repository.class.name.demodulize.underscore + "_field_tags"
- send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method) && method != 'repository_field_tags'
+ if repository.is_a?(Repository) &&
+ respond_to?(method) && method != 'repository_field_tags'
+ send(method, form, repository)
+ end
end
-
+
def scm_select_tag(repository)
scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
Redmine::Scm::Base.all.each do |scm|
- scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
+ if Setting.enabled_scm.include?(scm) ||
+ (repository && repository.class.name.demodulize == scm)
+ scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
+ end
end
-
select_tag('repository_scm',
options_for_select(scm_options, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?),
- :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
+ :onchange => remote_function(
+ :url => {
+ :controller => 'repositories',
+ :action => 'edit',
+ :id => @project
+ },
+ :method => :get,
+ :with => "Form.serialize(this.form)")
)
end
-
+
def with_leading_slash(path)
path.to_s.starts_with?('/') ? path : "/#{path}"
end
-
+
def without_leading_slash(path)
path.gsub(%r{^/+}, '')
end
def subversion_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
+ content_tag('p', form.text_field(:url, :size => 60, :required => true,
+ :disabled => (repository && !repository.root_url.blank?)) +
'
(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
content_tag('p', form.text_field(:login, :size => 30)) +
- content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
- :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
- :onfocus => "this.value=''; this.name='repository[password]';",
- :onchange => "this.name='repository[password]';"))
+ content_tag('p', form.password_field(
+ :password, :size => 30, :name => 'ignore',
+ :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
+ :onfocus => "this.value=''; this.name='repository[password]';",
+ :onchange => "this.name='repository[password]';"))
end
def darcs_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
+ content_tag('p', form.text_field(:url, :label => 'Root directory',
+ :size => 60, :required => true,
+ :disabled => (repository && !repository.new_record?))) +
+ content_tag('p', form.select(:log_encoding, [nil] + Setting::ENCODINGS,
+ :label => 'Commit messages encoding', :required => true))
end
-
+
def mercurial_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
+ content_tag('p', form.text_field(:url, :label => 'Root directory',
+ :size => 60, :required => true,
+ :disabled => (repository && !repository.root_url.blank?)) +
+ '
local repository (e.g. /hgrepo, c:\hgrepo)' )
end
def git_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
+ content_tag('p', form.text_field(:url, :label => 'Path to repository',
+ :size => 60, :required => true,
+ :disabled => (repository && !repository.root_url.blank?)) +
+ '
a bare and local repository (e.g. /gitrepo, c:\gitrepo)')
end
def cvs_field_tags(form, repository)
- content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
- content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
+ content_tag('p', form.text_field(:root_url,
+ :label => 'CVSROOT', :size => 60, :required => true,
+ :disabled => !repository.new_record?)) +
+ content_tag('p', form.text_field(:url, :label => 'Module',
+ :size => 30, :required => true,
+ :disabled => !repository.new_record?)) +
+ content_tag('p', form.select(:log_encoding, [nil] + Setting::ENCODINGS,
+ :label => 'Commit messages encoding', :required => true))
end
def bazaar_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
+ content_tag('p', form.text_field(:url, :label => 'Root directory',
+ :size => 60, :required => true,
+ :disabled => (repository && !repository.new_record?))) +
+ content_tag('p', form.select(:log_encoding, [nil] + Setting::ENCODINGS,
+ :label => 'Commit messages encoding', :required => true))
end
-
+
def filesystem_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
+ content_tag('p', form.text_field(:url, :label => 'Root directory',
+ :size => 60, :required => true,
+ :disabled => (repository && !repository.root_url.blank?))) +
+ content_tag('p', form.select(
+ :path_encoding, [nil] + Setting::ENCODINGS,
+ :label => 'Path encoding') +
+ '
Default: UTF-8')
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/.svn/text-base/users_helper.rb.svn-base
--- a/app/helpers/.svn/text-base/users_helper.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/.svn/text-base/users_helper.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -33,6 +33,10 @@
options
end
+ def user_mail_notification_options(user)
+ user.valid_notification_options.collect {|o| [l(o.last), o.first]}
+ end
+
def change_status_link(user)
url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/.svn/text-base/wiki_helper.rb.svn-base
--- a/app/helpers/.svn/text-base/wiki_helper.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/.svn/text-base/wiki_helper.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -29,41 +29,4 @@
end
s
end
-
- def html_diff(wdiff)
- words = wdiff.words.collect{|word| h(word)}
- words_add = 0
- words_del = 0
- dels = 0
- del_off = 0
- wdiff.diff.diffs.each do |diff|
- add_at = nil
- add_to = nil
- del_at = nil
- deleted = ""
- diff.each do |change|
- pos = change[1]
- if change[0] == "+"
- add_at = pos + dels unless add_at
- add_to = pos + dels
- words_add += 1
- else
- del_at = pos unless del_at
- deleted << ' ' + h(change[2])
- words_del += 1
- end
- end
- if add_at
- words[add_at] = '' + words[add_at]
- words[add_to] = words[add_to] + ''
- end
- if del_at
- words.insert del_at - del_off + dels + words_add, '' + deleted + ''
- dels += 1
- del_off += words_del
- words_del = 0
- end
- end
- simple_format_without_paragraph(words.join(' '))
- end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/application_helper.rb
--- a/app/helpers/application_helper.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/application_helper.rb Thu Mar 03 12:11:53 2011 +0000
@@ -109,6 +109,20 @@
link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
:title => l(:label_revision_id, format_revision(revision)))
end
+
+ # Generates a link to a message
+ def link_to_message(message, options={}, html_options = nil)
+ link_to(
+ h(truncate(message.subject, :length => 60)),
+ { :controller => 'messages', :action => 'show',
+ :board_id => message.board_id,
+ :id => message.root,
+ :r => (message.parent_id && message.id),
+ :anchor => (message.parent_id ? "message-#{message.id}" : nil)
+ }.merge(options),
+ html_options
+ )
+ end
# Generates a link to a project if active
# Examples:
@@ -458,12 +472,19 @@
only_path = options.delete(:only_path) == false ? false : true
text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
-
- parse_non_pre_blocks(text) do |text|
+
+ @parsed_headings = []
+ text = parse_non_pre_blocks(text) do |text|
[:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
send method_name, text, project, obj, attr, only_path, options
end
end
+
+ if @parsed_headings.any?
+ replace_toc(text, @parsed_headings)
+ end
+
+ text
end
def parse_non_pre_blocks(text)
@@ -588,16 +609,26 @@
# source:some/file#L120 -> Link to line 120 of the file
# source:some/file@52#L120 -> Link to line 120 of the file's revision 52
# export:some/file -> Force the download of the file
- # Forum messages:
+ # Forum messages:
# message#1218 -> Link to message with id 1218
+ #
+ # Links can refer other objects from other projects, using project identifier:
+ # identifier:r52
+ # identifier:document:"Some document"
+ # identifier:version:1.0.0
+ # identifier:source:some/file
def parse_redmine_links(text, project, obj, attr, only_path, options)
- text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
- leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
+ text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
+ leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
link = nil
+ if project_identifier
+ project = Project.visible.find_by_identifier(project_identifier)
+ end
if esc.nil?
if prefix.nil? && sep == 'r'
- if project && (changeset = project.changesets.find_by_revision(identifier))
- link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
+ # project.changesets.visible raises an SQL error because of a double join on repositories
+ if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
+ link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
:class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100))
end
@@ -611,24 +642,18 @@
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
end
when 'document'
- if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
+ if document = Document.visible.find_by_id(oid)
link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
:class => 'document'
end
when 'version'
- if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
+ if version = Version.visible.find_by_id(oid)
link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
:class => 'version'
end
when 'message'
- if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
- link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
- :controller => 'messages',
- :action => 'show',
- :board_id => message.board,
- :id => message.root,
- :anchor => (message.parent ? "message-#{message.id}" : nil)},
- :class => 'message'
+ if message = Message.visible.find_by_id(oid, :include => :parent)
+ link = link_to_message(message, {:only_path => only_path}, :class => 'message')
end
when 'project'
if p = Project.visible.find_by_id(oid)
@@ -640,26 +665,26 @@
name = identifier.gsub(%r{^"(.*)"$}, "\\1")
case prefix
when 'document'
- if project && document = project.documents.find_by_title(name)
+ if project && document = project.documents.visible.find_by_title(name)
link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
:class => 'document'
end
when 'version'
- if project && version = project.versions.find_by_name(name)
+ if project && version = project.versions.visible.find_by_name(name)
link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
:class => 'version'
end
when 'commit'
- if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
- link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
+ if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
+ link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
:class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100)
end
when 'source', 'export'
- if project && project.repository
+ if project && project.repository && User.current.allowed_to?(:browse_repository, project)
name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
path, rev, anchor = $1, $3, $5
- link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
+ link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
:path => to_path_param(path),
:rev => rev,
:anchor => anchor,
@@ -679,25 +704,30 @@
end
end
end
- leading + (link || "#{prefix}#{sep}#{identifier}")
+ leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")
end
end
- TOC_RE = /
')
+ end
+
+ def render_sidebar_queries
+ out = ''
+ queries = sidebar_queries.select {|q| !q.is_public?}
+ out << query_links(l(:label_my_queries), queries) if queries.any?
+ queries = sidebar_queries.select {|q| q.is_public?}
+ out << query_links(l(:label_query_plural), queries) if queries.any?
+ out
+ end
+
def show_detail(detail, no_html=false)
case detail.property
when 'attr'
@@ -164,7 +183,16 @@
end
end
- if !detail.value.blank?
+ if detail.property == 'attr' && detail.prop_key == 'description'
+ s = l(:text_journal_changed_no_detail, :label => label)
+ unless no_html
+ diff_link = link_to 'diff',
+ {:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id},
+ :title => l(:label_view_diff)
+ s << " (#{ diff_link })"
+ end
+ s
+ elsif !detail.value.blank?
case detail.property
when 'attr', 'cf'
if !detail.old_value.blank?
@@ -189,6 +217,20 @@
end
end
+ # Renders issue children recursively
+ def render_api_issue_children(issue, api)
+ return if issue.leaf?
+ api.array :children do
+ issue.children.each do |child|
+ api.issue(:id => child.id) do
+ api.tracker(:id => child.tracker_id, :name => child.tracker.name) unless child.tracker.nil?
+ api.subject child.subject
+ render_api_issue_children(child, api)
+ end
+ end
+ end
+ end
+
def issues_to_csv(issues, project = nil)
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
decimal_separator = l(:general_csv_decimal_separator)
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/messages_helper.rb
--- a/app/helpers/messages_helper.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/messages_helper.rb Thu Mar 03 12:11:53 2011 +0000
@@ -16,14 +16,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MessagesHelper
-
- def link_to_message(message)
- return '' unless message
- link_to h(truncate(message.subject, :length => 60)), :controller => 'messages',
- :action => 'show',
- :board_id => message.board_id,
- :id => message.root,
- :r => (message.parent_id && message.id),
- :anchor => (message.parent_id ? "message-#{message.id}" : nil)
- end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/projects_helper.rb
--- a/app/helpers/projects_helper.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/projects_helper.rb Thu Mar 03 12:11:53 2011 +0000
@@ -172,6 +172,8 @@
original_project = @project
+ level = 0
+
projects.each do |project|
s << render_project_in_table(project, cycle('odd', 'even'), 0)
end
@@ -179,8 +181,9 @@
s << ""
@project = original_project
-
s
+ level = 0
+ oddeven = cycle('odd','even')
end
@@ -216,11 +219,19 @@
end
if mgrs.size < 3
s << ''
tree.keys.sort.each do |file|
@@ -126,9 +115,18 @@
output << '
'
output
end
-
+
def to_utf8(str)
- return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
+ return str if str.blank?
+ if str.respond_to?(:force_encoding)
+ str.force_encoding('UTF-8')
+ else
+ # TODO:
+ # Japanese Shift_JIS(CP932) is not compatible with ASCII.
+ # UTF-7 and Japanese ISO-2022-JP are 7bits clean.
+ return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
+ end
+
@encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
@encodings.each do |encoding|
begin
@@ -137,68 +135,118 @@
# do nothing here and try the next encoding
end
end
+ str = replace_invalid_utf8(str)
+ end
+
+ def replace_invalid_utf8(str)
+ if str.respond_to?(:force_encoding)
+ str.force_encoding('UTF-8')
+ if ! str.valid_encoding?
+ str = str.encode("US-ASCII", :invalid => :replace,
+ :undef => :replace, :replace => '?').encode("UTF-8")
+ end
+ end
str
end
-
- def repository_field_tags(form, repository)
+
+ def repository_field_tags(form, repository)
method = repository.class.name.demodulize.underscore + "_field_tags"
- send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method) && method != 'repository_field_tags'
+ if repository.is_a?(Repository) &&
+ respond_to?(method) && method != 'repository_field_tags'
+ send(method, form, repository)
+ end
end
-
+
def scm_select_tag(repository)
scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
Redmine::Scm::Base.all.each do |scm|
- scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
+ if Setting.enabled_scm.include?(scm) ||
+ (repository && repository.class.name.demodulize == scm)
+ scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
+ end
end
-
select_tag('repository_scm',
options_for_select(scm_options, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?),
- :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
+ :onchange => remote_function(
+ :url => {
+ :controller => 'repositories',
+ :action => 'edit',
+ :id => @project
+ },
+ :method => :get,
+ :with => "Form.serialize(this.form)")
)
end
-
+
def with_leading_slash(path)
path.to_s.starts_with?('/') ? path : "/#{path}"
end
-
+
def without_leading_slash(path)
path.gsub(%r{^/+}, '')
end
def subversion_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
+ content_tag('p', form.text_field(:url, :size => 60, :required => true,
+ :disabled => (repository && !repository.root_url.blank?)) +
'
(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
content_tag('p', form.text_field(:login, :size => 30)) +
- content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
- :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
- :onfocus => "this.value=''; this.name='repository[password]';",
- :onchange => "this.name='repository[password]';"))
+ content_tag('p', form.password_field(
+ :password, :size => 30, :name => 'ignore',
+ :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
+ :onfocus => "this.value=''; this.name='repository[password]';",
+ :onchange => "this.name='repository[password]';"))
end
def darcs_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
+ content_tag('p', form.text_field(:url, :label => 'Root directory',
+ :size => 60, :required => true,
+ :disabled => (repository && !repository.new_record?))) +
+ content_tag('p', form.select(:log_encoding, [nil] + Setting::ENCODINGS,
+ :label => 'Commit messages encoding', :required => true))
end
-
+
def mercurial_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => true))
-# (repository && !repository.root_url.blank?)))
+ content_tag('p', form.text_field(:url, :label => 'Root directory',
+ :size => 60, :required => true,
+ :disabled => (repository && !repository.root_url.blank?)) +
+ '
local repository (e.g. /hgrepo, c:\hgrepo)' )
end
def git_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
+ content_tag('p', form.text_field(:url, :label => 'Path to repository',
+ :size => 60, :required => true,
+ :disabled => (repository && !repository.root_url.blank?)) +
+ '
a bare and local repository (e.g. /gitrepo, c:\gitrepo)')
end
def cvs_field_tags(form, repository)
- content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
- content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
+ content_tag('p', form.text_field(:root_url,
+ :label => 'CVSROOT', :size => 60, :required => true,
+ :disabled => !repository.new_record?)) +
+ content_tag('p', form.text_field(:url, :label => 'Module',
+ :size => 30, :required => true,
+ :disabled => !repository.new_record?)) +
+ content_tag('p', form.select(:log_encoding, [nil] + Setting::ENCODINGS,
+ :label => 'Commit messages encoding', :required => true))
end
def bazaar_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
+ content_tag('p', form.text_field(:url, :label => 'Root directory',
+ :size => 60, :required => true,
+ :disabled => (repository && !repository.new_record?))) +
+ content_tag('p', form.select(:log_encoding, [nil] + Setting::ENCODINGS,
+ :label => 'Commit messages encoding', :required => true))
end
-
+
def filesystem_field_tags(form, repository)
- content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
+ content_tag('p', form.text_field(:url, :label => 'Root directory',
+ :size => 60, :required => true,
+ :disabled => (repository && !repository.root_url.blank?))) +
+ content_tag('p', form.select(
+ :path_encoding, [nil] + Setting::ENCODINGS,
+ :label => 'Path encoding') +
+ '
Default: UTF-8')
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/users_helper.rb
--- a/app/helpers/users_helper.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/users_helper.rb Thu Mar 03 12:11:53 2011 +0000
@@ -33,6 +33,10 @@
options
end
+ def user_mail_notification_options(user)
+ user.valid_notification_options.collect {|o| [l(o.last), o.first]}
+ end
+
def change_status_link(user)
url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/helpers/wiki_helper.rb
--- a/app/helpers/wiki_helper.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/helpers/wiki_helper.rb Thu Mar 03 12:11:53 2011 +0000
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -29,41 +29,4 @@
end
s
end
-
- def html_diff(wdiff)
- words = wdiff.words.collect{|word| h(word)}
- words_add = 0
- words_del = 0
- dels = 0
- del_off = 0
- wdiff.diff.diffs.each do |diff|
- add_at = nil
- add_to = nil
- del_at = nil
- deleted = ""
- diff.each do |change|
- pos = change[1]
- if change[0] == "+"
- add_at = pos + dels unless add_at
- add_to = pos + dels
- words_add += 1
- else
- del_at = pos unless del_at
- deleted << ' ' + h(change[2])
- words_del += 1
- end
- end
- if add_at
- words[add_at] = '' + words[add_at]
- words[add_to] = words[add_to] + ''
- end
- if del_at
- words.insert del_at - del_off + dels + words_add, '' + deleted + ''
- dels += 1
- del_off += words_del
- words_del = 0
- end
- end
- simple_format_without_paragraph(words.join(' '))
- end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/all-wcprops
--- a/app/models/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/all-wcprops Thu Mar 03 12:11:53 2011 +0000
@@ -1,31 +1,31 @@
K 25
svn:wc:ra_dav:version-url
V 35
-/svn/!svn/ver/4404/trunk/app/models
+/svn/!svn/ver/4982/trunk/app/models
END
document.rb
K 25
svn:wc:ra_dav:version-url
V 47
-/svn/!svn/ver/3358/trunk/app/models/document.rb
+/svn/!svn/ver/4759/trunk/app/models/document.rb
END
mail_handler.rb
K 25
svn:wc:ra_dav:version-url
V 51
-/svn/!svn/ver/4404/trunk/app/models/mail_handler.rb
+/svn/!svn/ver/4820/trunk/app/models/mail_handler.rb
END
repository.rb
K 25
svn:wc:ra_dav:version-url
V 49
-/svn/!svn/ver/3472/trunk/app/models/repository.rb
+/svn/!svn/ver/4982/trunk/app/models/repository.rb
END
time_entry.rb
K 25
svn:wc:ra_dav:version-url
V 49
-/svn/!svn/ver/4277/trunk/app/models/time_entry.rb
+/svn/!svn/ver/4708/trunk/app/models/time_entry.rb
END
token.rb
K 25
@@ -43,7 +43,7 @@
K 25
svn:wc:ra_dav:version-url
V 55
-/svn/!svn/ver/3492/trunk/app/models/auth_source_ldap.rb
+/svn/!svn/ver/4950/trunk/app/models/auth_source_ldap.rb
END
principal.rb
K 25
@@ -81,11 +81,17 @@
V 50
/svn/!svn/ver/3240/trunk/app/models/enumeration.rb
END
+member_role.rb
+K 25
+svn:wc:ra_dav:version-url
+V 50
+/svn/!svn/ver/3250/trunk/app/models/member_role.rb
+END
issue.rb
K 25
svn:wc:ra_dav:version-url
V 44
-/svn/!svn/ver/4394/trunk/app/models/issue.rb
+/svn/!svn/ver/4954/trunk/app/models/issue.rb
END
time_entry_activity_custom_field.rb
K 25
@@ -93,12 +99,6 @@
V 71
/svn/!svn/ver/2952/trunk/app/models/time_entry_activity_custom_field.rb
END
-member_role.rb
-K 25
-svn:wc:ra_dav:version-url
-V 50
-/svn/!svn/ver/3250/trunk/app/models/member_role.rb
-END
issue_priority.rb
K 25
svn:wc:ra_dav:version-url
@@ -133,7 +133,7 @@
K 25
svn:wc:ra_dav:version-url
V 48
-/svn/!svn/ver/4296/trunk/app/models/wiki_page.rb
+/svn/!svn/ver/4952/trunk/app/models/wiki_page.rb
END
comment.rb
K 25
@@ -151,7 +151,7 @@
K 25
svn:wc:ra_dav:version-url
V 44
-/svn/!svn/ver/4387/trunk/app/models/query.rb
+/svn/!svn/ver/4888/trunk/app/models/query.rb
END
member.rb
K 25
@@ -169,7 +169,7 @@
K 25
svn:wc:ra_dav:version-url
V 45
-/svn/!svn/ver/4373/trunk/app/models/mailer.rb
+/svn/!svn/ver/4752/trunk/app/models/mailer.rb
END
journal.rb
K 25
@@ -199,25 +199,25 @@
K 25
svn:wc:ra_dav:version-url
V 51
-/svn/!svn/ver/3672/trunk/app/models/custom_field.rb
+/svn/!svn/ver/4599/trunk/app/models/custom_field.rb
END
setting.rb
K 25
svn:wc:ra_dav:version-url
V 46
-/svn/!svn/ver/3417/trunk/app/models/setting.rb
+/svn/!svn/ver/4899/trunk/app/models/setting.rb
END
user.rb
K 25
svn:wc:ra_dav:version-url
V 43
-/svn/!svn/ver/4234/trunk/app/models/user.rb
+/svn/!svn/ver/4936/trunk/app/models/user.rb
END
auth_source.rb
K 25
svn:wc:ra_dav:version-url
V 50
-/svn/!svn/ver/3745/trunk/app/models/auth_source.rb
+/svn/!svn/ver/4950/trunk/app/models/auth_source.rb
END
news_observer.rb
K 25
@@ -229,7 +229,7 @@
K 25
svn:wc:ra_dav:version-url
V 46
-/svn/!svn/ver/3373/trunk/app/models/message.rb
+/svn/!svn/ver/4759/trunk/app/models/message.rb
END
wiki_redirect.rb
K 25
@@ -241,7 +241,7 @@
K 25
svn:wc:ra_dav:version-url
V 53
-/svn/!svn/ver/3729/trunk/app/models/issue_relation.rb
+/svn/!svn/ver/4467/trunk/app/models/issue_relation.rb
END
document_observer.rb
K 25
@@ -253,7 +253,7 @@
K 25
svn:wc:ra_dav:version-url
V 46
-/svn/!svn/ver/4073/trunk/app/models/version.rb
+/svn/!svn/ver/4574/trunk/app/models/version.rb
END
issue_priority_custom_field.rb
K 25
@@ -265,13 +265,13 @@
K 25
svn:wc:ra_dav:version-url
V 44
-/svn/!svn/ver/3167/trunk/app/models/board.rb
+/svn/!svn/ver/4431/trunk/app/models/board.rb
END
attachment.rb
K 25
svn:wc:ra_dav:version-url
V 49
-/svn/!svn/ver/3774/trunk/app/models/attachment.rb
+/svn/!svn/ver/4755/trunk/app/models/attachment.rb
END
group_custom_field.rb
K 25
@@ -283,7 +283,7 @@
K 25
svn:wc:ra_dav:version-url
V 51
-/svn/!svn/ver/3881/trunk/app/models/issue_status.rb
+/svn/!svn/ver/4895/trunk/app/models/issue_status.rb
END
time_entry_activity.rb
K 25
@@ -295,19 +295,19 @@
K 25
svn:wc:ra_dav:version-url
V 46
-/svn/!svn/ver/3188/trunk/app/models/tracker.rb
+/svn/!svn/ver/4599/trunk/app/models/tracker.rb
END
journal_detail.rb
K 25
svn:wc:ra_dav:version-url
-V 52
-/svn/!svn/ver/478/trunk/app/models/journal_detail.rb
+V 53
+/svn/!svn/ver/4954/trunk/app/models/journal_detail.rb
END
group.rb
K 25
svn:wc:ra_dav:version-url
V 44
-/svn/!svn/ver/2869/trunk/app/models/group.rb
+/svn/!svn/ver/4437/trunk/app/models/group.rb
END
issue_observer.rb
K 25
@@ -319,13 +319,13 @@
K 25
svn:wc:ra_dav:version-url
V 48
-/svn/!svn/ver/3928/trunk/app/models/changeset.rb
+/svn/!svn/ver/4962/trunk/app/models/changeset.rb
END
role.rb
K 25
svn:wc:ra_dav:version-url
V 43
-/svn/!svn/ver/3363/trunk/app/models/role.rb
+/svn/!svn/ver/4599/trunk/app/models/role.rb
END
project_custom_field.rb
K 25
@@ -337,7 +337,7 @@
K 25
svn:wc:ra_dav:version-url
V 43
-/svn/!svn/ver/3632/trunk/app/models/wiki.rb
+/svn/!svn/ver/4680/trunk/app/models/wiki.rb
END
custom_value.rb
K 25
@@ -349,7 +349,7 @@
K 25
svn:wc:ra_dav:version-url
V 46
-/svn/!svn/ver/4402/trunk/app/models/project.rb
+/svn/!svn/ver/4645/trunk/app/models/project.rb
END
document_category.rb
K 25
@@ -361,11 +361,11 @@
K 25
svn:wc:ra_dav:version-url
V 43
-/svn/!svn/ver/3358/trunk/app/models/news.rb
+/svn/!svn/ver/4505/trunk/app/models/news.rb
END
user_preference.rb
K 25
svn:wc:ra_dav:version-url
V 54
-/svn/!svn/ver/1623/trunk/app/models/user_preference.rb
+/svn/!svn/ver/4900/trunk/app/models/user_preference.rb
END
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/entries
--- a/app/models/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,15 +1,15 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/models
http://redmine.rubyforge.org/svn
-2010-11-14T13:48:01.671461Z
-4404
-jplang
+2011-03-01T10:27:30.170724Z
+4982
+tmaruyama
@@ -35,11 +35,11 @@
-2010-09-23T14:37:44.471831Z
-6edd75717c818c718387e30ec7aee840
-2010-02-01T18:57:12.733490Z
-3358
-edavis10
+2011-03-03T11:05:09.000000Z
+190c117fe2ccfc4b1ebe70ae94e0d779
+2011-01-23T16:47:59.732003Z
+4759
+jplang
has-props
@@ -61,7 +61,7 @@
-1995
+2197
mail_handler.rb
file
@@ -69,11 +69,11 @@
-2010-11-19T13:04:46.952746Z
-dd4785525bab24b6791967d66ffe6cb9
-2010-11-14T13:48:01.671461Z
-4404
-jplang
+2011-03-03T11:40:18.000000Z
+ed655ccb5c306f548b3997d2dd3e3d60
+2011-02-12T10:08:11.630366Z
+4820
+jbbarth
has-props
@@ -95,7 +95,7 @@
-13455
+13778
repository.rb
file
@@ -103,11 +103,11 @@
-2010-09-24T11:56:52.924049Z
-6d9599543924edc24abb739bd3f7e718
-2010-02-21T14:42:45.276472Z
-3472
-jplang
+2011-03-03T11:40:18.000000Z
+a3fb46324c320aab8869e523c2a35d2a
+2011-03-01T10:27:30.170724Z
+4982
+tmaruyama
has-props
@@ -129,7 +129,7 @@
-6831
+8497
time_entry.rb
file
@@ -137,10 +137,10 @@
-2010-11-19T13:04:46.956816Z
-aa2cc7da3c19ce52813f08949e6cefec
-2010-10-22T20:40:11.668687Z
-4277
+2011-03-03T11:05:09.000000Z
+db327d70b8e3bcf04992bf7c69522467
+2011-01-14T18:04:16.470040Z
+4708
jplang
has-props
@@ -163,7 +163,7 @@
-4087
+4162
token.rb
file
@@ -171,7 +171,7 @@
-2010-09-23T14:37:44.487730Z
+2011-03-03T11:05:09.000000Z
441a5648e149e471c91928dc7f3f29a6
2009-06-02T17:24:50.360628Z
2778
@@ -205,7 +205,7 @@
-2010-11-19T13:04:46.956816Z
+2011-03-03T11:05:09.000000Z
7d895b3a013ce8f4e63dd4e7aab7eb20
2010-09-28T20:20:00.843023Z
4221
@@ -239,10 +239,10 @@
-2010-09-23T14:37:44.467732Z
-87f2ff0401993963de36fe7502cd6949
-2010-02-26T09:13:12.187275Z
-3492
+2011-03-03T11:40:18.000000Z
+82898965a0c07d8fa25a52cfced3139f
+2011-02-26T13:09:25.657748Z
+4950
jplang
has-props
@@ -273,7 +273,7 @@
-2010-09-24T12:48:25.923798Z
+2011-03-03T11:05:09.000000Z
01332c0957f952b76c01e7c7ebd112f0
2010-09-10T18:46:23.747988Z
4076
@@ -307,7 +307,7 @@
-2010-09-23T14:37:44.491776Z
+2011-03-03T11:05:09.000000Z
6bf31c0491d87fff1481a91519f66363
2009-11-15T15:22:55.021661Z
3064
@@ -341,7 +341,7 @@
-2010-09-23T14:37:44.471831Z
+2011-03-03T11:05:09.000000Z
cd405a055651d29541f2ebcf4e22f8dc
2007-03-15T22:11:02.843262Z
337
@@ -375,7 +375,7 @@
-2010-09-23T14:37:44.471831Z
+2011-03-03T11:05:09.000000Z
da42817b3f290549a03ada82a93dc5d0
2007-12-07T10:26:07.864320Z
955
@@ -409,7 +409,7 @@
-2010-09-23T14:37:44.467732Z
+2011-03-03T11:05:09.000000Z
622826f16c01373c631876eb1e480a65
2010-07-24T00:19:32.307471Z
3860
@@ -443,7 +443,7 @@
-2010-09-23T14:37:44.471831Z
+2011-03-03T11:05:09.000000Z
90ec955317994a58df9a5a7f85b810e7
2009-12-25T17:13:58.090736Z
3240
@@ -471,13 +471,81 @@
3856
+issue.rb
+file
+
+
+
+
+2011-03-03T11:40:18.000000Z
+de5e44263227d075bddb23d8f478e24d
+2011-02-27T13:34:41.060565Z
+4954
+jplang
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+32300
+
+member_role.rb
+file
+
+
+
+
+2011-03-03T11:05:09.000000Z
+95caba31fc55fea2d5ff257e70cf14b2
+2009-12-26T16:14:55.591181Z
+3250
+jplang
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2011
+
time_entry_activity_custom_field.rb
file
-2010-09-23T14:37:44.487730Z
+2011-03-03T11:05:09.000000Z
d5549b2847a255d4bdd7185a670988b5
2009-10-21T22:35:03.091600Z
2952
@@ -505,81 +573,13 @@
896
-member_role.rb
-file
-
-
-
-
-2010-09-23T14:37:44.475780Z
-95caba31fc55fea2d5ff257e70cf14b2
-2009-12-26T16:14:55.591181Z
-3250
-jplang
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-2011
-
-issue.rb
-file
-
-
-
-
-2010-11-19T13:04:46.956816Z
-35d32fdd4b71df26207f71005d93580e
-2010-11-12T11:34:53.754717Z
-4394
-jplang
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-32352
-
issue_priority.rb
file
-2010-09-23T14:37:44.471831Z
+2011-03-03T11:05:09.000000Z
028b9a9be35e74573921f9865f953439
2009-12-25T17:13:58.090736Z
3240
@@ -613,7 +613,7 @@
-2010-09-23T14:37:44.475780Z
+2011-03-03T11:05:09.000000Z
50c2305c38ffecfb2138cd437e1c255c
2009-12-13T14:26:54.396245Z
3169
@@ -641,13 +641,47 @@
979
+watcher.rb
+file
+
+
+
+
+2011-03-03T11:05:09.000000Z
+c72443f206052fafae720378fba40163
+2009-12-13T12:39:22.716082Z
+3167
+jplang
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2093
+
wiki_content.rb
file
-2010-11-19T13:04:46.956816Z
+2011-03-03T11:05:09.000000Z
3cac6a9c934a472fcbdf99cd00e5bfbb
2010-10-27T16:27:06.240747Z
4296
@@ -675,81 +709,13 @@
4480
-watcher.rb
-file
-
-
-
-
-2010-09-23T14:37:44.491776Z
-c72443f206052fafae720378fba40163
-2009-12-13T12:39:22.716082Z
-3167
-jplang
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-2093
-
-wiki_page.rb
-file
-
-
-
-
-2010-11-19T13:04:46.956816Z
-a45da72d99c7bc05ce93eb86205942e5
-2010-10-27T16:27:06.240747Z
-4296
-edavis10
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-6720
-
enabled_module.rb
file
-2010-09-23T14:37:44.471831Z
+2011-03-03T11:05:09.000000Z
56631baa5560f63cbb213855761c8fcc
2009-10-25T10:31:01.135799Z
2970
@@ -777,13 +743,47 @@
1296
+wiki_page.rb
+file
+
+
+
+
+2011-03-03T11:40:18.000000Z
+f95a3be2b4090d0bd5b7d74b9e1d0a7c
+2011-02-27T12:35:31.414622Z
+4952
+jplang
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+6539
+
comment.rb
file
-2010-09-23T14:37:44.467732Z
+2011-03-03T11:05:09.000000Z
0ca314834c39ddc12f2993caf5722e79
2007-04-25T15:06:20.062636Z
479
@@ -817,7 +817,7 @@
-2010-09-23T14:37:44.491776Z
+2011-03-03T11:05:09.000000Z
a6804cb0c0abc6de72a184e7384ed36d
2009-05-17T09:55:13.601447Z
2749
@@ -851,10 +851,10 @@
-2010-11-19T13:04:46.956816Z
-ab1aeac4c9dd4c6ff846a281927b515a
-2010-11-07T15:38:51.908839Z
-4387
+2011-03-03T11:40:18.000000Z
+886a34f5ef28f29a9eadb6e24dee89d6
+2011-02-20T13:03:32.835478Z
+4888
jplang
has-props
@@ -877,7 +877,7 @@
-26148
+26359
member.rb
file
@@ -885,7 +885,7 @@
-2010-09-23T14:37:44.475780Z
+2011-03-03T11:05:09.000000Z
84d28844bbac90dab2c56a27ed58db18
2010-08-09T05:10:00.803890Z
3929
@@ -919,7 +919,7 @@
-2010-09-23T14:37:44.471831Z
+2011-03-03T11:05:09.000000Z
60dadf29c4da2a4e97f463a647b8e98d
2009-10-21T22:34:22.740755Z
2945
@@ -953,10 +953,10 @@
-2010-11-19T13:04:46.956816Z
-1c4b2494e527ab830998b06f67b18f52
-2010-11-06T13:23:23.456511Z
-4373
+2011-03-03T11:05:09.000000Z
+f7340d6a500dabc6fa0aae801539d5e5
+2011-01-23T10:22:00.456857Z
+4752
jplang
has-props
@@ -979,7 +979,41 @@
-18490
+18498
+
+journal.rb
+file
+
+
+
+
+2011-03-03T11:05:09.000000Z
+f3797cd6a4b7e4d256aba15e317e98b1
+2010-09-05T22:57:20.669640Z
+4062
+edavis10
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+3214
workflow.rb
file
@@ -987,7 +1021,7 @@
-2010-09-23T14:37:44.491776Z
+2011-03-03T11:05:09.000000Z
4561dfcb12b3fa066a9516c59a9eef94
2010-04-30T12:19:51.238583Z
3709
@@ -1015,47 +1049,13 @@
4028
-journal.rb
-file
-
-
-
-
-2010-09-24T12:48:25.923798Z
-f3797cd6a4b7e4d256aba15e317e98b1
-2010-09-05T22:57:20.669640Z
-4062
-edavis10
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-3214
-
user_custom_field.rb
file
-2010-09-23T14:37:44.487730Z
+2011-03-03T11:05:09.000000Z
47067c42f4d6607335adf75d11c3323b
2007-03-12T17:59:02.654744Z
333
@@ -1089,7 +1089,7 @@
-2010-09-23T14:37:44.487730Z
+2011-03-03T11:05:09.000000Z
7dbc4f88d9fcd8f1057ea6d5982a1099
2008-07-22T18:52:00.008047Z
1689
@@ -1123,11 +1123,11 @@
-2010-09-23T14:37:44.467732Z
-48b90644181e44606c63f82c3655baa1
-2010-04-16T15:33:49.924704Z
-3672
-edavis10
+2011-03-03T11:05:09.000000Z
+a81e0b41100d375e6327075ab1f59242
+2010-12-31T15:30:50.544770Z
+4599
+jplang
has-props
@@ -1149,7 +1149,7 @@
-4344
+4287
setting.rb
file
@@ -1157,11 +1157,11 @@
-2010-09-23T14:37:44.487730Z
-5a0ad520fc95e7fefd2c6e6a1c9c3e76
-2010-02-12T22:08:02.981984Z
-3417
-edavis10
+2011-03-03T11:40:18.000000Z
+2815413f18c2ff6c015f0742ac28710a
+2011-02-21T09:41:34.953350Z
+4899
+tmaruyama
has-props
@@ -1183,7 +1183,7 @@
-5164
+5188
user.rb
file
@@ -1191,11 +1191,11 @@
-2010-11-19T13:04:46.956816Z
-de9dd6a174b66d7a7f16a699f1f1698f
-2010-10-06T05:08:38.847540Z
-4234
-jbbarth
+2011-03-03T11:40:18.000000Z
+8616fc89354fef549fdfc741600c1118
+2011-02-23T17:27:31.762248Z
+4936
+jplang
has-props
@@ -1217,7 +1217,7 @@
-15699
+19152
auth_source.rb
file
@@ -1225,11 +1225,11 @@
-2010-09-23T14:37:44.467732Z
-837db713425e094779466a392a668679
-2010-05-23T03:16:37.499264Z
-3745
-edavis10
+2011-03-03T11:40:18.000000Z
+f746a9534698bb8db5abf4bb7c9dd5ef
+2011-02-26T13:09:25.657748Z
+4950
+jplang
has-props
@@ -1251,7 +1251,7 @@
-1811
+2013
news_observer.rb
file
@@ -1259,7 +1259,7 @@
-2010-09-23T14:37:44.475780Z
+2011-03-03T11:05:09.000000Z
b98015db4679606ee5f8d75e55574745
2009-03-28T00:38:57.780983Z
2637
@@ -1293,10 +1293,10 @@
-2010-09-23T14:37:44.475780Z
-66b09263cf7988c661307523991d83d5
-2010-02-06T12:54:13.653502Z
-3373
+2011-03-03T11:05:09.000000Z
+82f0a68c26f05f4ed6aa94418a447a00
+2011-01-23T16:47:59.732003Z
+4759
jplang
has-props
@@ -1319,7 +1319,7 @@
-3576
+3789
wiki_redirect.rb
file
@@ -1327,7 +1327,7 @@
-2010-09-23T14:37:44.491776Z
+2011-03-03T11:05:09.000000Z
4e4c64bab677a9e081fc1230ff6a3773
2007-09-09T17:05:38.846724Z
720
@@ -1361,10 +1361,10 @@
-2010-09-23T14:37:44.475780Z
-c90f5daaa6fe06429b93f2010ba90fa0
-2010-05-01T14:07:36.553776Z
-3729
+2011-03-03T11:05:09.000000Z
+e79f69fb89a7015471e190050ba97847
+2010-12-04T18:10:02.635321Z
+4467
jplang
has-props
@@ -1387,7 +1387,7 @@
-4550
+4580
document_observer.rb
file
@@ -1395,7 +1395,7 @@
-2010-09-23T14:37:44.471831Z
+2011-03-03T11:05:09.000000Z
b76852d33bcfab19bb6c4e3e727da0ed
2009-03-28T00:38:57.780983Z
2637
@@ -1429,11 +1429,11 @@
-2010-09-24T12:48:25.927735Z
-7f91847223c06c86cef6696956c67846
-2010-09-10T03:09:11.557425Z
-4073
-edavis10
+2011-03-03T11:05:09.000000Z
+9fdcb569734799b2fface5b9c6207856
+2010-12-23T14:58:52.304263Z
+4574
+jplang
has-props
@@ -1455,7 +1455,7 @@
-7741
+7615
issue_priority_custom_field.rb
file
@@ -1463,7 +1463,7 @@
-2010-09-23T14:37:44.475780Z
+2011-03-03T11:05:09.000000Z
478dceec50480a17a7f2c321fc59e794
2009-10-21T22:34:22.740755Z
2945
@@ -1497,10 +1497,10 @@
-2010-09-23T14:37:44.467732Z
-9927f674ea1f10f785af7adfd11e6f83
-2009-12-13T12:39:22.716082Z
-3167
+2011-03-03T11:05:09.000000Z
+aaa3cd558c500d4db3bb506878e12fc1
+2010-11-27T12:42:11.793330Z
+4431
jplang
has-props
@@ -1523,7 +1523,7 @@
-2140
+2137
attachment.rb
file
@@ -1531,11 +1531,11 @@
-2010-09-23T14:37:44.467732Z
-56226fcf13a57b18ed9faf979b5cf1bc
-2010-06-19T03:54:28.250424Z
-3774
-edavis10
+2011-03-03T11:05:09.000000Z
+527c189ff333588f46ed31db163dcc5c
+2011-01-23T10:41:43.320139Z
+4755
+jplang
has-props
@@ -1557,7 +1557,7 @@
-6702
+6756
group_custom_field.rb
file
@@ -1565,7 +1565,7 @@
-2010-09-23T14:37:44.471831Z
+2011-03-03T11:05:09.000000Z
2c07c440361210f71d2eb821ce95ce12
2009-09-12T08:36:46.650954Z
2869
@@ -1599,10 +1599,10 @@
-2010-09-23T14:37:44.475780Z
-44dc499c4e4dfc3601165f07e6daa4b5
-2010-07-25T10:48:27.199057Z
-3881
+2011-03-03T11:40:18.000000Z
+4670d7c2c9ef9e23b0812e79c84c02de
+2011-02-20T15:38:07.840581Z
+4895
jplang
has-props
@@ -1625,7 +1625,7 @@
-3447
+3396
time_entry_activity.rb
file
@@ -1633,7 +1633,7 @@
-2010-09-23T14:37:44.487730Z
+2011-03-03T11:05:09.000000Z
2508e3911e5bb016a9925502244b358f
2009-12-25T17:13:58.090736Z
3240
@@ -1667,10 +1667,10 @@
-2010-09-23T14:37:44.487730Z
-00670b3218d9d2d2a8435809f524827f
-2009-12-18T15:41:32.828284Z
-3188
+2011-03-03T11:05:09.000000Z
+e7d08a30fb7e14fd7486a7d7e04efccb
+2010-12-31T15:30:50.544770Z
+4599
jplang
has-props
@@ -1693,7 +1693,7 @@
-2205
+2150
journal_detail.rb
file
@@ -1701,10 +1701,10 @@
-2010-09-23T14:37:44.475780Z
-4b700215d4c3bf72071a45448fa2b0e3
-2007-04-24T18:26:42.003879Z
-478
+2011-03-03T11:40:18.000000Z
+5f341c076b3e061541111187994969c8
+2011-02-27T13:34:41.060565Z
+4954
jplang
has-props
@@ -1727,7 +1727,7 @@
-1028
+864
group.rb
file
@@ -1735,10 +1735,10 @@
-2010-09-23T14:37:44.471831Z
-7fa34bcff98b6a84bbe125d949af18af
-2009-09-12T08:36:46.650954Z
-2869
+2011-03-03T11:05:09.000000Z
+a6be23c5316bf4e17cd5601624202b18
+2010-11-27T14:46:51.426568Z
+4437
jplang
has-props
@@ -1761,7 +1761,7 @@
-1884
+1918
issue_observer.rb
file
@@ -1769,7 +1769,7 @@
-2010-09-23T14:37:44.471831Z
+2011-03-03T11:05:09.000000Z
d4026d660fcb2601834d26268468aec2
2009-03-28T00:38:57.780983Z
2637
@@ -1803,11 +1803,11 @@
-2010-09-24T11:56:52.912062Z
-434403a24695ff195df946e1db456ca7
-2010-08-08T20:26:33.212059Z
-3928
-jbbarth
+2011-03-03T11:40:18.000000Z
+949462e61d78ec8bd5afc1af77363985
+2011-02-28T12:09:32.515358Z
+4962
+tmaruyama
has-props
@@ -1829,7 +1829,7 @@
-7481
+9103
role.rb
file
@@ -1837,11 +1837,11 @@
-2010-09-23T14:37:44.487730Z
-47730c63e98da3fdb40ab8551ee3d509
-2010-02-03T17:47:47.868223Z
-3363
-edavis10
+2011-03-03T11:05:09.000000Z
+15c62eb20c0904d6061c427984ea5ad6
+2010-12-31T15:30:50.544770Z
+4599
+jplang
has-props
@@ -1863,7 +1863,7 @@
-5143
+5088
project_custom_field.rb
file
@@ -1871,7 +1871,7 @@
-2010-09-23T14:37:44.479733Z
+2011-03-03T11:05:09.000000Z
926ef98901f13ba13688fdc3ab0184fc
2007-03-12T17:59:02.654744Z
333
@@ -1905,10 +1905,10 @@
-2010-09-23T14:37:44.491776Z
-5012ec7e38525085a500671f68708ba5
-2010-04-11T12:56:18.077630Z
-3632
+2011-03-03T11:05:09.000000Z
+c1467b11ffdf01173add19cbdcbecccd
+2011-01-10T18:32:04.408692Z
+4680
jplang
has-props
@@ -1931,7 +1931,7 @@
-3031
+3109
custom_value.rb
file
@@ -1939,7 +1939,7 @@
-2010-11-19T13:04:46.960863Z
+2011-03-03T11:05:09.000000Z
6938d61d0f9e1fc190bb1d0e6c05546e
2010-11-07T14:15:01.891476Z
4382
@@ -1973,10 +1973,10 @@
-2010-11-19T13:04:46.960863Z
-87ee5ad7f6983a739700f40cff0d65ae
-2010-11-14T12:33:14.198318Z
-4402
+2011-03-03T11:05:09.000000Z
+85527d6304192255d188255b78b10fb5
+2011-01-06T20:36:31.826591Z
+4645
jplang
has-props
@@ -1999,7 +1999,7 @@
-30070
+31594
document_category.rb
file
@@ -2007,7 +2007,7 @@
-2010-09-23T14:37:44.471831Z
+2011-03-03T11:05:09.000000Z
aeaf540cf9f940cbec4ddfc984dd6af5
2009-12-25T17:13:58.090736Z
3240
@@ -2041,11 +2041,11 @@
-2010-09-23T14:37:44.475780Z
-b1b4aa93c05ff00a28d777ecdd77d66d
-2010-02-01T18:57:12.733490Z
-3358
-edavis10
+2011-03-03T11:05:09.000000Z
+ce0648ce457209d4a582b3076287bf01
+2010-12-12T17:00:52.100205Z
+4505
+jplang
has-props
@@ -2067,7 +2067,7 @@
-1885
+2051
user_preference.rb
file
@@ -2075,10 +2075,10 @@
-2010-09-23T14:37:44.487730Z
-41ea06399352eb7b8c2903357abe1ab0
-2008-07-04T17:58:14.743502Z
-1623
+2011-03-03T11:40:18.000000Z
+feac9af79af67a78c767bd3def262255
+2011-02-21T09:53:29.844413Z
+4900
jplang
has-props
@@ -2101,5 +2101,5 @@
-1513
+1671
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/attachment.rb.svn-base
--- a/app/models/.svn/text-base/attachment.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/attachment.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -43,7 +43,7 @@
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
cattr_accessor :storage_path
- @@storage_path = "#{RAILS_ROOT}/files"
+ @@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{RAILS_ROOT}/files"
def validate
if self.filesize > Setting.attachment_max_size.to_i.kilobytes
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/auth_source.rb.svn-base
--- a/app/models/.svn/text-base/auth_source.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/auth_source.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AuthSource < ActiveRecord::Base
+ include Redmine::Ciphering
+
has_many :users
validates_presence_of :name
@@ -31,6 +33,14 @@
def auth_method_name
"Abstract"
end
+
+ def account_password
+ read_ciphered_attribute(:account_password)
+ end
+
+ def account_password=(arg)
+ write_ciphered_attribute(:account_password, arg)
+ end
def allow_password_changes?
self.class.allow_password_changes?
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/auth_source_ldap.rb.svn-base
--- a/app/models/.svn/text-base/auth_source_ldap.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/auth_source_ldap.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -20,8 +20,8 @@
class AuthSourceLdap < AuthSource
validates_presence_of :host, :port, :attr_login
- validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true
- validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true
+ validates_length_of :name, :host, :maximum => 60, :allow_nil => true
+ validates_length_of :account, :account_password, :base_dn, :maximum => 255, :allow_nil => true
validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
validates_numericality_of :port, :only_integer => true
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/board.rb.svn-base
--- a/app/models/.svn/text-base/board.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/board.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -18,7 +18,7 @@
class Board < ActiveRecord::Base
belongs_to :project
has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
- has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC"
+ has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC"
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
acts_as_list :scope => :project_id
acts_as_watchable
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/changeset.rb.svn-base
--- a/app/models/.svn/text-base/changeset.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/changeset.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -23,10 +23,10 @@
has_many :changes, :dependent => :delete_all
has_and_belongs_to_many :issues
- acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
+ acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
:description => :long_comments,
:datetime => :committed_on,
- :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.revision}}
+ :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
acts_as_searchable :columns => 'comments',
:include => {:repository => :project},
@@ -47,20 +47,30 @@
def revision=(r)
write_attribute :revision, (r.nil? ? nil : r.to_s)
end
-
- def comments=(comment)
- write_attribute(:comments, Changeset.normalize_comments(comment))
+
+ # Returns the identifier of this changeset; depending on repository backends
+ def identifier
+ if repository.class.respond_to? :changeset_identifier
+ repository.class.changeset_identifier self
+ else
+ revision.to_s
+ end
end
def committed_on=(date)
self.commit_date = date
super
end
+
+ # Returns the readable identifier
+ def format_identifier
+ if repository.class.respond_to? :format_changeset_identifier
+ repository.class.format_changeset_identifier self
+ else
+ identifier
+ end
+ end
- def committer=(arg)
- write_attribute(:committer, self.class.to_utf8(arg.to_s))
- end
-
def project
repository.project
end
@@ -70,59 +80,51 @@
end
def before_create
- self.user = repository.find_committer_user(committer)
+ self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
+ self.comments = self.class.normalize_comments(self.comments, repository.repo_log_encoding)
+ self.user = repository.find_committer_user(self.committer)
end
-
+
def after_create
scan_comment_for_issue_ids
end
+ TIMELOG_RE = /
+ (
+ ((\d+)(h|hours?))((\d+)(m|min)?)?
+ |
+ ((\d+)(h|hours?|m|min))
+ |
+ (\d+):(\d+)
+ |
+ (\d+([\.,]\d+)?)h?
+ )
+ /x
+
def scan_comment_for_issue_ids
return if comments.blank?
# keywords used to reference issues
ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
+ ref_keywords_any = ref_keywords.delete('*')
# keywords used to fix issues
fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
- return if kw_regexp.blank?
referenced_issues = []
- if ref_keywords.delete('*')
- # find any issue ID in the comments
- target_issue_ids = []
- comments.scan(%r{([\s\(\[,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
- referenced_issues += find_referenced_issues_by_id(target_issue_ids)
- end
-
- comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
- action = match[0]
- target_issue_ids = match[1].scan(/\d+/)
- target_issues = find_referenced_issues_by_id(target_issue_ids)
- if fix_keywords.include?(action.downcase) && fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
- # update status of issues
- logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
- target_issues.each do |issue|
- # the issue may have been updated by the closure of another one (eg. duplicate)
- issue.reload
- # don't change the status is the issue is closed
- next if issue.status.is_closed?
- csettext = "r#{self.revision}"
- if self.scmid && (! (csettext =~ /^r[0-9]+$/))
- csettext = "commit:\"#{self.scmid}\""
- end
- journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
- issue.status = fix_status
- unless Setting.commit_fix_done_ratio.blank?
- issue.done_ratio = Setting.commit_fix_done_ratio.to_i
- end
- Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
- { :changeset => self, :issue => issue })
- issue.save
+ comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
+ action, refs = match[2], match[3]
+ next unless action.present? || ref_keywords_any
+
+ refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
+ issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
+ if issue
+ referenced_issues << issue
+ fix_issue(issue) if fix_keywords.include?(action.to_s.downcase)
+ log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
end
end
- referenced_issues += target_issues
end
referenced_issues.uniq!
@@ -136,6 +138,14 @@
def long_comments
@long_comments || split_comments.last
end
+
+ def text_tag
+ if scmid?
+ "commit:#{scmid}"
+ else
+ "r#{revision}"
+ end
+ end
# Returns the previous changeset
def previous
@@ -147,11 +157,6 @@
@next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
end
- # Strips and reencodes a commit log before insertion into the database
- def self.normalize_comments(str)
- to_utf8(str.to_s.strip)
- end
-
# Creates a new Change from it's common parameters
def create_change(change)
Change.create(:changeset => self,
@@ -160,16 +165,67 @@
:from_path => change[:from_path],
:from_revision => change[:from_revision])
end
-
+
private
- # Finds issues that can be referenced by the commit message
- # i.e. issues that belong to the repository project, a subproject or a parent project
- def find_referenced_issues_by_id(ids)
- return [] if ids.compact.empty?
- Issue.find_all_by_id(ids, :include => :project).select {|issue|
- project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project)
- }
+ # Finds an issue that can be referenced by the commit message
+ # i.e. an issue that belong to the repository project, a subproject or a parent project
+ def find_referenced_issue_by_id(id)
+ return nil if id.blank?
+ issue = Issue.find_by_id(id.to_i, :include => :project)
+ if issue
+ unless project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project)
+ issue = nil
+ end
+ end
+ issue
+ end
+
+ def fix_issue(issue)
+ status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
+ if status.nil?
+ logger.warn("No status macthes commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
+ return issue
+ end
+
+ # the issue may have been updated by the closure of another one (eg. duplicate)
+ issue.reload
+ # don't change the status is the issue is closed
+ return if issue.status && issue.status.is_closed?
+
+ journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag))
+ issue.status = status
+ unless Setting.commit_fix_done_ratio.blank?
+ issue.done_ratio = Setting.commit_fix_done_ratio.to_i
+ end
+ Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
+ { :changeset => self, :issue => issue })
+ unless issue.save
+ logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
+ end
+ issue
+ end
+
+ def log_time(issue, hours)
+ time_entry = TimeEntry.new(
+ :user => user,
+ :hours => hours,
+ :issue => issue,
+ :spent_on => commit_date,
+ :comments => l(:text_time_logged_by_changeset, :value => text_tag, :locale => Setting.default_language)
+ )
+ time_entry.activity = log_time_activity unless log_time_activity.nil?
+
+ unless time_entry.save
+ logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
+ end
+ time_entry
+ end
+
+ def log_time_activity
+ if Setting.commit_logtime_activity_id.to_i > 0
+ TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
+ end
end
def split_comments
@@ -179,9 +235,17 @@
return @short_comments, @long_comments
end
- def self.to_utf8(str)
- return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
- encoding = Setting.commit_logs_encoding.to_s.strip
+ public
+
+ # Strips and reencodes a commit log before insertion into the database
+ def self.normalize_comments(str, encoding)
+ Changeset.to_utf8(str.to_s.strip, encoding)
+ end
+
+ private
+
+ def self.to_utf8(str, encoding)
+ return str if str.blank?
unless encoding.blank? || encoding == 'UTF-8'
begin
str = Iconv.conv('UTF-8', encoding, str)
@@ -189,12 +253,20 @@
# do nothing here
end
end
- # removes invalid UTF8 sequences
- begin
- Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
- rescue Iconv::InvalidEncoding
- # "UTF-8//IGNORE" is not supported on some OS
- str
+ if str.respond_to?(:force_encoding)
+ str.force_encoding('UTF-8')
+ if ! str.valid_encoding?
+ str = str.encode("US-ASCII", :invalid => :replace,
+ :undef => :replace, :replace => '?').encode("UTF-8")
+ end
+ else
+ # removes invalid UTF8 sequences
+ begin
+ str = Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
+ rescue Iconv::InvalidEncoding
+ # "UTF-8//IGNORE" is not supported on some OS
+ end
end
+ str
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/custom_field.rb.svn-base
--- a/app/models/.svn/text-base/custom_field.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/custom_field.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -23,7 +23,6 @@
validates_presence_of :name, :field_format
validates_uniqueness_of :name, :scope => :type
validates_length_of :name, :maximum => 30
- validates_format_of :name, :with => /^[\w\s\.\'\-]*$/i
validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
def initialize(attributes = nil)
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/document.rb.svn-base
--- a/app/models/.svn/text-base/document.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/document.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -29,6 +29,9 @@
validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60
+ named_scope :visible, lambda {|*args| { :include => :project,
+ :conditions => Project.allowed_to_condition(args.first || User.current, :view_documents) } }
+
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_documents, project)
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/group.rb.svn-base
--- a/app/models/.svn/text-base/group.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/group.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -31,6 +31,7 @@
def user_added(user)
members.each do |member|
+ next if member.project.nil?
user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
member.member_roles.each do |member_role|
user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/issue.rb.svn-base
--- a/app/models/.svn/text-base/issue.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/issue.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Issue < ActiveRecord::Base
+ include Redmine::SafeAttributes
+
belongs_to :project
belongs_to :tracker
belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
@@ -32,7 +34,7 @@
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
- acts_as_nested_set :scope => 'root_id'
+ acts_as_nested_set :scope => 'root_id', :dependent => :destroy
acts_as_attachable :after_remove => :attachment_removed
acts_as_customizable
acts_as_watchable
@@ -68,8 +70,7 @@
:conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
named_scope :for_gantt, lambda {
{
- :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
- :order => "#{Issue.table_name}.due_date ASC, #{Issue.table_name}.start_date ASC, #{Issue.table_name}.id ASC"
+ :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version]
}
}
@@ -88,7 +89,6 @@
before_create :default_assign
before_save :close_duplicates, :update_done_ratio_from_issue_status
after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
- after_destroy :destroy_children
after_destroy :update_parent_attributes
# Returns true if usr or current user is allowed to view the issue
@@ -215,30 +215,29 @@
write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
end
- SAFE_ATTRIBUTES = %w(
- tracker_id
- status_id
- parent_issue_id
- category_id
- assigned_to_id
- priority_id
- fixed_version_id
- subject
- description
- start_date
- due_date
- done_ratio
- estimated_hours
- custom_field_values
- lock_version
- ) unless const_defined?(:SAFE_ATTRIBUTES)
+ safe_attributes 'tracker_id',
+ 'status_id',
+ 'parent_issue_id',
+ 'category_id',
+ 'assigned_to_id',
+ 'priority_id',
+ 'fixed_version_id',
+ 'subject',
+ 'description',
+ 'start_date',
+ 'due_date',
+ 'done_ratio',
+ 'estimated_hours',
+ 'custom_field_values',
+ 'custom_fields',
+ 'lock_version',
+ :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
- SAFE_ATTRIBUTES_ON_TRANSITION = %w(
- status_id
- assigned_to_id
- fixed_version_id
- done_ratio
- ) unless const_defined?(:SAFE_ATTRIBUTES_ON_TRANSITION)
+ safe_attributes 'status_id',
+ 'assigned_to_id',
+ 'fixed_version_id',
+ 'done_ratio',
+ :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
# Safely sets attributes
# Should be called from controllers instead of #attributes=
@@ -249,13 +248,8 @@
return unless attrs.is_a?(Hash)
# User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
- if new_record? || user.allowed_to?(:edit_issues, project)
- attrs = attrs.reject {|k,v| !SAFE_ATTRIBUTES.include?(k)}
- elsif new_statuses_allowed_to(user).any?
- attrs = attrs.reject {|k,v| !SAFE_ATTRIBUTES_ON_TRANSITION.include?(k)}
- else
- return
- end
+ attrs = delete_unsafe_attributes(attrs, user)
+ return if attrs.empty?
# Tracker must be set before since new_statuses_allowed_to depends on it.
if t = attrs.delete('tracker_id')
@@ -276,7 +270,7 @@
if !user.allowed_to?(:manage_subtasks, project)
attrs.delete('parent_issue_id')
elsif !attrs['parent_issue_id'].blank?
- attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'])
+ attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
end
end
@@ -428,7 +422,12 @@
# Returns an array of status that user is able to apply
def new_statuses_allowed_to(user, include_default=false)
- statuses = status.find_new_statuses_allowed_to(user.roles_for_project(project), tracker)
+ statuses = status.find_new_statuses_allowed_to(
+ user.roles_for_project(project),
+ tracker,
+ author == user,
+ assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
+ )
statuses << status unless statuses.empty?
statuses << IssueStatus.default if include_default
statuses = statuses.uniq.sort
@@ -461,11 +460,14 @@
(relations_from + relations_to).sort
end
- def all_dependent_issues
+ def all_dependent_issues(except=nil)
+ except ||= self
dependencies = []
relations_from.each do |relation|
- dependencies << relation.issue_to
- dependencies += relation.issue_to.all_dependent_issues
+ if relation.issue_to && relation.issue_to != except
+ dependencies << relation.issue_to
+ dependencies += relation.issue_to.all_dependent_issues(except)
+ end
end
dependencies
end
@@ -760,14 +762,6 @@
end
end
- def destroy_children
- unless leaf?
- children.each do |child|
- child.destroy
- end
- end
- end
-
# Update issues so their versions are not pointing to a
# fixed_version that is not shared with the issue's project
def self.update_versions(conditions=nil)
@@ -835,7 +829,7 @@
def create_journal
if @current_journal
# attributes changes
- (Issue.column_names - %w(id description root_id lft rgt lock_version created_on updated_on)).each {|c|
+ (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
@current_journal.details << JournalDetail.new(:property => 'attr',
:prop_key => c,
:old_value => @issue_before_change.send(c),
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/issue_relation.rb.svn-base
--- a/app/models/.svn/text-base/issue_relation.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/issue_relation.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -84,14 +84,15 @@
def set_issue_to_dates
soonest_start = self.successor_soonest_start
- if soonest_start
+ if soonest_start && issue_to
issue_to.reschedule_after(soonest_start)
end
end
def successor_soonest_start
- return nil unless (TYPE_PRECEDES == self.relation_type) && (issue_from.start_date || issue_from.due_date)
- (issue_from.due_date || issue_from.start_date) + 1 + delay
+ if (TYPE_PRECEDES == self.relation_type) && delay && issue_from && (issue_from.start_date || issue_from.due_date)
+ (issue_from.due_date || issue_from.start_date) + 1 + delay
+ end
end
def <=>(relation)
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/issue_status.rb.svn-base
--- a/app/models/.svn/text-base/issue_status.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/issue_status.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -25,7 +25,6 @@
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :maximum => 30
- validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
def after_save
@@ -51,10 +50,16 @@
# Returns an array of all statuses the given role can switch to
# Uses association cache when called more than one time
- def new_statuses_allowed_to(roles, tracker)
+ def new_statuses_allowed_to(roles, tracker, author=false, assignee=false)
if roles && tracker
role_ids = roles.collect(&:id)
- new_statuses = workflows.select {|w| role_ids.include?(w.role_id) && w.tracker_id == tracker.id}.collect{|w| w.new_status}.compact.sort
+ transitions = workflows.select do |w|
+ role_ids.include?(w.role_id) &&
+ w.tracker_id == tracker.id &&
+ (author || !w.author) &&
+ (assignee || !w.assignee)
+ end
+ transitions.collect{|w| w.new_status}.compact.sort
else
[]
end
@@ -62,24 +67,19 @@
# Same thing as above but uses a database query
# More efficient than the previous method if called just once
- def find_new_statuses_allowed_to(roles, tracker)
+ def find_new_statuses_allowed_to(roles, tracker, author=false, assignee=false)
if roles && tracker
+ conditions = {:role_id => roles.collect(&:id), :tracker_id => tracker.id}
+ conditions[:author] = false unless author
+ conditions[:assignee] = false unless assignee
+
workflows.find(:all,
:include => :new_status,
- :conditions => { :role_id => roles.collect(&:id),
- :tracker_id => tracker.id}).collect{ |w| w.new_status }.compact.sort
+ :conditions => conditions).collect{|w| w.new_status}.compact.sort
else
[]
end
end
-
- def new_status_allowed_to?(status, roles, tracker)
- if status && roles && tracker
- !workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => roles.collect(&:id), :tracker_id => tracker.id}).nil?
- else
- false
- end
- end
def <=>(status)
position <=> status.position
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/journal_detail.rb.svn-base
--- a/app/models/.svn/text-base/journal_detail.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/journal_detail.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -17,9 +17,4 @@
class JournalDetail < ActiveRecord::Base
belongs_to :journal
-
- def before_save
- self.value = value[0..254] if value && value.is_a?(String)
- self.old_value = old_value[0..254] if old_value && old_value.is_a?(String)
- end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/mail_handler.rb.svn-base
--- a/app/models/.svn/text-base/mail_handler.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/mail_handler.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -100,7 +100,7 @@
elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
receive_message_reply(m[1].to_i)
else
- receive_issue
+ dispatch_to_default
end
rescue ActiveRecord::RecordInvalid => e
# TODO: send a email to the user
@@ -113,6 +113,10 @@
logger.error "MailHandler: unauthorized attempt from #{user}" if logger
false
end
+
+ def dispatch_to_default
+ receive_issue
+ end
# Creates a new issue
def receive_issue
@@ -148,6 +152,9 @@
raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
end
+ # ignore CLI-supplied defaults for new issues
+ @@handler_options[:issue].clear
+
journal = issue.init_journal(user, cleaned_up_text_body)
issue.safe_attributes = issue_attributes_from_keywords(issue)
issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
@@ -232,8 +239,8 @@
def extract_keyword!(text, attr, format=nil)
keys = [attr.to_s.humanize]
if attr.is_a?(Symbol)
- keys << l("field_#{attr}", :default => '', :locale => user.language) if user
- keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
+ keys << l("field_#{attr}", :default => '', :locale => user.language) if user && user.language.present?
+ keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) if Setting.default_language.present?
end
keys.reject! {|k| k.blank?}
keys.collect! {|k| Regexp.escape(k)}
@@ -256,8 +263,8 @@
assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_user_from_keyword(k)
assigned_to = nil if assigned_to && !issue.assignable_users.include?(assigned_to)
- {
- 'tracker_id' => ((k = get_keyword(:tracker)) && issue.project.trackers.find_by_name(k).try(:id)) || issue.project.trackers.find(:first).try(:id),
+ attrs = {
+ 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.find_by_name(k).try(:id),
'status_id' => (k = get_keyword(:status)) && IssueStatus.find_by_name(k).try(:id),
'priority_id' => (k = get_keyword(:priority)) && IssuePriority.find_by_name(k).try(:id),
'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.find_by_name(k).try(:id),
@@ -268,6 +275,12 @@
'estimated_hours' => get_keyword(:estimated_hours, :override => true),
'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
}.delete_if {|k, v| v.blank? }
+
+ if issue.new_record? && attrs['tracker_id'].nil?
+ attrs['tracker_id'] = issue.project.trackers.find(:first).try(:id)
+ end
+
+ attrs
end
# Returns a Hash of issue custom field values extracted from keywords in the email body
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/mailer.rb.svn-base
--- a/app/models/.svn/text-base/mailer.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/mailer.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -296,7 +296,7 @@
if raise_errors
raise e
elsif mylogger
- mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/email.yml."
+ mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml."
end
ensure
self.class.raise_delivery_errors = raise_errors
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/message.rb.svn-base
--- a/app/models/.svn/text-base/message.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/message.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -42,6 +42,9 @@
after_create :add_author_as_watcher
+ named_scope :visible, lambda {|*args| { :include => {:board => :project},
+ :conditions => Project.allowed_to_condition(args.first || User.current, :view_messages) } }
+
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_messages, project)
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/news.rb.svn-base
--- a/app/models/.svn/text-base/news.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/news.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -29,6 +29,11 @@
acts_as_activity_provider :find_options => {:include => [:project, :author]},
:author_key => :author_id
+ named_scope :visible, lambda {|*args| {
+ :include => :project,
+ :conditions => Project.allowed_to_condition(args.first || User.current, :view_news)
+ }}
+
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_news, project)
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/project.rb.svn-base
--- a/app/models/.svn/text-base/project.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/project.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Project < ActiveRecord::Base
+ include Redmine::SafeAttributes
+
# Project statuses
STATUS_ACTIVE = 1
STATUS_ARCHIVED = 9
@@ -64,7 +66,7 @@
:url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
:author => nil
- attr_protected :status, :enabled_module_names
+ attr_protected :status
validates_presence_of :name, :identifier
validates_uniqueness_of :identifier
@@ -84,6 +86,24 @@
named_scope :all_public, { :conditions => { :is_public => true } }
named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
+ def initialize(attributes = nil)
+ super
+
+ initialized = (attributes || {}).stringify_keys
+ if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
+ self.identifier = Project.next_identifier
+ end
+ if !initialized.key?('is_public')
+ self.is_public = Setting.default_projects_public?
+ end
+ if !initialized.key?('enabled_module_names')
+ self.enabled_module_names = Setting.default_projects_modules
+ end
+ if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
+ self.trackers = Tracker.all
+ end
+ end
+
def identifier=(identifier)
super unless identifier_frozen?
end
@@ -431,24 +451,20 @@
# The earliest start date of a project, based on it's issues and versions
def start_date
- if module_enabled?(:issue_tracking)
- [
- issues.minimum('start_date'),
- shared_versions.collect(&:effective_date),
- shared_versions.collect {|v| v.fixed_issues.minimum('start_date')}
- ].flatten.compact.min
- end
+ [
+ issues.minimum('start_date'),
+ shared_versions.collect(&:effective_date),
+ shared_versions.collect(&:start_date)
+ ].flatten.compact.min
end
# The latest due date of an issue or version
def due_date
- if module_enabled?(:issue_tracking)
- [
- issues.maximum('due_date'),
- shared_versions.collect(&:effective_date),
- shared_versions.collect {|v| v.fixed_issues.maximum('due_date')}
- ].flatten.compact.max
- end
+ [
+ issues.maximum('due_date'),
+ shared_versions.collect(&:effective_date),
+ shared_versions.collect {|v| v.fixed_issues.maximum('due_date')}
+ ].flatten.compact.max
end
def overdue?
@@ -492,7 +508,7 @@
def enabled_module_names=(module_names)
if module_names && module_names.is_a?(Array)
- module_names = module_names.collect(&:to_s)
+ module_names = module_names.collect(&:to_s).reject(&:blank?)
# remove disabled modules
enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
# add new modules
@@ -501,7 +517,25 @@
enabled_modules.clear
end
end
+
+ # Returns an array of the enabled modules names
+ def enabled_module_names
+ enabled_modules.collect(&:name)
+ end
+
+ safe_attributes 'name',
+ 'description',
+ 'homepage',
+ 'is_public',
+ 'identifier',
+ 'custom_field_values',
+ 'custom_fields',
+ 'tracker_ids',
+ 'issue_custom_field_ids'
+ safe_attributes 'enabled_module_names',
+ :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
+
# Returns an array of projects that are in this project's hierarchy
#
# Example: parents, children, siblings
@@ -669,12 +703,20 @@
end
self.issues << new_issue
- issues_map[issue.id] = new_issue
+ if new_issue.new_record?
+ logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info
+ else
+ issues_map[issue.id] = new_issue unless new_issue.new_record?
+ end
end
# Relations after in case issues related each other
project.issues.each do |issue|
new_issue = issues_map[issue.id]
+ unless new_issue
+ # Issue was not copied
+ next
+ end
# Relations
issue.relations_from.each do |source_relation|
@@ -701,7 +743,12 @@
# Copies members from +project+
def copy_members(project)
- project.memberships.each do |member|
+ # Copy users first, then groups to handle members with inherited and given roles
+ members_to_copy = []
+ members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
+ members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
+
+ members_to_copy.each do |member|
new_member = Member.new
new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
# only copy non inherited roles
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/query.rb.svn-base
--- a/app/models/.svn/text-base/query.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/query.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -187,10 +187,18 @@
if project
user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
else
- project_ids = Project.all(:conditions => Project.visible_by(User.current)).collect(&:id)
- if project_ids.any?
- # members of the user's projects
- user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids]).sort.collect{|s| [s.name, s.id.to_s] }
+ all_projects = Project.visible.all
+ if all_projects.any?
+ # members of visible projects
+ user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort.collect{|s| [s.name, s.id.to_s] }
+
+ # project filter
+ project_values = []
+ Project.project_tree(all_projects) do |p, level|
+ prefix = (level > 0 ? ('--' * level + ' ') : '')
+ project_values << ["#{prefix}#{p.name}", p.id.to_s]
+ end
+ @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
end
end
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
@@ -225,12 +233,6 @@
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
end
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
- # project filter
- project_values = Project.all(:conditions => Project.visible_by(User.current), :order => 'lft').map do |p|
- pre = (p.level > 0 ? ('--' * p.level + ' ') : '')
- ["#{pre}#{p.name}",p.id.to_s]
- end
- @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values}
end
@available_filters
end
@@ -376,15 +378,15 @@
# Returns true if the query is a grouped query
def grouped?
- !group_by.blank?
+ !group_by_column.nil?
end
def group_by_column
- groupable_columns.detect {|c| c.name.to_s == group_by}
+ groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
end
def group_by_statement
- group_by_column.groupable
+ group_by_column.try(:groupable)
end
def project_statement
@@ -564,9 +566,19 @@
sql = ''
case operator
when "="
- sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
+ if value.any?
+ sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
+ else
+ # IN an empty set
+ sql = "1=0"
+ end
when "!"
- sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
+ if value.any?
+ sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
+ else
+ # NOT IN an empty set
+ sql = "1=1"
+ end
when "!*"
sql = "#{db_table}.#{db_field} IS NULL"
sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/repository.rb.svn-base
--- a/app/models/.svn/text-base/repository.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/repository.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Repository < ActiveRecord::Base
+ include Redmine::Ciphering
+
belongs_to :project
has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
has_many :changes, :through => :changesets
@@ -24,29 +26,43 @@
# has_many :changesets, :dependent => :destroy is too slow for big repositories
before_destroy :clear_changesets
+ validates_length_of :password, :maximum => 255, :allow_nil => true
# Checks if the SCM is enabled when creating a repository
validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
-
+
# Removes leading and trailing whitespace
def url=(arg)
write_attribute(:url, arg ? arg.to_s.strip : nil)
end
-
+
# Removes leading and trailing whitespace
def root_url=(arg)
write_attribute(:root_url, arg ? arg.to_s.strip : nil)
end
+
+ def password
+ read_ciphered_attribute(:password)
+ end
+
+ def password=(arg)
+ write_ciphered_attribute(:password, arg)
+ end
+
+ def scm_adapter
+ self.class.scm_adapter_class
+ end
def scm
- @scm ||= self.scm_adapter.new url, root_url, login, password
+ @scm ||= self.scm_adapter.new(url, root_url,
+ login, password, path_encoding)
update_attribute(:root_url, @scm.root_url) if root_url.blank?
@scm
end
-
+
def scm_name
self.class.scm_name
end
-
+
def supports_cat?
scm.supports_cat?
end
@@ -86,17 +102,25 @@
def diff(path, rev, rev_to)
scm.diff(path, rev, rev_to)
end
-
+
+ def diff_format_revisions(cs, cs_to, sep=':')
+ text = ""
+ text << cs_to.format_identifier + sep if cs_to
+ text << cs.format_identifier if cs
+ text
+ end
+
# Returns a path relative to the url of the repository
def relative_path(path)
path
end
-
+
# Finds and returns a revision with a number or the beginning of a hash
def find_changeset_by_name(name)
+ return nil if name.blank?
changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
end
-
+
def latest_changeset
@latest_changeset ||= changesets.find(:first)
end
@@ -165,18 +189,27 @@
user
end
end
-
+
+ def repo_log_encoding
+ encoding = log_encoding.to_s.strip
+ encoding.blank? ? 'UTF-8' : encoding
+ end
+
# Fetches new changesets for all repositories of active projects
# Can be called periodically by an external script
# eg. ruby script/runner "Repository.fetch_changesets"
def self.fetch_changesets
Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
if project.repository
- project.repository.fetch_changesets
+ begin
+ project.repository.fetch_changesets
+ rescue Redmine::Scm::Adapters::CommandFailed => e
+ logger.error "scm: error during fetching changesets: #{e.message}"
+ end
end
end
end
-
+
# scan changeset comments to find related and fixed issues for all repositories
def self.scan_changesets_for_issue_ids
find(:all).each(&:scan_changesets_for_issue_ids)
@@ -189,16 +222,50 @@
def self.available_scm
subclasses.collect {|klass| [klass.scm_name, klass.name]}
end
-
+
def self.factory(klass_name, *args)
klass = "Repository::#{klass_name}".constantize
klass.new(*args)
rescue
nil
end
-
+
+ def self.scm_adapter_class
+ nil
+ end
+
+ def self.scm_command
+ ret = ""
+ begin
+ ret = self.scm_adapter_class.client_command if self.scm_adapter_class
+ rescue Redmine::Scm::Adapters::CommandFailed => e
+ logger.error "scm: error during get command: #{e.message}"
+ end
+ ret
+ end
+
+ def self.scm_version_string
+ ret = ""
+ begin
+ ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
+ rescue Redmine::Scm::Adapters::CommandFailed => e
+ logger.error "scm: error during get version string: #{e.message}"
+ end
+ ret
+ end
+
+ def self.scm_available
+ ret = false
+ begin
+ ret = self.scm_adapter_class.client_available if self.scm_adapter_class
+ rescue Redmine::Scm::Adapters::CommandFailed => e
+ logger.error "scm: error during get scm available: #{e.message}"
+ end
+ ret
+ end
+
private
-
+
def before_save
# Strips url and root_url
url.strip!
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/role.rb.svn-base
--- a/app/models/.svn/text-base/role.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/role.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -43,7 +43,6 @@
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :maximum => 30
- validates_format_of :name, :with => /^[\w\s\'\-]*$/i
def permissions
read_attribute(:permissions) || []
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/setting.rb.svn-base
--- a/app/models/.svn/text-base/setting.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/setting.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -65,6 +65,7 @@
UTF-16LE
EUC-JP
Shift_JIS
+ CP932
GB18030
GBK
ISCII91
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/time_entry.rb.svn-base
--- a/app/models/.svn/text-base/time_entry.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/time_entry.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -66,6 +66,9 @@
# these attributes make time aggregations easier
def spent_on=(date)
super
+ if spent_on.is_a?(Time)
+ self.spent_on = spent_on.to_date
+ end
self.tyear = spent_on ? spent_on.year : nil
self.tmonth = spent_on ? spent_on.month : nil
self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/tracker.rb.svn-base
--- a/app/models/.svn/text-base/tracker.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/tracker.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -31,7 +31,6 @@
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :maximum => 30
- validates_format_of :name, :with => /^[\w\s\'\-]*$/i
def to_s; name end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/user.rb.svn-base
--- a/app/models/.svn/text-base/user.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/user.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -18,7 +18,8 @@
require "digest/sha1"
class User < Principal
-
+ include Redmine::SafeAttributes
+
# Account statuses
STATUS_ANONYMOUS = 0
STATUS_ACTIVE = 1
@@ -34,21 +35,21 @@
}
MAIL_NOTIFICATION_OPTIONS = [
- [:all, :label_user_mail_option_all],
- [:selected, :label_user_mail_option_selected],
- [:none, :label_user_mail_option_none],
- [:only_my_events, :label_user_mail_option_only_my_events],
- [:only_assigned, :label_user_mail_option_only_assigned],
- [:only_owner, :label_user_mail_option_only_owner]
- ]
+ ['all', :label_user_mail_option_all],
+ ['selected', :label_user_mail_option_selected],
+ ['only_my_events', :label_user_mail_option_only_my_events],
+ ['only_assigned', :label_user_mail_option_only_assigned],
+ ['only_owner', :label_user_mail_option_only_owner],
+ ['none', :label_user_mail_option_none]
+ ]
has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
:after_remove => Proc.new {|user, group| group.user_removed(user)}
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
has_many :changesets, :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
- has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
- has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
+ has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
+ has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
belongs_to :auth_source
# Active non-anonymous users scope
@@ -59,7 +60,7 @@
attr_accessor :password, :password_confirmation
attr_accessor :last_before_login_on
# Prevents unauthorized assignments
- attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
+ attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
@@ -67,12 +68,14 @@
# Login must contain lettres, numbers, underscores only
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
validates_length_of :login, :maximum => 30
- validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
validates_length_of :firstname, :lastname, :maximum => 30
validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
validates_length_of :mail, :maximum => 60, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true
+ validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
+ before_destroy :remove_references_before_destroy
+
def before_create
self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
true
@@ -80,7 +83,9 @@
def before_save
# update hashed_password if password was set
- self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
+ if self.password && self.auth_source_id.blank?
+ salt_password(password)
+ end
end
def reload(*args)
@@ -118,7 +123,7 @@
return nil unless user.auth_source.authenticate(login, password)
else
# authentication with local password
- return nil unless User.hash_password(password) == user.hashed_password
+ return nil unless user.check_password?(password)
end
else
# user is not yet registered, try to authenticate with available sources
@@ -197,13 +202,21 @@
update_attribute(:status, STATUS_LOCKED)
end
+ # Returns true if +clear_password+ is the correct user's password, otherwise false
def check_password?(clear_password)
if auth_source_id.present?
auth_source.authenticate(self.login, clear_password)
else
- User.hash_password(clear_password) == self.hashed_password
+ User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
end
end
+
+ # Generates a random salt and computes hashed_password for +clear_password+
+ # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
+ def salt_password(clear_password)
+ self.salt = User.generate_salt
+ self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
+ end
# Does the backend storage allow this user to change their password?
def change_password_allowed?
@@ -259,12 +272,16 @@
notified_projects_ids
end
+ def valid_notification_options
+ self.class.valid_notification_options(self)
+ end
+
# Only users that belong to more than 1 project can select projects for which they are notified
- def valid_notification_options
+ def self.valid_notification_options(user=nil)
# Note that @user.membership.size would fail since AR ignores
# :include association option when doing a count
- if memberships.length < 1
- MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == :selected}
+ if user.nil? || user.memberships.length < 1
+ MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
else
MAIL_NOTIFICATION_OPTIONS
end
@@ -390,32 +407,54 @@
def allowed_to_globally?(action, options)
allowed_to?(action, nil, options.reverse_merge(:global => true))
end
+
+ safe_attributes 'login',
+ 'firstname',
+ 'lastname',
+ 'mail',
+ 'mail_notification',
+ 'language',
+ 'custom_field_values',
+ 'custom_fields',
+ 'identity_url'
+
+ safe_attributes 'status',
+ 'auth_source_id',
+ :if => lambda {|user, current_user| current_user.admin?}
+
+ safe_attributes 'group_ids',
+ :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
# Utility method to help check if a user should be notified about an
# event.
#
# TODO: only supports Issue events currently
def notify_about?(object)
- case mail_notification.to_sym
- when :all
+ case mail_notification
+ when 'all'
true
- when :selected
- # Handled by the Project
- when :none
- false
- when :only_my_events
+ when 'selected'
+ # user receives notifications for created/assigned issues on unselected projects
if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
true
else
false
end
- when :only_assigned
+ when 'none'
+ false
+ when 'only_my_events'
+ if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
+ true
+ else
+ false
+ end
+ when 'only_assigned'
if object.is_a?(Issue) && object.assigned_to == self
true
else
false
end
- when :only_owner
+ when 'only_owner'
if object.is_a?(Issue) && object.author == self
true
else
@@ -444,6 +483,20 @@
end
anonymous_user
end
+
+ # Salts all existing unsalted passwords
+ # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
+ # This method is used in the SaltPasswords migration and is to be kept as is
+ def self.salt_unsalted_passwords!
+ transaction do
+ User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
+ next if user.hashed_password.blank?
+ salt = User.generate_salt
+ hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
+ User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
+ end
+ end
+ end
protected
@@ -455,11 +508,42 @@
end
private
+
+ # Removes references that are not handled by associations
+ # Things that are not deleted are reassociated with the anonymous user
+ def remove_references_before_destroy
+ return if self.id.nil?
+
+ substitute = User.anonymous
+ Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
+ Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
+ JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
+ JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
+ Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ # Remove private queries and keep public ones
+ Query.delete_all ['user_id = ? AND is_public = ?', id, false]
+ Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
+ TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
+ Token.delete_all ['user_id = ?', id]
+ Watcher.delete_all ['user_id = ?', id]
+ WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ end
# Return password digest
def self.hash_password(clear_password)
Digest::SHA1.hexdigest(clear_password || "")
end
+
+ # Returns a 128bits random salt as a hex string (32 chars long)
+ def self.generate_salt
+ ActiveSupport::SecureRandom.hex(16)
+ end
+
end
class AnonymousUser < User
@@ -480,4 +564,9 @@
def mail; nil end
def time_zone; nil end
def rss_key; nil end
+
+ # Anonymous user can not be destroyed
+ def destroy
+ false
+ end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/user_preference.rb.svn-base
--- a/app/models/.svn/text-base/user_preference.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/user_preference.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -51,4 +51,7 @@
def comments_sorting; self[:comments_sorting] end
def comments_sorting=(order); self[:comments_sorting]=order end
+
+ def warn_on_leaving_unsaved; self[:warn_on_leaving_unsaved] || '1'; end
+ def warn_on_leaving_unsaved=(value); self[:warn_on_leaving_unsaved]=value; end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/version.rb.svn-base
--- a/app/models/.svn/text-base/version.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/version.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -43,7 +43,7 @@
end
def start_date
- effective_date
+ @start_date ||= fixed_issues.minimum('start_date')
end
def due_date
@@ -77,8 +77,7 @@
def behind_schedule?
if completed_pourcent == 100
return false
- elsif due_date && fixed_issues.present? && fixed_issues.minimum('start_date') # TODO: should use #start_date but that method is wrong...
- start_date = fixed_issues.minimum('start_date')
+ elsif due_date && start_date
done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
return done_date <= Date.today
else
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/wiki.rb.svn-base
--- a/app/models/.svn/text-base/wiki.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/wiki.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -46,10 +46,10 @@
def find_page(title, options = {})
title = start_page if title.blank?
title = Wiki.titleize(title)
- page = pages.find_by_title(title)
+ page = pages.first(:conditions => ["LOWER(title) LIKE LOWER(?)", title])
if !page && !(options[:with_redirect] == false)
# search for a redirect
- redirect = redirects.find_by_title(title)
+ redirect = redirects.first(:conditions => ["LOWER(title) LIKE LOWER(?)", title])
page = find_page(redirect.redirects_to, :with_redirect => false) if redirect
end
page
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/.svn/text-base/wiki_page.rb.svn-base
--- a/app/models/.svn/text-base/wiki_page.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/.svn/text-base/wiki_page.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -1,5 +1,5 @@
# Redmine - project management software
-# Copyright (C) 2006-2009 Jean-Philippe Lang
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -149,17 +149,13 @@
end
end
-class WikiDiff
- attr_reader :diff, :words, :content_to, :content_from
+class WikiDiff < Redmine::Helpers::Diff
+ attr_reader :content_to, :content_from
def initialize(content_to, content_from)
@content_to = content_to
@content_from = content_from
- @words = content_to.text.split(/(\s+)/)
- @words = @words.select {|word| word != ' '}
- words_from = content_from.text.split(/(\s+)/)
- words_from = words_from.select {|word| word != ' '}
- @diff = words_from.diff @words
+ super(content_to.text, content_from.text)
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/attachment.rb
--- a/app/models/attachment.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/attachment.rb Thu Mar 03 12:11:53 2011 +0000
@@ -43,7 +43,7 @@
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
cattr_accessor :storage_path
- @@storage_path = "#{RAILS_ROOT}/files"
+ @@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{RAILS_ROOT}/files"
def validate
if self.filesize > Setting.attachment_max_size.to_i.kilobytes
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/auth_source.rb
--- a/app/models/auth_source.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/auth_source.rb Thu Mar 03 12:11:53 2011 +0000
@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AuthSource < ActiveRecord::Base
+ include Redmine::Ciphering
+
has_many :users
validates_presence_of :name
@@ -31,6 +33,14 @@
def auth_method_name
"Abstract"
end
+
+ def account_password
+ read_ciphered_attribute(:account_password)
+ end
+
+ def account_password=(arg)
+ write_ciphered_attribute(:account_password, arg)
+ end
def allow_password_changes?
self.class.allow_password_changes?
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/auth_source_ldap.rb
--- a/app/models/auth_source_ldap.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/auth_source_ldap.rb Thu Mar 03 12:11:53 2011 +0000
@@ -20,8 +20,8 @@
class AuthSourceLdap < AuthSource
validates_presence_of :host, :port, :attr_login
- validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true
- validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true
+ validates_length_of :name, :host, :maximum => 60, :allow_nil => true
+ validates_length_of :account, :account_password, :base_dn, :maximum => 255, :allow_nil => true
validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
validates_numericality_of :port, :only_integer => true
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/board.rb
--- a/app/models/board.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/board.rb Thu Mar 03 12:11:53 2011 +0000
@@ -18,7 +18,7 @@
class Board < ActiveRecord::Base
belongs_to :project
has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
- has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC"
+ has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC"
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
acts_as_list :scope => :project_id
acts_as_watchable
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/changeset.rb
--- a/app/models/changeset.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/changeset.rb Thu Mar 03 12:11:53 2011 +0000
@@ -23,7 +23,7 @@
has_many :changes, :dependent => :delete_all
has_and_belongs_to_many :issues
- acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{RepositoriesHelper.format_revision(o)}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
+ acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
:description => :long_comments,
:datetime => :committed_on,
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
@@ -48,35 +48,29 @@
write_attribute :revision, (r.nil? ? nil : r.to_s)
end
- # Returns the identifier of this changeset.
- # e.g. revision number for centralized system; hash id for DVCS
+ # Returns the identifier of this changeset; depending on repository backends
def identifier
- scmid || revision
- end
-
- # Returns the wiki identifier, "rN" or "commit:ABCDEF"
- def wiki_identifier
- if scmid # hash-like
- "commit:#{scmid}"
- else # numeric
- "r#{revision}"
+ if repository.class.respond_to? :changeset_identifier
+ repository.class.changeset_identifier self
+ else
+ revision.to_s
end
end
- private :wiki_identifier
-
- def comments=(comment)
- write_attribute(:comments, Changeset.normalize_comments(comment))
- end
def committed_on=(date)
self.commit_date = date
super
end
+
+ # Returns the readable identifier
+ def format_identifier
+ if repository.class.respond_to? :format_changeset_identifier
+ repository.class.format_changeset_identifier self
+ else
+ identifier
+ end
+ end
- def committer=(arg)
- write_attribute(:committer, self.class.to_utf8(arg.to_s))
- end
-
def project
repository.project
end
@@ -86,55 +80,51 @@
end
def before_create
- self.user = repository.find_committer_user(committer)
+ self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
+ self.comments = self.class.normalize_comments(self.comments, repository.repo_log_encoding)
+ self.user = repository.find_committer_user(self.committer)
end
-
+
def after_create
scan_comment_for_issue_ids
end
+ TIMELOG_RE = /
+ (
+ ((\d+)(h|hours?))((\d+)(m|min)?)?
+ |
+ ((\d+)(h|hours?|m|min))
+ |
+ (\d+):(\d+)
+ |
+ (\d+([\.,]\d+)?)h?
+ )
+ /x
+
def scan_comment_for_issue_ids
return if comments.blank?
# keywords used to reference issues
ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
+ ref_keywords_any = ref_keywords.delete('*')
# keywords used to fix issues
fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
- return if kw_regexp.blank?
referenced_issues = []
- if ref_keywords.delete('*')
- # find any issue ID in the comments
- target_issue_ids = []
- comments.scan(%r{([\s\(\[,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
- referenced_issues += find_referenced_issues_by_id(target_issue_ids)
- end
-
- comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
- action = match[0]
- target_issue_ids = match[1].scan(/\d+/)
- target_issues = find_referenced_issues_by_id(target_issue_ids)
- if fix_keywords.include?(action.downcase) && fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
- # update status of issues
- logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
- target_issues.each do |issue|
- # the issue may have been updated by the closure of another one (eg. duplicate)
- issue.reload
- # don't change the status is the issue is closed
- next if issue.status.is_closed?
- journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, wiki_identifier))
- issue.status = fix_status
- unless Setting.commit_fix_done_ratio.blank?
- issue.done_ratio = Setting.commit_fix_done_ratio.to_i
- end
- Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
- { :changeset => self, :issue => issue })
- issue.save
+ comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
+ action, refs = match[2], match[3]
+ next unless action.present? || ref_keywords_any
+
+ refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
+ issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
+ if issue
+ referenced_issues << issue
+ fix_issue(issue) if fix_keywords.include?(action.to_s.downcase)
+ log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
end
end
- referenced_issues += target_issues
end
referenced_issues.uniq!
@@ -148,6 +138,14 @@
def long_comments
@long_comments || split_comments.last
end
+
+ def text_tag
+ if scmid?
+ "commit:#{scmid}"
+ else
+ "r#{revision}"
+ end
+ end
# Returns the previous changeset
def previous
@@ -159,11 +157,6 @@
@next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
end
- # Strips and reencodes a commit log before insertion into the database
- def self.normalize_comments(str)
- to_utf8(str.to_s.strip)
- end
-
# Creates a new Change from it's common parameters
def create_change(change)
Change.create(:changeset => self,
@@ -172,16 +165,67 @@
:from_path => change[:from_path],
:from_revision => change[:from_revision])
end
-
+
private
- # Finds issues that can be referenced by the commit message
- # i.e. issues that belong to the repository project, a subproject or a parent project
- def find_referenced_issues_by_id(ids)
- return [] if ids.compact.empty?
- Issue.find_all_by_id(ids, :include => :project).select {|issue|
- project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project)
- }
+ # Finds an issue that can be referenced by the commit message
+ # i.e. an issue that belong to the repository project, a subproject or a parent project
+ def find_referenced_issue_by_id(id)
+ return nil if id.blank?
+ issue = Issue.find_by_id(id.to_i, :include => :project)
+ if issue
+ unless project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project)
+ issue = nil
+ end
+ end
+ issue
+ end
+
+ def fix_issue(issue)
+ status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
+ if status.nil?
+ logger.warn("No status macthes commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
+ return issue
+ end
+
+ # the issue may have been updated by the closure of another one (eg. duplicate)
+ issue.reload
+ # don't change the status is the issue is closed
+ return if issue.status && issue.status.is_closed?
+
+ journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag))
+ issue.status = status
+ unless Setting.commit_fix_done_ratio.blank?
+ issue.done_ratio = Setting.commit_fix_done_ratio.to_i
+ end
+ Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
+ { :changeset => self, :issue => issue })
+ unless issue.save
+ logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
+ end
+ issue
+ end
+
+ def log_time(issue, hours)
+ time_entry = TimeEntry.new(
+ :user => user,
+ :hours => hours,
+ :issue => issue,
+ :spent_on => commit_date,
+ :comments => l(:text_time_logged_by_changeset, :value => text_tag, :locale => Setting.default_language)
+ )
+ time_entry.activity = log_time_activity unless log_time_activity.nil?
+
+ unless time_entry.save
+ logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
+ end
+ time_entry
+ end
+
+ def log_time_activity
+ if Setting.commit_logtime_activity_id.to_i > 0
+ TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
+ end
end
def split_comments
@@ -191,9 +235,17 @@
return @short_comments, @long_comments
end
- def self.to_utf8(str)
- return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
- encoding = Setting.commit_logs_encoding.to_s.strip
+ public
+
+ # Strips and reencodes a commit log before insertion into the database
+ def self.normalize_comments(str, encoding)
+ Changeset.to_utf8(str.to_s.strip, encoding)
+ end
+
+ private
+
+ def self.to_utf8(str, encoding)
+ return str if str.blank?
unless encoding.blank? || encoding == 'UTF-8'
begin
str = Iconv.conv('UTF-8', encoding, str)
@@ -201,12 +253,20 @@
# do nothing here
end
end
- # removes invalid UTF8 sequences
- begin
- Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
- rescue Iconv::InvalidEncoding
- # "UTF-8//IGNORE" is not supported on some OS
- str
+ if str.respond_to?(:force_encoding)
+ str.force_encoding('UTF-8')
+ if ! str.valid_encoding?
+ str = str.encode("US-ASCII", :invalid => :replace,
+ :undef => :replace, :replace => '?').encode("UTF-8")
+ end
+ else
+ # removes invalid UTF8 sequences
+ begin
+ str = Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
+ rescue Iconv::InvalidEncoding
+ # "UTF-8//IGNORE" is not supported on some OS
+ end
end
+ str
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/custom_field.rb
--- a/app/models/custom_field.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/custom_field.rb Thu Mar 03 12:11:53 2011 +0000
@@ -23,7 +23,6 @@
validates_presence_of :name, :field_format
validates_uniqueness_of :name, :scope => :type
validates_length_of :name, :maximum => 30
- validates_format_of :name, :with => /^[\w\s\.\'\-]*$/i
validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
def initialize(attributes = nil)
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/document.rb
--- a/app/models/document.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/document.rb Thu Mar 03 12:11:53 2011 +0000
@@ -29,6 +29,9 @@
validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60
+ named_scope :visible, lambda {|*args| { :include => :project,
+ :conditions => Project.allowed_to_condition(args.first || User.current, :view_documents) } }
+
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_documents, project)
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/group.rb
--- a/app/models/group.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/group.rb Thu Mar 03 12:11:53 2011 +0000
@@ -31,6 +31,7 @@
def user_added(user)
members.each do |member|
+ next if member.project.nil?
user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
member.member_roles.each do |member_role|
user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/issue.rb
--- a/app/models/issue.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/issue.rb Thu Mar 03 12:11:53 2011 +0000
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Issue < ActiveRecord::Base
+ include Redmine::SafeAttributes
+
belongs_to :project
belongs_to :tracker
belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
@@ -27,12 +29,12 @@
has_many :journals, :as => :journalized, :dependent => :destroy
has_many :time_entries, :dependent => :delete_all
- has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.id ASC"
+ has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
- acts_as_nested_set :scope => 'root_id'
+ acts_as_nested_set :scope => 'root_id', :dependent => :destroy
acts_as_attachable :after_remove => :attachment_removed
acts_as_customizable
acts_as_watchable
@@ -68,8 +70,7 @@
:conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
named_scope :for_gantt, lambda {
{
- :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
- :order => "#{Issue.table_name}.due_date ASC, #{Issue.table_name}.start_date ASC, #{Issue.table_name}.id ASC"
+ :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version]
}
}
@@ -88,7 +89,6 @@
before_create :default_assign
before_save :close_duplicates, :update_done_ratio_from_issue_status
after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
- after_destroy :destroy_children
after_destroy :update_parent_attributes
# Returns true if usr or current user is allowed to view the issue
@@ -215,30 +215,29 @@
write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
end
- SAFE_ATTRIBUTES = %w(
- tracker_id
- status_id
- parent_issue_id
- category_id
- assigned_to_id
- priority_id
- fixed_version_id
- subject
- description
- start_date
- due_date
- done_ratio
- estimated_hours
- custom_field_values
- lock_version
- ) unless const_defined?(:SAFE_ATTRIBUTES)
+ safe_attributes 'tracker_id',
+ 'status_id',
+ 'parent_issue_id',
+ 'category_id',
+ 'assigned_to_id',
+ 'priority_id',
+ 'fixed_version_id',
+ 'subject',
+ 'description',
+ 'start_date',
+ 'due_date',
+ 'done_ratio',
+ 'estimated_hours',
+ 'custom_field_values',
+ 'custom_fields',
+ 'lock_version',
+ :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
- SAFE_ATTRIBUTES_ON_TRANSITION = %w(
- status_id
- assigned_to_id
- fixed_version_id
- done_ratio
- ) unless const_defined?(:SAFE_ATTRIBUTES_ON_TRANSITION)
+ safe_attributes 'status_id',
+ 'assigned_to_id',
+ 'fixed_version_id',
+ 'done_ratio',
+ :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
# Safely sets attributes
# Should be called from controllers instead of #attributes=
@@ -249,13 +248,8 @@
return unless attrs.is_a?(Hash)
# User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
- if new_record? || user.allowed_to?(:edit_issues, project)
- attrs = attrs.reject {|k,v| !SAFE_ATTRIBUTES.include?(k)}
- elsif new_statuses_allowed_to(user).any?
- attrs = attrs.reject {|k,v| !SAFE_ATTRIBUTES_ON_TRANSITION.include?(k)}
- else
- return
- end
+ attrs = delete_unsafe_attributes(attrs, user)
+ return if attrs.empty?
# Tracker must be set before since new_statuses_allowed_to depends on it.
if t = attrs.delete('tracker_id')
@@ -276,7 +270,7 @@
if !user.allowed_to?(:manage_subtasks, project)
attrs.delete('parent_issue_id')
elsif !attrs['parent_issue_id'].blank?
- attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'])
+ attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
end
end
@@ -428,7 +422,12 @@
# Returns an array of status that user is able to apply
def new_statuses_allowed_to(user, include_default=false)
- statuses = status.find_new_statuses_allowed_to(user.roles_for_project(project), tracker)
+ statuses = status.find_new_statuses_allowed_to(
+ user.roles_for_project(project),
+ tracker,
+ author == user,
+ assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
+ )
statuses << status unless statuses.empty?
statuses << IssueStatus.default if include_default
statuses = statuses.uniq.sort
@@ -461,11 +460,14 @@
(relations_from + relations_to).sort
end
- def all_dependent_issues
+ def all_dependent_issues(except=nil)
+ except ||= self
dependencies = []
relations_from.each do |relation|
- dependencies << relation.issue_to
- dependencies += relation.issue_to.all_dependent_issues
+ if relation.issue_to && relation.issue_to != except
+ dependencies << relation.issue_to
+ dependencies += relation.issue_to.all_dependent_issues(except)
+ end
end
dependencies
end
@@ -761,14 +763,6 @@
end
end
- def destroy_children
- unless leaf?
- children.each do |child|
- child.destroy
- end
- end
- end
-
# Update issues so their versions are not pointing to a
# fixed_version that is not shared with the issue's project
def self.update_versions(conditions=nil)
@@ -836,7 +830,7 @@
def create_journal
if @current_journal
# attributes changes
- (Issue.column_names - %w(id description root_id lft rgt lock_version created_on updated_on)).each {|c|
+ (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
@current_journal.details << JournalDetail.new(:property => 'attr',
:prop_key => c,
:old_value => @issue_before_change.send(c),
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/issue_relation.rb
--- a/app/models/issue_relation.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/issue_relation.rb Thu Mar 03 12:11:53 2011 +0000
@@ -84,14 +84,15 @@
def set_issue_to_dates
soonest_start = self.successor_soonest_start
- if soonest_start
+ if soonest_start && issue_to
issue_to.reschedule_after(soonest_start)
end
end
def successor_soonest_start
- return nil unless (TYPE_PRECEDES == self.relation_type) && (issue_from.start_date || issue_from.due_date)
- (issue_from.due_date || issue_from.start_date) + 1 + delay
+ if (TYPE_PRECEDES == self.relation_type) && delay && issue_from && (issue_from.start_date || issue_from.due_date)
+ (issue_from.due_date || issue_from.start_date) + 1 + delay
+ end
end
def <=>(relation)
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/issue_status.rb
--- a/app/models/issue_status.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/issue_status.rb Thu Mar 03 12:11:53 2011 +0000
@@ -25,7 +25,6 @@
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :maximum => 30
- validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
def after_save
@@ -51,10 +50,16 @@
# Returns an array of all statuses the given role can switch to
# Uses association cache when called more than one time
- def new_statuses_allowed_to(roles, tracker)
+ def new_statuses_allowed_to(roles, tracker, author=false, assignee=false)
if roles && tracker
role_ids = roles.collect(&:id)
- new_statuses = workflows.select {|w| role_ids.include?(w.role_id) && w.tracker_id == tracker.id}.collect{|w| w.new_status}.compact.sort
+ transitions = workflows.select do |w|
+ role_ids.include?(w.role_id) &&
+ w.tracker_id == tracker.id &&
+ (author || !w.author) &&
+ (assignee || !w.assignee)
+ end
+ transitions.collect{|w| w.new_status}.compact.sort
else
[]
end
@@ -62,24 +67,19 @@
# Same thing as above but uses a database query
# More efficient than the previous method if called just once
- def find_new_statuses_allowed_to(roles, tracker)
+ def find_new_statuses_allowed_to(roles, tracker, author=false, assignee=false)
if roles && tracker
+ conditions = {:role_id => roles.collect(&:id), :tracker_id => tracker.id}
+ conditions[:author] = false unless author
+ conditions[:assignee] = false unless assignee
+
workflows.find(:all,
:include => :new_status,
- :conditions => { :role_id => roles.collect(&:id),
- :tracker_id => tracker.id}).collect{ |w| w.new_status }.compact.sort
+ :conditions => conditions).collect{|w| w.new_status}.compact.sort
else
[]
end
end
-
- def new_status_allowed_to?(status, roles, tracker)
- if status && roles && tracker
- !workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => roles.collect(&:id), :tracker_id => tracker.id}).nil?
- else
- false
- end
- end
def <=>(status)
position <=> status.position
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/journal_detail.rb
--- a/app/models/journal_detail.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/journal_detail.rb Thu Mar 03 12:11:53 2011 +0000
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -17,9 +17,4 @@
class JournalDetail < ActiveRecord::Base
belongs_to :journal
-
- def before_save
- self.value = value[0..254] if value && value.is_a?(String)
- self.old_value = old_value[0..254] if old_value && old_value.is_a?(String)
- end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/mail_handler.rb
--- a/app/models/mail_handler.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/mail_handler.rb Thu Mar 03 12:11:53 2011 +0000
@@ -100,7 +100,7 @@
elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
receive_message_reply(m[1].to_i)
else
- receive_issue
+ dispatch_to_default
end
rescue ActiveRecord::RecordInvalid => e
# TODO: send a email to the user
@@ -113,6 +113,10 @@
logger.error "MailHandler: unauthorized attempt from #{user}" if logger
false
end
+
+ def dispatch_to_default
+ receive_issue
+ end
# Creates a new issue
def receive_issue
@@ -148,6 +152,9 @@
raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
end
+ # ignore CLI-supplied defaults for new issues
+ @@handler_options[:issue].clear
+
journal = issue.init_journal(user, cleaned_up_text_body)
issue.safe_attributes = issue_attributes_from_keywords(issue)
issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
@@ -232,8 +239,8 @@
def extract_keyword!(text, attr, format=nil)
keys = [attr.to_s.humanize]
if attr.is_a?(Symbol)
- keys << l("field_#{attr}", :default => '', :locale => user.language) if user
- keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
+ keys << l("field_#{attr}", :default => '', :locale => user.language) if user && user.language.present?
+ keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) if Setting.default_language.present?
end
keys.reject! {|k| k.blank?}
keys.collect! {|k| Regexp.escape(k)}
@@ -256,8 +263,8 @@
assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_user_from_keyword(k)
assigned_to = nil if assigned_to && !issue.assignable_users.include?(assigned_to)
- {
- 'tracker_id' => ((k = get_keyword(:tracker)) && issue.project.trackers.find_by_name(k).try(:id)) || issue.project.trackers.find(:first).try(:id),
+ attrs = {
+ 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.find_by_name(k).try(:id),
'status_id' => (k = get_keyword(:status)) && IssueStatus.find_by_name(k).try(:id),
'priority_id' => (k = get_keyword(:priority)) && IssuePriority.find_by_name(k).try(:id),
'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.find_by_name(k).try(:id),
@@ -268,6 +275,12 @@
'estimated_hours' => get_keyword(:estimated_hours, :override => true),
'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
}.delete_if {|k, v| v.blank? }
+
+ if issue.new_record? && attrs['tracker_id'].nil?
+ attrs['tracker_id'] = issue.project.trackers.find(:first).try(:id)
+ end
+
+ attrs
end
# Returns a Hash of issue custom field values extracted from keywords in the email body
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/mailer.rb
--- a/app/models/mailer.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/mailer.rb Thu Mar 03 12:11:53 2011 +0000
@@ -296,7 +296,7 @@
if raise_errors
raise e
elsif mylogger
- mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/email.yml."
+ mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml."
end
ensure
self.class.raise_delivery_errors = raise_errors
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/message.rb
--- a/app/models/message.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/message.rb Thu Mar 03 12:11:53 2011 +0000
@@ -42,6 +42,9 @@
after_create :add_author_as_watcher
+ named_scope :visible, lambda {|*args| { :include => {:board => :project},
+ :conditions => Project.allowed_to_condition(args.first || User.current, :view_messages) } }
+
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_messages, project)
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/news.rb
--- a/app/models/news.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/news.rb Thu Mar 03 12:11:53 2011 +0000
@@ -29,6 +29,11 @@
acts_as_activity_provider :find_options => {:include => [:project, :author]},
:author_key => :author_id
+ named_scope :visible, lambda {|*args| {
+ :include => :project,
+ :conditions => Project.allowed_to_condition(args.first || User.current, :view_news)
+ }}
+
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_news, project)
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/project.rb
--- a/app/models/project.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/project.rb Thu Mar 03 12:11:53 2011 +0000
@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Project < ActiveRecord::Base
+ include Redmine::SafeAttributes
+
# Project statuses
STATUS_ACTIVE = 1
STATUS_ARCHIVED = 9
@@ -64,7 +66,7 @@
:url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
:author => nil
- attr_protected :status, :enabled_module_names
+ attr_protected :status
validates_presence_of :name, :identifier
validates_uniqueness_of :identifier
@@ -85,6 +87,24 @@
named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
named_scope :visible_roots, lambda { { :conditions => Project.root_visible_by(User.current) } }
+ def initialize(attributes = nil)
+ super
+
+ initialized = (attributes || {}).stringify_keys
+ if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
+ self.identifier = Project.next_identifier
+ end
+ if !initialized.key?('is_public')
+ self.is_public = Setting.default_projects_public?
+ end
+ if !initialized.key?('enabled_module_names')
+ self.enabled_module_names = Setting.default_projects_modules
+ end
+ if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
+ self.trackers = Tracker.all
+ end
+ end
+
def identifier=(identifier)
super unless identifier_frozen?
end
@@ -443,24 +463,20 @@
# The earliest start date of a project, based on it's issues and versions
def start_date
- if module_enabled?(:issue_tracking)
- [
- issues.minimum('start_date'),
- shared_versions.collect(&:effective_date),
- shared_versions.collect {|v| v.fixed_issues.minimum('start_date')}
- ].flatten.compact.min
- end
+ [
+ issues.minimum('start_date'),
+ shared_versions.collect(&:effective_date),
+ shared_versions.collect(&:start_date)
+ ].flatten.compact.min
end
# The latest due date of an issue or version
def due_date
- if module_enabled?(:issue_tracking)
- [
- issues.maximum('due_date'),
- shared_versions.collect(&:effective_date),
- shared_versions.collect {|v| v.fixed_issues.maximum('due_date')}
- ].flatten.compact.max
- end
+ [
+ issues.maximum('due_date'),
+ shared_versions.collect(&:effective_date),
+ shared_versions.collect {|v| v.fixed_issues.maximum('due_date')}
+ ].flatten.compact.max
end
def overdue?
@@ -504,7 +520,7 @@
def enabled_module_names=(module_names)
if module_names && module_names.is_a?(Array)
- module_names = module_names.collect(&:to_s)
+ module_names = module_names.collect(&:to_s).reject(&:blank?)
# remove disabled modules
enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
# add new modules
@@ -513,7 +529,25 @@
enabled_modules.clear
end
end
+
+ # Returns an array of the enabled modules names
+ def enabled_module_names
+ enabled_modules.collect(&:name)
+ end
+
+ safe_attributes 'name',
+ 'description',
+ 'homepage',
+ 'is_public',
+ 'identifier',
+ 'custom_field_values',
+ 'custom_fields',
+ 'tracker_ids',
+ 'issue_custom_field_ids'
+ safe_attributes 'enabled_module_names',
+ :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
+
# Returns an array of projects that are in this project's hierarchy
#
# Example: parents, children, siblings
@@ -681,12 +715,20 @@
end
self.issues << new_issue
- issues_map[issue.id] = new_issue
+ if new_issue.new_record?
+ logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info
+ else
+ issues_map[issue.id] = new_issue unless new_issue.new_record?
+ end
end
# Relations after in case issues related each other
project.issues.each do |issue|
new_issue = issues_map[issue.id]
+ unless new_issue
+ # Issue was not copied
+ next
+ end
# Relations
issue.relations_from.each do |source_relation|
@@ -713,7 +755,12 @@
# Copies members from +project+
def copy_members(project)
- project.memberships.each do |member|
+ # Copy users first, then groups to handle members with inherited and given roles
+ members_to_copy = []
+ members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
+ members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
+
+ members_to_copy.each do |member|
new_member = Member.new
new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
# only copy non inherited roles
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/query.rb
--- a/app/models/query.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/query.rb Thu Mar 03 12:11:53 2011 +0000
@@ -187,10 +187,18 @@
if project
user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
else
- project_ids = Project.all(:conditions => Project.visible_by(User.current)).collect(&:id)
- if project_ids.any?
- # members of the user's projects
- user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids]).sort.collect{|s| [s.name, s.id.to_s] }
+ all_projects = Project.visible.all
+ if all_projects.any?
+ # members of visible projects
+ user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort.collect{|s| [s.name, s.id.to_s] }
+
+ # project filter
+ project_values = []
+ Project.project_tree(all_projects) do |p, level|
+ prefix = (level > 0 ? ('--' * level + ' ') : '')
+ project_values << ["#{prefix}#{p.name}", p.id.to_s]
+ end
+ @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
end
end
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
@@ -225,12 +233,6 @@
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
end
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
- # project filter
- project_values = Project.all(:conditions => Project.visible_by(User.current), :order => 'lft').map do |p|
- pre = (p.level > 0 ? ('--' * p.level + ' ') : '')
- ["#{pre}#{p.name}",p.id.to_s]
- end
- @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values}
end
@available_filters
end
@@ -376,15 +378,15 @@
# Returns true if the query is a grouped query
def grouped?
- !group_by.blank?
+ !group_by_column.nil?
end
def group_by_column
- groupable_columns.detect {|c| c.name.to_s == group_by}
+ groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
end
def group_by_statement
- group_by_column.groupable
+ group_by_column.try(:groupable)
end
def project_statement
@@ -564,9 +566,19 @@
sql = ''
case operator
when "="
- sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
+ if value.any?
+ sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
+ else
+ # IN an empty set
+ sql = "1=0"
+ end
when "!"
- sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
+ if value.any?
+ sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
+ else
+ # NOT IN an empty set
+ sql = "1=1"
+ end
when "!*"
sql = "#{db_table}.#{db_field} IS NULL"
sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository.rb
--- a/app/models/repository.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository.rb Thu Mar 03 12:11:53 2011 +0000
@@ -16,37 +16,53 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Repository < ActiveRecord::Base
+ include Redmine::Ciphering
+
belongs_to :project
- has_many :changesets, :order => "#{Changeset.table_name}.id DESC"
+ has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
has_many :changes, :through => :changesets
# Raw SQL to delete changesets and changes in the database
# has_many :changesets, :dependent => :destroy is too slow for big repositories
before_destroy :clear_changesets
+ validates_length_of :password, :maximum => 255, :allow_nil => true
# Checks if the SCM is enabled when creating a repository
validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
-
+
# Removes leading and trailing whitespace
def url=(arg)
write_attribute(:url, arg ? arg.to_s.strip : nil)
end
-
+
# Removes leading and trailing whitespace
def root_url=(arg)
write_attribute(:root_url, arg ? arg.to_s.strip : nil)
end
+
+ def password
+ read_ciphered_attribute(:password)
+ end
+
+ def password=(arg)
+ write_ciphered_attribute(:password, arg)
+ end
+
+ def scm_adapter
+ self.class.scm_adapter_class
+ end
def scm
- @scm ||= self.scm_adapter.new url, root_url, login, password
+ @scm ||= self.scm_adapter.new(url, root_url,
+ login, password, path_encoding)
update_attribute(:root_url, @scm.root_url) if root_url.blank?
@scm
end
-
+
def scm_name
self.class.scm_name
end
-
+
def supports_cat?
scm.supports_cat?
end
@@ -86,20 +102,25 @@
def diff(path, rev, rev_to)
scm.diff(path, rev, rev_to)
end
-
+
+ def diff_format_revisions(cs, cs_to, sep=':')
+ text = ""
+ text << cs_to.format_identifier + sep if cs_to
+ text << cs.format_identifier if cs
+ text
+ end
+
# Returns a path relative to the url of the repository
def relative_path(path)
path
end
-
+
# Finds and returns a revision with a number or the beginning of a hash
def find_changeset_by_name(name)
- # TODO: is this query efficient enough? can we write as single query?
- e = changesets.find(:first, :conditions => ['revision = ? OR scmid = ?', name.to_s, name.to_s])
- return e if e
- changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
+ return nil if name.blank?
+ changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
end
-
+
def latest_changeset
@latest_changeset ||= changesets.find(:first)
end
@@ -109,11 +130,12 @@
def latest_changesets(path, rev, limit=10)
if path.blank?
changesets.find(:all, :include => :user,
+ :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
:limit => limit)
else
changes.find(:all, :include => {:changeset => :user},
:conditions => ["path = ?", path.with_leading_slash],
- :order => "#{Changeset.table_name}.id DESC",
+ :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
:limit => limit).collect(&:changeset)
end
end
@@ -167,7 +189,12 @@
user
end
end
-
+
+ def repo_log_encoding
+ encoding = log_encoding.to_s.strip
+ encoding.blank? ? 'UTF-8' : encoding
+ end
+
# Fetches new changesets for all repositories of active projects
# Can be called periodically by an external script
# eg. ruby script/runner "Repository.fetch_changesets"
@@ -177,12 +204,12 @@
begin
project.repository.fetch_changesets
rescue Redmine::Scm::Adapters::CommandFailed => e
- logger.error "Repository: error during fetching changesets: #{e.message}"
+ logger.error "scm: error during fetching changesets: #{e.message}"
end
end
end
end
-
+
# scan changeset comments to find related and fixed issues for all repositories
def self.scan_changesets_for_issue_ids
find(:all).each(&:scan_changesets_for_issue_ids)
@@ -195,16 +222,50 @@
def self.available_scm
subclasses.collect {|klass| [klass.scm_name, klass.name]}
end
-
+
def self.factory(klass_name, *args)
klass = "Repository::#{klass_name}".constantize
klass.new(*args)
rescue
nil
end
-
+
+ def self.scm_adapter_class
+ nil
+ end
+
+ def self.scm_command
+ ret = ""
+ begin
+ ret = self.scm_adapter_class.client_command if self.scm_adapter_class
+ rescue Redmine::Scm::Adapters::CommandFailed => e
+ logger.error "scm: error during get command: #{e.message}"
+ end
+ ret
+ end
+
+ def self.scm_version_string
+ ret = ""
+ begin
+ ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
+ rescue Redmine::Scm::Adapters::CommandFailed => e
+ logger.error "scm: error during get version string: #{e.message}"
+ end
+ ret
+ end
+
+ def self.scm_available
+ ret = false
+ begin
+ ret = self.scm_adapter_class.client_available if self.scm_adapter_class
+ rescue Redmine::Scm::Adapters::CommandFailed => e
+ logger.error "scm: error during get scm available: #{e.message}"
+ end
+ ret
+ end
+
private
-
+
def before_save
# Strips url and root_url
url.strip!
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/.svn/all-wcprops
--- a/app/models/repository/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/.svn/all-wcprops Thu Mar 03 12:11:53 2011 +0000
@@ -1,47 +1,47 @@
K 25
svn:wc:ra_dav:version-url
V 46
-/svn/!svn/ver/3394/trunk/app/models/repository
+/svn/!svn/ver/4982/trunk/app/models/repository
END
subversion.rb
K 25
svn:wc:ra_dav:version-url
V 60
-/svn/!svn/ver/3360/trunk/app/models/repository/subversion.rb
+/svn/!svn/ver/4962/trunk/app/models/repository/subversion.rb
END
bazaar.rb
K 25
svn:wc:ra_dav:version-url
V 56
-/svn/!svn/ver/1537/trunk/app/models/repository/bazaar.rb
+/svn/!svn/ver/4982/trunk/app/models/repository/bazaar.rb
END
git.rb
K 25
svn:wc:ra_dav:version-url
V 53
-/svn/!svn/ver/3394/trunk/app/models/repository/git.rb
+/svn/!svn/ver/4975/trunk/app/models/repository/git.rb
END
mercurial.rb
K 25
svn:wc:ra_dav:version-url
V 59
-/svn/!svn/ver/3360/trunk/app/models/repository/mercurial.rb
+/svn/!svn/ver/4975/trunk/app/models/repository/mercurial.rb
END
filesystem.rb
K 25
svn:wc:ra_dav:version-url
V 60
-/svn/!svn/ver/1508/trunk/app/models/repository/filesystem.rb
+/svn/!svn/ver/4975/trunk/app/models/repository/filesystem.rb
END
cvs.rb
K 25
svn:wc:ra_dav:version-url
V 53
-/svn/!svn/ver/1768/trunk/app/models/repository/cvs.rb
+/svn/!svn/ver/4982/trunk/app/models/repository/cvs.rb
END
darcs.rb
K 25
svn:wc:ra_dav:version-url
V 55
-/svn/!svn/ver/3360/trunk/app/models/repository/darcs.rb
+/svn/!svn/ver/4982/trunk/app/models/repository/darcs.rb
END
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/.svn/entries
--- a/app/models/repository/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,15 +1,15 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/models/repository
http://redmine.rubyforge.org/svn
-2010-02-07T15:17:21.677785Z
-3394
-jplang
+2011-03-01T10:27:30.170724Z
+4982
+tmaruyama
@@ -32,11 +32,11 @@
-2010-09-23T14:37:44.487730Z
-b02ecaea01c5990cbde09689be12ec7d
-2010-02-02T17:02:32.020293Z
-3360
-edavis10
+2011-03-03T11:40:18.000000Z
+b9f366b3f38bd42149e09e6ed0ed8755
+2011-02-28T12:09:32.515358Z
+4962
+tmaruyama
has-props
@@ -58,7 +58,7 @@
-3306
+3358
bazaar.rb
file
@@ -66,11 +66,11 @@
-2010-09-23T14:37:44.483732Z
-84591e1c5afa1b74a10954dd521ceb4e
-2008-06-14T15:44:36.838876Z
-1537
-jplang
+2011-03-03T11:40:18.000000Z
+d6a230859a110341c0d64865248db51c
+2011-03-01T10:27:30.170724Z
+4982
+tmaruyama
has-props
@@ -92,7 +92,7 @@
-3617
+3877
git.rb
file
@@ -100,11 +100,11 @@
-2010-09-24T11:56:52.924049Z
-b3a159a082904eaf5e084eb0ea2a6988
-2010-02-07T15:17:21.677785Z
-3394
-jplang
+2011-03-03T11:40:18.000000Z
+dfce133f28c5c85af0740865903d5320
+2011-03-01T05:14:03.146872Z
+4975
+tmaruyama
has-props
@@ -126,7 +126,7 @@
-2872
+4245
mercurial.rb
file
@@ -134,11 +134,11 @@
-2010-09-24T11:56:52.932091Z
-6ca7d031f8d4d508518892a607462b88
-2010-02-02T17:02:32.020293Z
-3360
-edavis10
+2011-03-03T11:40:18.000000Z
+d659744391dfa02d54481d4dd8752930
+2011-03-01T05:14:03.146872Z
+4975
+tmaruyama
has-props
@@ -160,7 +160,7 @@
-3505
+4022
filesystem.rb
file
@@ -168,11 +168,11 @@
-2010-09-23T14:37:44.483732Z
-ed75a7df6cfdc54b7059c9bb20c079d3
-2008-06-08T15:40:24.603157Z
-1508
-jplang
+2011-03-03T11:40:18.000000Z
+994276556c2feac05d149117eea01413
+2011-03-01T05:14:03.146872Z
+4975
+tmaruyama
has-props
@@ -194,7 +194,7 @@
-1258
+1451
cvs.rb
file
@@ -202,11 +202,11 @@
-2010-09-23T14:37:44.483732Z
-4bf6afb2a5e03df854a06fb116fc6fd2
-2008-08-26T12:28:15.338708Z
-1768
-jplang
+2011-03-03T11:40:18.000000Z
+5eecf7abe1216489c4d1c2f6a107c36b
+2011-03-01T10:27:30.170724Z
+4982
+tmaruyama
has-props
@@ -228,7 +228,7 @@
-6357
+6952
darcs.rb
file
@@ -236,11 +236,11 @@
-2010-09-23T14:37:44.483732Z
-75c2836fd6ba92e0aef7a9cfc249ebb7
-2010-02-02T17:02:32.020293Z
-3360
-edavis10
+2011-03-03T11:40:18.000000Z
+03bf9128aa09528266cabb76ff0808a9
+2011-03-01T10:27:30.170724Z
+4982
+tmaruyama
has-props
@@ -262,5 +262,5 @@
-3566
+3826
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/.svn/text-base/bazaar.rb.svn-base
--- a/app/models/repository/.svn/text-base/bazaar.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/.svn/text-base/bazaar.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -19,16 +19,24 @@
class Repository::Bazaar < Repository
attr_protected :root_url
- validates_presence_of :url
+ validates_presence_of :url, :log_encoding
- def scm_adapter
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "Root directory",
+ "log_encoding" => "Commit messages encoding",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::BazaarAdapter
end
-
+
def self.scm_name
'Bazaar'
end
-
+
def entries(path=nil, identifier=nil)
entries = scm.entries(path, identifier)
if entries
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/.svn/text-base/cvs.rb.svn-base
--- a/app/models/repository/.svn/text-base/cvs.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/.svn/text-base/cvs.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -19,16 +19,25 @@
require 'digest/sha1'
class Repository::Cvs < Repository
- validates_presence_of :url, :root_url
+ validates_presence_of :url, :root_url, :log_encoding
- def scm_adapter
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "CVSROOT",
+ "root_url" => "Module",
+ "log_encoding" => "Commit messages encoding",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::CvsAdapter
end
-
+
def self.scm_name
'CVS'
end
-
+
def entry(path=nil, identifier=nil)
rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
scm.entry(path, rev.nil? ? nil : rev.committed_on)
@@ -104,23 +113,28 @@
scm.revisions('', fetch_since, nil, :with_paths => true) do |revision|
# only add the change to the database, if it doen't exists. the cvs log
# is not exclusive at all.
- unless changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
- revision
+ tmp_time = revision.time.clone
+ unless changes.find_by_path_and_revision(
+ scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
+ cmt = Changeset.normalize_comments(revision.message, repo_log_encoding)
cs = changesets.find(:first, :conditions=>{
- :committed_on=>revision.time-time_delta..revision.time+time_delta,
+ :committed_on=>tmp_time - time_delta .. tmp_time + time_delta,
:committer=>revision.author,
- :comments=>Changeset.normalize_comments(revision.message)
+ :comments=>cmt
})
# create a new changeset....
unless cs
# we use a temporaray revision number here (just for inserting)
# later on, we calculate a continous positive number
- latest = changesets.find(:first, :order => 'id DESC')
+ tmp_time2 = tmp_time.clone.gmtime
+ branch = revision.paths[0][:branch]
+ scmid = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S")
cs = Changeset.create(:repository => self,
- :revision => "_#{tmp_rev_num}",
+ :revision => "tmp#{tmp_rev_num}",
+ :scmid => scmid,
:committer => revision.author,
- :committed_on => revision.time,
+ :committed_on => tmp_time,
:comments => revision.message)
tmp_rev_num += 1
end
@@ -144,10 +158,13 @@
end
# Renumber new changesets in chronological order
- changesets.find(:all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE '_%'").each do |changeset|
+ changesets.find(
+ :all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE 'tmp%'"
+ ).each do |changeset|
changeset.update_attribute :revision, next_revision_number
end
end # transaction
+ @current_revision_number = nil
end
private
@@ -155,7 +172,9 @@
# Returns the next revision number to assign to a CVS changeset
def next_revision_number
# Need to retrieve existing revision numbers to sort them as integers
- @current_revision_number ||= (connection.select_values("SELECT revision FROM #{Changeset.table_name} WHERE repository_id = #{id} AND revision NOT LIKE '_%'").collect(&:to_i).max || 0)
+ sql = "SELECT revision FROM #{Changeset.table_name} "
+ sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'"
+ @current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0)
@current_revision_number += 1
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/.svn/text-base/darcs.rb.svn-base
--- a/app/models/repository/.svn/text-base/darcs.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/.svn/text-base/darcs.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -18,16 +18,24 @@
require 'redmine/scm/adapters/darcs_adapter'
class Repository::Darcs < Repository
- validates_presence_of :url
+ validates_presence_of :url, :log_encoding
- def scm_adapter
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "Root directory",
+ "log_encoding" => "Commit messages encoding",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::DarcsAdapter
end
-
+
def self.scm_name
'Darcs'
end
-
+
def entry(path=nil, identifier=nil)
patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
scm.entry(path, patch.nil? ? nil : patch.scmid)
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/.svn/text-base/filesystem.rb.svn-base
--- a/app/models/repository/.svn/text-base/filesystem.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/.svn/text-base/filesystem.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -24,14 +24,21 @@
attr_protected :root_url
validates_presence_of :url
- def scm_adapter
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "Root directory",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::FilesystemAdapter
end
-
+
def self.scm_name
'Filesystem'
end
-
+
def entries(path=nil, identifier=nil)
scm.entries(path, identifier)
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/.svn/text-base/git.rb.svn-base
--- a/app/models/repository/.svn/text-base/git.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/.svn/text-base/git.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -21,14 +21,35 @@
attr_protected :root_url
validates_presence_of :url
- def scm_adapter
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "Path to repository",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::GitAdapter
end
-
+
def self.scm_name
'Git'
end
+ def repo_log_encoding
+ 'UTF-8'
+ end
+
+ # Returns the identifier for the given git changeset
+ def self.changeset_identifier(changeset)
+ changeset.scmid
+ end
+
+ # Returns the readable identifier for the given git changeset
+ def self.format_changeset_identifier(changeset)
+ changeset.revision[0, 8]
+ end
+
def branches
scm.branches
end
@@ -37,6 +58,13 @@
scm.tags
end
+ def find_changeset_by_name(name)
+ return nil if name.nil? || name.empty?
+ e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
+ return e if e
+ changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
+ end
+
# With SCM's that have a sequential commit numbering, redmine is able to be
# clever and only fetch changesets going forward from the most recent one
# it knows about. However, with git, you never know if people have merged
@@ -62,7 +90,28 @@
revisions.reject!{|r| recent_revisions.include?(r.scmid)}
# Save the remaining ones to the database
- revisions.each{|r| r.save(self)} unless revisions.nil?
+ unless revisions.nil?
+ revisions.each do |rev|
+ transaction do
+ changeset = Changeset.new(
+ :repository => self,
+ :revision => rev.identifier,
+ :scmid => rev.scmid,
+ :committer => rev.author,
+ :committed_on => rev.time,
+ :comments => rev.message)
+
+ if changeset.save
+ rev.paths.each do |file|
+ Change.create(
+ :changeset => changeset,
+ :action => file[:action],
+ :path => file[:path])
+ end
+ end
+ end
+ end
+ end
end
def latest_changesets(path,rev,limit=10)
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/.svn/text-base/mercurial.rb.svn-base
--- a/app/models/repository/.svn/text-base/mercurial.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/.svn/text-base/mercurial.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -18,73 +18,101 @@
require 'redmine/scm/adapters/mercurial_adapter'
class Repository::Mercurial < Repository
+ # sort changesets by revision number
+ has_many :changesets, :order => "#{Changeset.table_name}.id DESC", :foreign_key => 'repository_id'
+
attr_protected :root_url
validates_presence_of :url
- def scm_adapter
+ FETCH_AT_ONCE = 100 # number of changesets to fetch at once
+
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "Root directory",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::MercurialAdapter
end
-
+
def self.scm_name
'Mercurial'
end
-
- def entries(path=nil, identifier=nil)
- entries=scm.entries(path, identifier)
- if entries
- entries.each do |entry|
- next unless entry.is_file?
- # Set the filesize unless browsing a specific revision
- if identifier.nil?
- full_path = File.join(root_url, entry.path)
- entry.size = File.stat(full_path).size if File.file?(full_path)
- end
- # Search the DB for the entry's last change
- change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
- if change
- entry.lastrev.identifier = change.changeset.revision
- entry.lastrev.name = change.changeset.revision
- entry.lastrev.author = change.changeset.committer
- entry.lastrev.revision = change.revision
+
+ def repo_log_encoding
+ 'UTF-8'
+ end
+
+ # Returns the readable identifier for the given mercurial changeset
+ def self.format_changeset_identifier(changeset)
+ "#{changeset.revision}:#{changeset.scmid}"
+ end
+
+ # Returns the identifier for the given Mercurial changeset
+ def self.changeset_identifier(changeset)
+ changeset.scmid
+ end
+
+ def branches
+ nil
+ end
+
+ def tags
+ nil
+ end
+
+ def diff_format_revisions(cs, cs_to, sep=':')
+ super(cs, cs_to, ' ')
+ end
+
+ # Finds and returns a revision with a number or the beginning of a hash
+ def find_changeset_by_name(name)
+ return nil if name.nil? || name.empty?
+ if /[^\d]/ =~ name or name.to_s.size > 8
+ e = changesets.find(:first, :conditions => ['scmid = ?', name.to_s])
+ else
+ e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
+ end
+ return e if e
+ changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) # last ditch
+ end
+
+ # Returns the latest changesets for +path+; sorted by revision number
+ # Default behavior is to search in cached changesets
+ def latest_changesets(path, rev, limit=10)
+ if path.blank?
+ changesets.find(:all, :include => :user, :limit => limit)
+ else
+ changesets.find(:all, :select => "DISTINCT #{Changeset.table_name}.*",
+ :joins => :changes,
+ :conditions => ["#{Change.table_name}.path = ? OR #{Change.table_name}.path LIKE ? ESCAPE ?",
+ path.with_leading_slash,
+ "#{path.with_leading_slash.gsub(/[%_\\]/) { |s| "\\#{s}" }}/%", '\\'],
+ :include => :user, :limit => limit)
+ end
+ end
+
+ def fetch_changesets
+ scm_rev = scm.info.lastrev.revision.to_i
+ db_rev = latest_changeset ? latest_changeset.revision.to_i : -1
+ return unless db_rev < scm_rev # already up-to-date
+
+ logger.debug "Fetching changesets for repository #{url}" if logger
+ (db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i|
+ transaction do
+ scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re|
+ cs = Changeset.create(:repository => self,
+ :revision => re.revision,
+ :scmid => re.scmid,
+ :committer => re.author,
+ :committed_on => re.time,
+ :comments => re.message)
+ re.paths.each { |e| cs.create_change(e) }
end
end
end
- entries
- end
-
- def fetch_changesets
- scm_info = scm.info
- if scm_info
- # latest revision found in database
- db_revision = latest_changeset ? latest_changeset.revision.to_i : -1
- # latest revision in the repository
- latest_revision = scm_info.lastrev
- return if latest_revision.nil?
- scm_revision = latest_revision.identifier.to_i
- if db_revision < scm_revision
- logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
- identifier_from = db_revision + 1
- while (identifier_from <= scm_revision)
- # loads changesets by batches of 100
- identifier_to = [identifier_from + 99, scm_revision].min
- revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true)
- transaction do
- revisions.each do |revision|
- changeset = Changeset.create(:repository => self,
- :revision => revision.identifier,
- :scmid => revision.scmid,
- :committer => revision.author,
- :committed_on => revision.time,
- :comments => revision.message)
-
- revision.paths.each do |change|
- changeset.create_change(change)
- end
- end
- end unless revisions.nil?
- identifier_from = identifier_to + 1
- end
- end
- end
+ self
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/.svn/text-base/subversion.rb.svn-base
--- a/app/models/repository/.svn/text-base/subversion.rb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/.svn/text-base/subversion.rb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -22,14 +22,18 @@
validates_presence_of :url
validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i
- def scm_adapter
+ def self.scm_adapter_class
Redmine::Scm::Adapters::SubversionAdapter
end
-
+
def self.scm_name
'Subversion'
end
+ def repo_log_encoding
+ 'UTF-8'
+ end
+
def latest_changesets(path, rev, limit=10)
revisions = scm.revisions(path, rev, nil, :limit => limit)
revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : []
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/bazaar.rb
--- a/app/models/repository/bazaar.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/bazaar.rb Thu Mar 03 12:11:53 2011 +0000
@@ -19,16 +19,24 @@
class Repository::Bazaar < Repository
attr_protected :root_url
- validates_presence_of :url
+ validates_presence_of :url, :log_encoding
- def scm_adapter
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "Root directory",
+ "log_encoding" => "Commit messages encoding",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::BazaarAdapter
end
-
+
def self.scm_name
'Bazaar'
end
-
+
def entries(path=nil, identifier=nil)
entries = scm.entries(path, identifier)
if entries
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/cvs.rb
--- a/app/models/repository/cvs.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/cvs.rb Thu Mar 03 12:11:53 2011 +0000
@@ -19,16 +19,25 @@
require 'digest/sha1'
class Repository::Cvs < Repository
- validates_presence_of :url, :root_url
+ validates_presence_of :url, :root_url, :log_encoding
- def scm_adapter
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "CVSROOT",
+ "root_url" => "Module",
+ "log_encoding" => "Commit messages encoding",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::CvsAdapter
end
-
+
def self.scm_name
'CVS'
end
-
+
def entry(path=nil, identifier=nil)
rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
scm.entry(path, rev.nil? ? nil : rev.committed_on)
@@ -104,23 +113,28 @@
scm.revisions('', fetch_since, nil, :with_paths => true) do |revision|
# only add the change to the database, if it doen't exists. the cvs log
# is not exclusive at all.
- unless changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
- revision
+ tmp_time = revision.time.clone
+ unless changes.find_by_path_and_revision(
+ scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
+ cmt = Changeset.normalize_comments(revision.message, repo_log_encoding)
cs = changesets.find(:first, :conditions=>{
- :committed_on=>revision.time-time_delta..revision.time+time_delta,
+ :committed_on=>tmp_time - time_delta .. tmp_time + time_delta,
:committer=>revision.author,
- :comments=>Changeset.normalize_comments(revision.message)
+ :comments=>cmt
})
# create a new changeset....
unless cs
# we use a temporaray revision number here (just for inserting)
# later on, we calculate a continous positive number
- latest = changesets.find(:first, :order => 'id DESC')
+ tmp_time2 = tmp_time.clone.gmtime
+ branch = revision.paths[0][:branch]
+ scmid = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S")
cs = Changeset.create(:repository => self,
- :revision => "_#{tmp_rev_num}",
+ :revision => "tmp#{tmp_rev_num}",
+ :scmid => scmid,
:committer => revision.author,
- :committed_on => revision.time,
+ :committed_on => tmp_time,
:comments => revision.message)
tmp_rev_num += 1
end
@@ -144,10 +158,13 @@
end
# Renumber new changesets in chronological order
- changesets.find(:all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE '_%'").each do |changeset|
+ changesets.find(
+ :all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE 'tmp%'"
+ ).each do |changeset|
changeset.update_attribute :revision, next_revision_number
end
end # transaction
+ @current_revision_number = nil
end
private
@@ -155,7 +172,9 @@
# Returns the next revision number to assign to a CVS changeset
def next_revision_number
# Need to retrieve existing revision numbers to sort them as integers
- @current_revision_number ||= (connection.select_values("SELECT revision FROM #{Changeset.table_name} WHERE repository_id = #{id} AND revision NOT LIKE '_%'").collect(&:to_i).max || 0)
+ sql = "SELECT revision FROM #{Changeset.table_name} "
+ sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'"
+ @current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0)
@current_revision_number += 1
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/darcs.rb
--- a/app/models/repository/darcs.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/darcs.rb Thu Mar 03 12:11:53 2011 +0000
@@ -18,16 +18,24 @@
require 'redmine/scm/adapters/darcs_adapter'
class Repository::Darcs < Repository
- validates_presence_of :url
+ validates_presence_of :url, :log_encoding
- def scm_adapter
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "Root directory",
+ "log_encoding" => "Commit messages encoding",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::DarcsAdapter
end
-
+
def self.scm_name
'Darcs'
end
-
+
def entry(path=nil, identifier=nil)
patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
scm.entry(path, patch.nil? ? nil : patch.scmid)
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/filesystem.rb
--- a/app/models/repository/filesystem.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/filesystem.rb Thu Mar 03 12:11:53 2011 +0000
@@ -24,14 +24,21 @@
attr_protected :root_url
validates_presence_of :url
- def scm_adapter
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "Root directory",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::FilesystemAdapter
end
-
+
def self.scm_name
'Filesystem'
end
-
+
def entries(path=nil, identifier=nil)
scm.entries(path, identifier)
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/git.rb
--- a/app/models/repository/git.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/git.rb Thu Mar 03 12:11:53 2011 +0000
@@ -21,14 +21,35 @@
attr_protected :root_url
validates_presence_of :url
- def scm_adapter
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "Path to repository",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::GitAdapter
end
-
+
def self.scm_name
'Git'
end
+ def repo_log_encoding
+ 'UTF-8'
+ end
+
+ # Returns the identifier for the given git changeset
+ def self.changeset_identifier(changeset)
+ changeset.scmid
+ end
+
+ # Returns the readable identifier for the given git changeset
+ def self.format_changeset_identifier(changeset)
+ changeset.revision[0, 8]
+ end
+
def branches
scm.branches
end
@@ -37,6 +58,13 @@
scm.tags
end
+ def find_changeset_by_name(name)
+ return nil if name.nil? || name.empty?
+ e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
+ return e if e
+ changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
+ end
+
# With SCM's that have a sequential commit numbering, redmine is able to be
# clever and only fetch changesets going forward from the most recent one
# it knows about. However, with git, you never know if people have merged
@@ -49,7 +77,7 @@
c = changesets.find(:first, :order => 'committed_on DESC')
since = (c ? c.committed_on - 7.days : nil)
- revisions = scm.revisions('', nil, nil, :all => true, :since => since, :reverse => true)
+ revisions = scm.revisions('', nil, nil, :all => true, :since => since)
return if revisions.nil? || revisions.empty?
recent_changesets = changesets.find(:all, :conditions => ['committed_on >= ?', since])
@@ -62,7 +90,28 @@
revisions.reject!{|r| recent_revisions.include?(r.scmid)}
# Save the remaining ones to the database
- revisions.each{|r| r.save(self)} unless revisions.nil?
+ unless revisions.nil?
+ revisions.each do |rev|
+ transaction do
+ changeset = Changeset.new(
+ :repository => self,
+ :revision => rev.identifier,
+ :scmid => rev.scmid,
+ :committer => rev.author,
+ :committed_on => rev.time,
+ :comments => rev.message)
+
+ if changeset.save
+ rev.paths.each do |file|
+ Change.create(
+ :changeset => changeset,
+ :action => file[:action],
+ :path => file[:path])
+ end
+ end
+ end
+ end
+ end
end
def latest_changesets(path,rev,limit=10)
@@ -75,7 +124,7 @@
"scmid IN (?)",
revisions.map!{|c| c.scmid}
],
- :order => 'id DESC'
+ :order => 'committed_on DESC'
)
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/mercurial.rb
--- a/app/models/repository/mercurial.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/mercurial.rb Thu Mar 03 12:11:53 2011 +0000
@@ -18,58 +18,81 @@
require 'redmine/scm/adapters/mercurial_adapter'
class Repository::Mercurial < Repository
+ # sort changesets by revision number
+ has_many :changesets, :order => "#{Changeset.table_name}.id DESC", :foreign_key => 'repository_id'
+
attr_protected :root_url
validates_presence_of :url
FETCH_AT_ONCE = 100 # number of changesets to fetch at once
- def scm_adapter
+ ATTRIBUTE_KEY_NAMES = {
+ "url" => "Root directory",
+ }
+ def self.human_attribute_name(attribute_key_name)
+ ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
+ end
+
+ def self.scm_adapter_class
Redmine::Scm::Adapters::MercurialAdapter
end
-
+
def self.scm_name
'Mercurial'
end
-
- def entries(path=nil, identifier=nil)
- scm.entries(path, identifier)
+
+ def repo_log_encoding
+ 'UTF-8'
+ end
+
+ # Returns the readable identifier for the given mercurial changeset
+ def self.format_changeset_identifier(changeset)
+ "#{changeset.revision}:#{changeset.scmid}"
+ end
+
+ # Returns the identifier for the given Mercurial changeset
+ def self.changeset_identifier(changeset)
+ changeset.scmid
end
def branches
- bras = scm.branches
- bras.sort unless bras == %w|default|
+ nil
end
- # Returns the latest changesets for +path+
- def latest_changesets(path, rev, limit=10)
- changesets.find(:all, :include => :user,
- :conditions => latest_changesets_cond(path, rev, limit),
- :limit => limit)
+ def tags
+ nil
end
- def latest_changesets_cond(path, rev, limit)
- cond, args = [], []
+ def diff_format_revisions(cs, cs_to, sep=':')
+ super(cs, cs_to, ' ')
+ end
- if scm.branchmap.member? rev
- # dirty hack to filter by branch. branch name should be in database.
- cond << "#{Changeset.table_name}.scmid IN (?)"
- args << scm.nodes_in_branch(rev, path, rev, 0, :limit => limit)
- elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil
- cond << "#{Changeset.table_name}.id <= ?"
- args << last.id
+ # Finds and returns a revision with a number or the beginning of a hash
+ def find_changeset_by_name(name)
+ return nil if name.nil? || name.empty?
+ if /[^\d]/ =~ name or name.to_s.size > 8
+ e = changesets.find(:first, :conditions => ['scmid = ?', name.to_s])
+ else
+ e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
end
+ return e if e
+ changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) # last ditch
+ end
- unless path.blank?
- # TODO: there must be a better way to build sub-query
- cond << "EXISTS (SELECT * FROM #{Change.table_name}
- WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id
- AND (#{Change.table_name}.path = ? OR #{Change.table_name}.path LIKE ?))"
- args << path.with_leading_slash << "#{path.with_leading_slash}/%"
+ # Returns the latest changesets for +path+; sorted by revision number
+ # Default behavior is to search in cached changesets
+ def latest_changesets(path, rev, limit=10)
+ if path.blank?
+ changesets.find(:all, :include => :user, :limit => limit)
+ else
+ changesets.find(:all, :select => "DISTINCT #{Changeset.table_name}.*",
+ :joins => :changes,
+ :conditions => ["#{Change.table_name}.path = ? OR #{Change.table_name}.path LIKE ? ESCAPE ?",
+ path.with_leading_slash,
+ "#{path.with_leading_slash.gsub(/[%_\\]/) { |s| "\\#{s}" }}/%", '\\'],
+ :include => :user, :limit => limit)
end
-
- [cond.join(' AND '), *args] unless cond.empty?
end
- private :latest_changesets_cond
def fetch_changesets
scm_rev = scm.info.lastrev.revision.to_i
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/repository/subversion.rb
--- a/app/models/repository/subversion.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/repository/subversion.rb Thu Mar 03 12:11:53 2011 +0000
@@ -22,14 +22,18 @@
validates_presence_of :url
validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i
- def scm_adapter
+ def self.scm_adapter_class
Redmine::Scm::Adapters::SubversionAdapter
end
-
+
def self.scm_name
'Subversion'
end
+ def repo_log_encoding
+ 'UTF-8'
+ end
+
def latest_changesets(path, rev, limit=10)
revisions = scm.revisions(path, rev, nil, :limit => limit)
revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : []
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/role.rb
--- a/app/models/role.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/role.rb Thu Mar 03 12:11:53 2011 +0000
@@ -43,7 +43,6 @@
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :maximum => 30
- validates_format_of :name, :with => /^[\w\s\'\-]*$/i
def permissions
read_attribute(:permissions) || []
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/setting.rb
--- a/app/models/setting.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/setting.rb Thu Mar 03 12:11:53 2011 +0000
@@ -65,6 +65,7 @@
UTF-16LE
EUC-JP
Shift_JIS
+ CP932
GB18030
GBK
ISCII91
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/time_entry.rb
--- a/app/models/time_entry.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/time_entry.rb Thu Mar 03 12:11:53 2011 +0000
@@ -66,6 +66,9 @@
# these attributes make time aggregations easier
def spent_on=(date)
super
+ if spent_on.is_a?(Time)
+ self.spent_on = spent_on.to_date
+ end
self.tyear = spent_on ? spent_on.year : nil
self.tmonth = spent_on ? spent_on.month : nil
self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/tracker.rb
--- a/app/models/tracker.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/tracker.rb Thu Mar 03 12:11:53 2011 +0000
@@ -31,7 +31,6 @@
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :maximum => 30
- validates_format_of :name, :with => /^[\w\s\'\-]*$/i
def to_s; name end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/user.rb
--- a/app/models/user.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/user.rb Thu Mar 03 12:11:53 2011 +0000
@@ -18,7 +18,8 @@
require "digest/sha1"
class User < Principal
-
+ include Redmine::SafeAttributes
+
# Account statuses
STATUS_ANONYMOUS = 0
STATUS_ACTIVE = 1
@@ -34,21 +35,21 @@
}
MAIL_NOTIFICATION_OPTIONS = [
- [:all, :label_user_mail_option_all],
- [:selected, :label_user_mail_option_selected],
- [:none, :label_user_mail_option_none],
- [:only_my_events, :label_user_mail_option_only_my_events],
- [:only_assigned, :label_user_mail_option_only_assigned],
- [:only_owner, :label_user_mail_option_only_owner]
- ]
+ ['all', :label_user_mail_option_all],
+ ['selected', :label_user_mail_option_selected],
+ ['only_my_events', :label_user_mail_option_only_my_events],
+ ['only_assigned', :label_user_mail_option_only_assigned],
+ ['only_owner', :label_user_mail_option_only_owner],
+ ['none', :label_user_mail_option_none]
+ ]
has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
:after_remove => Proc.new {|user, group| group.user_removed(user)}
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
has_many :changesets, :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
- has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
- has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
+ has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
+ has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
belongs_to :auth_source
has_one :ssamr_user_detail, :dependent => :destroy, :class_name => 'SsamrUserDetail'
@@ -62,7 +63,7 @@
attr_accessor :password, :password_confirmation
attr_accessor :last_before_login_on
# Prevents unauthorized assignments
- attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
+ attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
@@ -73,12 +74,14 @@
# Login must contain lettres, numbers, underscores only
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
validates_length_of :login, :maximum => 30
- validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
validates_length_of :firstname, :lastname, :maximum => 30
validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
validates_length_of :mail, :maximum => 60, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true
+ validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
+ before_destroy :remove_references_before_destroy
+
validates_acceptance_of :terms_and_conditions, :on => :create, :message => :must_accept_terms_and_conditions
def before_create
@@ -88,7 +91,9 @@
def before_save
# update hashed_password if password was set
- self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
+ if self.password && self.auth_source_id.blank?
+ salt_password(password)
+ end
end
def reload(*args)
@@ -130,7 +135,7 @@
return nil unless user.auth_source.authenticate(login, password)
else
# authentication with local password
- return nil unless User.hash_password(password) == user.hashed_password
+ return nil unless user.check_password?(password)
end
else
# user is not yet registered, try to authenticate with available sources
@@ -209,13 +214,21 @@
update_attribute(:status, STATUS_LOCKED)
end
+ # Returns true if +clear_password+ is the correct user's password, otherwise false
def check_password?(clear_password)
if auth_source_id.present?
auth_source.authenticate(self.login, clear_password)
else
- User.hash_password(clear_password) == self.hashed_password
+ User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
end
end
+
+ # Generates a random salt and computes hashed_password for +clear_password+
+ # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
+ def salt_password(clear_password)
+ self.salt = User.generate_salt
+ self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
+ end
# Does the backend storage allow this user to change their password?
def change_password_allowed?
@@ -271,12 +284,16 @@
notified_projects_ids
end
+ def valid_notification_options
+ self.class.valid_notification_options(self)
+ end
+
# Only users that belong to more than 1 project can select projects for which they are notified
- def valid_notification_options
+ def self.valid_notification_options(user=nil)
# Note that @user.membership.size would fail since AR ignores
# :include association option when doing a count
- if memberships.length < 1
- MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == :selected}
+ if user.nil? || user.memberships.length < 1
+ MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
else
MAIL_NOTIFICATION_OPTIONS
end
@@ -402,32 +419,54 @@
def allowed_to_globally?(action, options)
allowed_to?(action, nil, options.reverse_merge(:global => true))
end
+
+ safe_attributes 'login',
+ 'firstname',
+ 'lastname',
+ 'mail',
+ 'mail_notification',
+ 'language',
+ 'custom_field_values',
+ 'custom_fields',
+ 'identity_url'
+
+ safe_attributes 'status',
+ 'auth_source_id',
+ :if => lambda {|user, current_user| current_user.admin?}
+
+ safe_attributes 'group_ids',
+ :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
# Utility method to help check if a user should be notified about an
# event.
#
# TODO: only supports Issue events currently
def notify_about?(object)
- case mail_notification.to_sym
- when :all
+ case mail_notification
+ when 'all'
true
- when :selected
- # Handled by the Project
- when :none
- false
- when :only_my_events
+ when 'selected'
+ # user receives notifications for created/assigned issues on unselected projects
if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
true
else
false
end
- when :only_assigned
+ when 'none'
+ false
+ when 'only_my_events'
+ if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
+ true
+ else
+ false
+ end
+ when 'only_assigned'
if object.is_a?(Issue) && object.assigned_to == self
true
else
false
end
- when :only_owner
+ when 'only_owner'
if object.is_a?(Issue) && object.author == self
true
else
@@ -456,6 +495,20 @@
end
anonymous_user
end
+
+ # Salts all existing unsalted passwords
+ # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
+ # This method is used in the SaltPasswords migration and is to be kept as is
+ def self.salt_unsalted_passwords!
+ transaction do
+ User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
+ next if user.hashed_password.blank?
+ salt = User.generate_salt
+ hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
+ User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
+ end
+ end
+ end
protected
@@ -467,11 +520,42 @@
end
private
+
+ # Removes references that are not handled by associations
+ # Things that are not deleted are reassociated with the anonymous user
+ def remove_references_before_destroy
+ return if self.id.nil?
+
+ substitute = User.anonymous
+ Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
+ Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
+ JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
+ JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
+ Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ # Remove private queries and keep public ones
+ Query.delete_all ['user_id = ? AND is_public = ?', id, false]
+ Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
+ TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
+ Token.delete_all ['user_id = ?', id]
+ Watcher.delete_all ['user_id = ?', id]
+ WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
+ end
# Return password digest
def self.hash_password(clear_password)
Digest::SHA1.hexdigest(clear_password || "")
end
+
+ # Returns a 128bits random salt as a hex string (32 chars long)
+ def self.generate_salt
+ ActiveSupport::SecureRandom.hex(16)
+ end
+
end
class AnonymousUser < User
@@ -492,4 +576,9 @@
def mail; nil end
def time_zone; nil end
def rss_key; nil end
+
+ # Anonymous user can not be destroyed
+ def destroy
+ false
+ end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/user_preference.rb
--- a/app/models/user_preference.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/user_preference.rb Thu Mar 03 12:11:53 2011 +0000
@@ -51,4 +51,7 @@
def comments_sorting; self[:comments_sorting] end
def comments_sorting=(order); self[:comments_sorting]=order end
+
+ def warn_on_leaving_unsaved; self[:warn_on_leaving_unsaved] || '1'; end
+ def warn_on_leaving_unsaved=(value); self[:warn_on_leaving_unsaved]=value; end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/version.rb
--- a/app/models/version.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/version.rb Thu Mar 03 12:11:53 2011 +0000
@@ -43,7 +43,7 @@
end
def start_date
- effective_date
+ @start_date ||= fixed_issues.minimum('start_date')
end
def due_date
@@ -77,8 +77,7 @@
def behind_schedule?
if completed_pourcent == 100
return false
- elsif due_date && fixed_issues.present? && fixed_issues.minimum('start_date') # TODO: should use #start_date but that method is wrong...
- start_date = fixed_issues.minimum('start_date')
+ elsif due_date && start_date
done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
return done_date <= Date.today
else
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/wiki.rb
--- a/app/models/wiki.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/wiki.rb Thu Mar 03 12:11:53 2011 +0000
@@ -46,10 +46,10 @@
def find_page(title, options = {})
title = start_page if title.blank?
title = Wiki.titleize(title)
- page = pages.find_by_title(title)
+ page = pages.first(:conditions => ["LOWER(title) LIKE LOWER(?)", title])
if !page && !(options[:with_redirect] == false)
# search for a redirect
- redirect = redirects.find_by_title(title)
+ redirect = redirects.first(:conditions => ["LOWER(title) LIKE LOWER(?)", title])
page = find_page(redirect.redirects_to, :with_redirect => false) if redirect
end
page
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/models/wiki_page.rb
--- a/app/models/wiki_page.rb Tue Feb 22 16:48:15 2011 +0000
+++ b/app/models/wiki_page.rb Thu Mar 03 12:11:53 2011 +0000
@@ -1,5 +1,5 @@
# Redmine - project management software
-# Copyright (C) 2006-2009 Jean-Philippe Lang
+# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -149,17 +149,13 @@
end
end
-class WikiDiff
- attr_reader :diff, :words, :content_to, :content_from
+class WikiDiff < Redmine::Helpers::Diff
+ attr_reader :content_to, :content_from
def initialize(content_to, content_from)
@content_to = content_to
@content_from = content_from
- @words = content_to.text.split(/(\s+)/)
- @words = @words.select {|word| word != ' '}
- words_from = content_from.text.split(/(\s+)/)
- words_from = words_from.select {|word| word != ' '}
- @diff = words_from.diff @words
+ super(content_to.text, content_from.text)
end
end
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/sweepers/.svn/all-wcprops
--- a/app/sweepers/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-K 25
-svn:wc:ra_dav:version-url
-V 37
-/svn/!svn/ver/1157/trunk/app/sweepers
-END
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/sweepers/.svn/entries
--- a/app/sweepers/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/sweepers/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,7 +1,7 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/sweepers
http://redmine.rubyforge.org/svn
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/.svn/all-wcprops
--- a/app/views/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/.svn/all-wcprops Thu Mar 03 12:11:53 2011 +0000
@@ -1,5 +1,5 @@
K 25
svn:wc:ra_dav:version-url
V 34
-/svn/!svn/ver/4409/trunk/app/views
+/svn/!svn/ver/4983/trunk/app/views
END
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/.svn/entries
--- a/app/views/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,15 +1,15 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/views
http://redmine.rubyforge.org/svn
-2010-11-16T19:49:08.085592Z
-4409
-jplang
+2011-03-01T10:27:51.597350Z
+4983
+tmaruyama
@@ -38,36 +38,36 @@
members
dir
-welcome
+context_menus
dir
-context_menus
+welcome
dir
journals
dir
+time_entry_reports
+dir
+
workflows
dir
-time_entry_reports
-dir
-
reports
dir
-timelog
-dir
-
-settings
-dir
-
layouts
dir
custom_fields
dir
+settings
+dir
+
+timelog
+dir
+
users
dir
@@ -77,15 +77,15 @@
files
dir
+previews
+dir
+
ldap_auth_sources
dir
auth_sources
dir
-previews
-dir
-
search
dir
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/account/.svn/all-wcprops
--- a/app/views/account/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-K 25
-svn:wc:ra_dav:version-url
-V 42
-/svn/!svn/ver/3530/trunk/app/views/account
-END
-password_recovery.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 66
-/svn/!svn/ver/3341/trunk/app/views/account/password_recovery.rhtml
-END
-lost_password.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 61
-/svn/!svn/ver/747/trunk/app/views/account/lost_password.rhtml
-END
-register.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 57
-/svn/!svn/ver/2678/trunk/app/views/account/register.rhtml
-END
-login.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 54
-/svn/!svn/ver/3530/trunk/app/views/account/login.rhtml
-END
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/account/.svn/entries
--- a/app/views/account/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/account/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,7 +1,7 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/views/account
http://redmine.rubyforge.org/svn
@@ -32,7 +32,7 @@
-2010-09-23T14:37:44.495777Z
+2011-03-03T11:05:11.000000Z
232ce32581d2f869b81c300244decff5
2010-01-28T18:54:51.800438Z
3341
@@ -66,7 +66,7 @@
-2010-09-23T14:37:44.495777Z
+2011-03-03T11:05:11.000000Z
cf3d603bbb4825640988086c7871c165
2007-09-22T13:17:49.935719Z
747
@@ -100,7 +100,7 @@
-2010-09-23T14:37:44.495777Z
+2011-03-03T11:05:11.000000Z
82c6920dc6c95e35b0248de4be82885b
2009-04-21T13:43:57.529967Z
2678
@@ -134,7 +134,7 @@
-2010-09-23T14:37:44.495777Z
+2011-03-03T11:05:11.000000Z
72b586f183037fcab519bc7575d3b2b5
2010-03-03T20:21:05.265018Z
3530
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/activities/.svn/all-wcprops
--- a/app/views/activities/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-K 25
-svn:wc:ra_dav:version-url
-V 45
-/svn/!svn/ver/4047/trunk/app/views/activities
-END
-index.html.erb
-K 25
-svn:wc:ra_dav:version-url
-V 60
-/svn/!svn/ver/4047/trunk/app/views/activities/index.html.erb
-END
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/activities/.svn/entries
--- a/app/views/activities/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/activities/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,7 +1,7 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/views/activities
http://redmine.rubyforge.org/svn
@@ -32,7 +32,7 @@
-2010-09-24T12:48:29.578245Z
+2011-03-03T11:05:11.000000Z
9e6d9091be2c8769f8e262500d974f84
2010-08-27T14:05:54.014502Z
4047
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/admin/.svn/all-wcprops
--- a/app/views/admin/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-K 25
-svn:wc:ra_dav:version-url
-V 40
-/svn/!svn/ver/4271/trunk/app/views/admin
-END
-info.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 51
-/svn/!svn/ver/3200/trunk/app/views/admin/info.rhtml
-END
-plugins.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 54
-/svn/!svn/ver/2041/trunk/app/views/admin/plugins.rhtml
-END
-_menu.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 52
-/svn/!svn/ver/3909/trunk/app/views/admin/_menu.rhtml
-END
-_no_data.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 55
-/svn/!svn/ver/1040/trunk/app/views/admin/_no_data.rhtml
-END
-index.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 52
-/svn/!svn/ver/3176/trunk/app/views/admin/index.rhtml
-END
-projects.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 55
-/svn/!svn/ver/4271/trunk/app/views/admin/projects.rhtml
-END
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/admin/.svn/entries
--- a/app/views/admin/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/admin/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,7 +1,7 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/views/admin
http://redmine.rubyforge.org/svn
@@ -32,7 +32,7 @@
-2010-09-23T14:37:44.499776Z
+2011-03-03T11:05:11.000000Z
a7b3d0461b8dac7e68d5b758e6b93b45
2009-12-19T20:33:24.113306Z
3200
@@ -66,7 +66,7 @@
-2010-09-23T14:37:44.503775Z
+2011-03-03T11:05:11.000000Z
aacfd3ff934f52585eba4f460f52df31
2008-11-16T17:12:02.001794Z
2041
@@ -100,7 +100,7 @@
-2010-09-23T14:37:44.499776Z
+2011-03-03T11:05:11.000000Z
a2dcf50c0d70604e64f18cd28f15280b
2010-08-04T00:38:22.739166Z
3909
@@ -134,7 +134,7 @@
-2010-09-23T14:37:44.499776Z
+2011-03-03T11:05:11.000000Z
8db715728d1f5851c242e085110f2bb6
2008-01-05T11:33:49.132886Z
1040
@@ -168,7 +168,7 @@
-2010-09-23T14:37:44.499776Z
+2011-03-03T11:05:11.000000Z
35378eb3f0e385475e3bd0586f22324a
2009-12-17T18:21:02.630202Z
3176
@@ -202,7 +202,7 @@
-2010-11-19T13:04:49.448967Z
+2011-03-03T11:05:11.000000Z
9aaefdc17951db18479f38fb0db73ea7
2010-10-22T15:11:04.321155Z
4271
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/attachments/.svn/all-wcprops
--- a/app/views/attachments/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-K 25
-svn:wc:ra_dav:version-url
-V 46
-/svn/!svn/ver/3879/trunk/app/views/attachments
-END
-_links.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 59
-/svn/!svn/ver/2116/trunk/app/views/attachments/_links.rhtml
-END
-file.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 57
-/svn/!svn/ver/2693/trunk/app/views/attachments/file.rhtml
-END
-_form.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 58
-/svn/!svn/ver/3879/trunk/app/views/attachments/_form.rhtml
-END
-diff.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 57
-/svn/!svn/ver/2693/trunk/app/views/attachments/diff.rhtml
-END
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/attachments/.svn/entries
--- a/app/views/attachments/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/attachments/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,7 +1,7 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/views/attachments
http://redmine.rubyforge.org/svn
@@ -32,7 +32,7 @@
-2010-09-23T14:37:44.503775Z
+2011-03-03T11:05:10.000000Z
6f5329a1a81f3798a84a26b0261f5000
2008-12-09T16:54:46.963649Z
2116
@@ -66,7 +66,7 @@
-2010-09-23T14:37:44.507774Z
+2011-03-03T11:05:10.000000Z
b8bc854a91c56c3e3d45390e8ed4bb8d
2009-04-25T09:35:14.494071Z
2693
@@ -100,7 +100,7 @@
-2010-09-23T14:37:44.503775Z
+2011-03-03T11:05:10.000000Z
9cd3f9685ce632814961d9f7e67c4d26
2010-07-25T10:34:55.569539Z
3879
@@ -134,7 +134,7 @@
-2010-09-23T14:37:44.503775Z
+2011-03-03T11:05:10.000000Z
43e4ae1b5703daa55b37eb438a169304
2009-04-25T09:35:14.494071Z
2693
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/auth_sources/.svn/all-wcprops
--- a/app/views/auth_sources/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-K 25
-svn:wc:ra_dav:version-url
-V 47
-/svn/!svn/ver/3744/trunk/app/views/auth_sources
-END
-index.html.erb
-K 25
-svn:wc:ra_dav:version-url
-V 62
-/svn/!svn/ver/3436/trunk/app/views/auth_sources/index.html.erb
-END
-edit.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 57
-/svn/!svn/ver/333/trunk/app/views/auth_sources/edit.rhtml
-END
-_form.html.erb
-K 25
-svn:wc:ra_dav:version-url
-V 62
-/svn/!svn/ver/3744/trunk/app/views/auth_sources/_form.html.erb
-END
-new.rhtml
-K 25
-svn:wc:ra_dav:version-url
-V 56
-/svn/!svn/ver/333/trunk/app/views/auth_sources/new.rhtml
-END
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/auth_sources/.svn/entries
--- a/app/views/auth_sources/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/auth_sources/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,7 +1,7 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/views/auth_sources
http://redmine.rubyforge.org/svn
@@ -32,7 +32,7 @@
-2010-09-23T14:37:44.507774Z
+2011-03-03T11:05:10.000000Z
2f1b944eb2979ed37624c653969fa6bf
2010-02-15T16:41:21.789274Z
3436
@@ -66,7 +66,7 @@
-2010-09-23T14:37:44.507774Z
+2011-03-03T11:05:10.000000Z
6f4f645b6d66417180eaadb0e204a56e
2007-03-12T17:59:02.654744Z
333
@@ -100,7 +100,7 @@
-2010-09-23T14:37:44.507774Z
+2011-03-03T11:05:10.000000Z
4ea0fc7f46738e3709d552778de7a756
2010-05-23T03:16:31.304135Z
3744
@@ -134,7 +134,7 @@
-2010-09-23T14:37:44.507774Z
+2011-03-03T11:05:10.000000Z
1cc2f5049536de6e5d6d4e926f96ab38
2007-03-12T17:59:02.654744Z
333
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/auto_completes/.svn/all-wcprops
--- a/app/views/auto_completes/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-K 25
-svn:wc:ra_dav:version-url
-V 49
-/svn/!svn/ver/3945/trunk/app/views/auto_completes
-END
-issues.html.erb
-K 25
-svn:wc:ra_dav:version-url
-V 65
-/svn/!svn/ver/3945/trunk/app/views/auto_completes/issues.html.erb
-END
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/auto_completes/.svn/entries
--- a/app/views/auto_completes/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/auto_completes/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,7 +1,7 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/views/auto_completes
http://redmine.rubyforge.org/svn
@@ -32,7 +32,7 @@
-2010-09-23T14:37:44.511773Z
+2011-03-03T11:05:10.000000Z
d325b0677890591680b96985e16a0139
2010-08-17T15:03:58.074505Z
3945
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/boards/.svn/all-wcprops
--- a/app/views/boards/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/boards/.svn/all-wcprops Thu Mar 03 12:11:53 2011 +0000
@@ -1,13 +1,13 @@
K 25
svn:wc:ra_dav:version-url
V 41
-/svn/!svn/ver/4047/trunk/app/views/boards
+/svn/!svn/ver/4889/trunk/app/views/boards
END
show.rhtml
K 25
svn:wc:ra_dav:version-url
V 52
-/svn/!svn/ver/3426/trunk/app/views/boards/show.rhtml
+/svn/!svn/ver/4889/trunk/app/views/boards/show.rhtml
END
_form.rhtml
K 25
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/boards/.svn/entries
--- a/app/views/boards/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/boards/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,15 +1,15 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/views/boards
http://redmine.rubyforge.org/svn
-2010-08-27T14:05:54.014502Z
-4047
-edavis10
+2011-02-20T13:11:10.508449Z
+4889
+jplang
@@ -32,10 +32,10 @@
-2010-09-23T14:37:44.515775Z
-74a37f0fbb3bb635f2c0389a2624988e
-2010-02-14T11:52:12.027647Z
-3426
+2011-03-03T11:40:18.000000Z
+0f9b1735f403a56ed575c63dc474a611
+2011-02-20T13:11:10.508449Z
+4889
jplang
has-props
@@ -58,7 +58,7 @@
-3365
+3402
_form.rhtml
file
@@ -66,7 +66,7 @@
-2010-09-23T14:37:44.515775Z
+2011-03-03T11:05:10.000000Z
0416740cf4bf599b0da68e597c0cd44c
2007-05-13T17:09:56.765659Z
529
@@ -100,7 +100,7 @@
-2010-09-23T14:37:44.515775Z
+2011-03-03T11:05:10.000000Z
00f3643ebd3d5af2322b92c885a6eec6
2007-05-13T17:09:56.765659Z
529
@@ -134,7 +134,7 @@
-2010-09-24T12:48:28.319823Z
+2011-03-03T11:05:10.000000Z
5c323ebf8fea05f556896ed49186773b
2010-08-27T14:05:54.014502Z
4047
@@ -168,7 +168,7 @@
-2010-09-23T14:37:44.515775Z
+2011-03-03T11:05:10.000000Z
b951b4d73988508c5a42fdb7b7df695c
2007-05-13T17:09:56.765659Z
529
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/boards/.svn/text-base/show.rhtml.svn-base
--- a/app/views/boards/.svn/text-base/show.rhtml.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/boards/.svn/text-base/show.rhtml.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -69,4 +69,5 @@
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %>
+ <%= stylesheet_link_tag 'scm' %>
<% end %>
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/boards/show.rhtml
--- a/app/views/boards/show.rhtml Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/boards/show.rhtml Thu Mar 03 12:11:53 2011 +0000
@@ -69,4 +69,5 @@
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %>
+ <%= stylesheet_link_tag 'scm' %>
<% end %>
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/calendars/.svn/all-wcprops
--- a/app/views/calendars/.svn/all-wcprops Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/calendars/.svn/all-wcprops Thu Mar 03 12:11:53 2011 +0000
@@ -1,11 +1,11 @@
K 25
svn:wc:ra_dav:version-url
V 44
-/svn/!svn/ver/4238/trunk/app/views/calendars
+/svn/!svn/ver/4911/trunk/app/views/calendars
END
show.html.erb
K 25
svn:wc:ra_dav:version-url
V 58
-/svn/!svn/ver/4238/trunk/app/views/calendars/show.html.erb
+/svn/!svn/ver/4911/trunk/app/views/calendars/show.html.erb
END
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/calendars/.svn/entries
--- a/app/views/calendars/.svn/entries Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/calendars/.svn/entries Thu Mar 03 12:11:53 2011 +0000
@@ -1,15 +1,15 @@
10
dir
-4411
+4993
http://redmine.rubyforge.org/svn/trunk/app/views/calendars
http://redmine.rubyforge.org/svn
-2010-10-07T15:26:53.500793Z
-4238
-winterheart
+2011-02-21T14:02:22.565987Z
+4911
+jplang
@@ -32,11 +32,11 @@
-2010-11-19T13:04:49.472856Z
-d47ece5d0e0ff017f3f1b5ce4b776ba0
-2010-10-07T15:26:53.500793Z
-4238
-winterheart
+2011-03-03T11:40:18.000000Z
+7da8933c1cb08241ebe459d9cabe8043
+2011-02-21T14:02:22.565987Z
+4911
+jplang
has-props
@@ -58,5 +58,5 @@
-1946
+2007
diff -r 7cec015f07ce -r 73ff0e6a11b1 app/views/calendars/.svn/text-base/show.html.erb.svn-base
--- a/app/views/calendars/.svn/text-base/show.html.erb.svn-base Tue Feb 22 16:48:15 2011 +0000
+++ b/app/views/calendars/.svn/text-base/show.html.erb.svn-base Thu Mar 03 12:11:53 2011 +0000
@@ -1,10 +1,10 @@
-<%= l(:label_calendar) %>
+<%= @query.new_record? ? l(:label_calendar) : h(@query.name) %>
<% form_tag(calendar_path, :method => :put, :id => 'query_form') do %>
<%= hidden_field_tag('project_id', @project.to_param) if @project%>
-