Compare commits

..
93 Commits
Author SHA1 Message Date
Jean-Philippe Lang 0c98bf9c64 tagged version 1.3.3
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/1.3.3@9415 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 08:19:35 +00:00
Jean-Philippe Lang 5f2ebc202d Set version to 1.3.3.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9414 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 08:19:08 +00:00
Jean-Philippe Lang d85f8425d9 Updated 1.3 CHANGELOG.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9407 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 06:31:45 +00:00
Jean-Philippe Lang 4215198570 Merged r9380 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9396 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:45:32 +00:00
Jean-Philippe Lang ed0d0533e7 Merged r9379 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9394 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:42:56 +00:00
Jean-Philippe Lang 22d639402c #safe_attribute? not defined in 1.3-stable.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9357 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 13:22:27 +00:00
Jean-Philippe Lang cda408dd81 Merged r9350 and r9351 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9353 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 12:23:53 +00:00
Toshi MARUYAMA bec5e4443d Merged r9343 from trunk
Simplified Chinese translation updated by fangzheng (#10611)

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9345 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 10:50:36 +00:00
Toshi MARUYAMA cb37643ea9 Merge r9312 from trunk
pdf: fix tcpdf automatic line break bug (#10554)

Contributed by Jun NAITOH.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9322 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-04 12:23:13 +00:00
Toshi MARUYAMA 3e24f6f110 Merge r9313 from trunk
fix Dutch "label_file_added" translation (#10591)

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9314 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-03 12:01:30 +00:00
Toshi MARUYAMA 2e6347a2f1 pdf: backport r8731 (#10505)
Fixed that tag[0] may be nil.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9278 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-27 22:36:41 +00:00
Jean-Philippe Lang 8a2a17b8eb Merged r9228 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9229 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-11 08:21:52 +00:00
Jean-Philippe Lang b24ad1f0bc Merged r9128 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9158 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 19:40:50 +00:00
Jean-Philippe Lang 898e10491f Merged r9135 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9157 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:41:22 +00:00
Jean-Philippe Lang 4a9013917d Backported r9140 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9156 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:40:41 +00:00
Jean-Philippe Lang 4e64c01a32 Merged r9139 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9155 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:36:47 +00:00
Jean-Philippe Lang 177ff05b97 Merged r9138 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9154 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:36:17 +00:00
Jean-Philippe Lang e41afb4fe9 Backported r9137 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9153 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:35:22 +00:00
Jean-Philippe Lang 5475e0dcc5 Backported r9136 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9152 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:32:54 +00:00
Jean-Philippe Lang 985c885f98 Backported r9134 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9151 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:30:46 +00:00
Jean-Philippe Lang 210419047c Merged r9133 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9150 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:28:49 +00:00
Jean-Philippe Lang add23a54b5 Backported r9132 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9149 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:28:04 +00:00
Jean-Philippe Lang 8f66932a46 Merged r9131 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9148 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:24:51 +00:00
Jean-Philippe Lang bddc546bf0 Backported r9130 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9147 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:24:00 +00:00
Jean-Philippe Lang 5141f1e474 Merged r9129 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9146 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-07 18:22:06 +00:00
Jean-Philippe Lang 94a0d1abf0 Fixed: rubytree gem does not detect with rubgems 1.8 (#9815).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9127 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-06 18:30:50 +00:00
Jean-Philippe Lang ee6450f812 Merged r9124 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9125 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 19:54:32 +00:00
Jean-Philippe Lang 4b141793ab Merged r9122 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9123 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 17:16:14 +00:00
Jean-Philippe Lang fc584eb3ad Merged r9050 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9121 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 13:22:49 +00:00
Jean-Philippe Lang 6f30f85f96 Merged r9047 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9120 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 13:20:22 +00:00
Jean-Philippe Lang 8c6f674973 Backported r8883 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9119 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 13:09:46 +00:00
Jean-Philippe Lang 0b0f819cfa Merged r8882 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9118 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 13:08:06 +00:00
Jean-Philippe Lang 41dba6864a Merged r8917 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9117 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 13:07:24 +00:00
Jean-Philippe Lang 4cf38052c2 Merged r8880 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9116 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 13:05:31 +00:00
Jean-Philippe Lang f587807f6b Merged r8865 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9115 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 13:04:36 +00:00
Jean-Philippe Lang f74e4eddba Backported r8835 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9114 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 12:51:24 +00:00
Jean-Philippe Lang 39af3f1b79 Merged r8804 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9113 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 12:46:41 +00:00
Jean-Philippe Lang 92dde0d75f Merged r8863 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9112 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 12:45:51 +00:00
Jean-Philippe Lang 488926276d Merged r8841 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9111 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 12:44:39 +00:00
Jean-Philippe Lang 90c6a8579b Merged r8876 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9110 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 12:43:41 +00:00
Jean-Philippe Lang 4930b3e8bf Merged r8871 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9109 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-05 12:42:29 +00:00
Jean-Philippe Lang 09ea1fd58d Merged r9024 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9042 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-01 15:15:56 +00:00
Jean-Philippe Lang 8eb4bc9c29 Merged r9025 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9041 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-01 15:15:24 +00:00
Jean-Philippe Lang ffc6c99ae8 Merged r9026 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9040 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-01 15:14:52 +00:00
Jean-Philippe Lang 294accf197 Merged r9027 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9039 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-01 15:14:15 +00:00
Jean-Philippe Lang 8270de39f0 Merged r9028 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@9038 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-03-01 15:13:39 +00:00
Jean-Philippe Lang 26db0de8cc Merged r8909 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8910 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-19 15:41:24 +00:00
Jean-Philippe Lang f8875ee513 Merged r8902 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8903 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-17 22:37:03 +00:00
Toshi MARUYAMA cbd90fdeaf Merged r8900 from trunk (#10258)
fix Italian "description_date_range_list" translation by pasquale [:dedalus].

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8901 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-17 14:32:14 +00:00
Toshi MARUYAMA d5573e67e9 Merged r8896 from trunk (#10258)
Italian translation for 1.3-stable updated by Andrea Saccavini.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8897 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-17 11:05:33 +00:00
Toshi MARUYAMA 5b0178299b Merged r8806 from trunk (#10160)
Bulgarian translation for 1.3-stable updated by Ivan Cenov.

added:
* setting_repositories_encodings
changed:
* setting_cache_formatted_text

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8808 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-06 23:23:19 +00:00
Jean-Philippe Lang e0b4f151a2 Merged r8682 and r8800 from trunk for 1.3.1 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8801 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-06 11:16:39 +00:00
Toshi MARUYAMA 5581bfa539 Merged r8795 from trunk (#9832)
scm: fix revision graph height is hard-coded.

Contributed by Jakob Skjerning.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8796 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-06 03:08:39 +00:00
Jean-Philippe Lang 84c61545a9 Merged r8780 from trunk (#10115).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8781 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-05 11:56:42 +00:00
Jean-Philippe Lang eb2638a25f Backported r8686 from trunk (#10013).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8770 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-04 11:17:30 +00:00
Jean-Philippe Lang 6d66abe3ac Merged r8568 from trunk (#9937).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8769 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-04 11:15:22 +00:00
Jean-Philippe Lang 6c6519e595 Merged r8231, r8235, r8236, 8237 from trunk (#9795).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8768 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-04 11:14:23 +00:00
Jean-Philippe Lang 55d5e79e5f Merged r8258 from trunk (#9793).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8767 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-04 11:09:42 +00:00
Jean-Philippe Lang 1dcdba7cdc Merged r8210 from trunk (#9792).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8766 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-04 11:08:00 +00:00
Jean-Philippe Lang 3a474ead99 Merged r8209 from trunk (#9775).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8765 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-04 11:06:34 +00:00
Toshi MARUYAMA 9318cd2fbe Merged r8761 from trunk
attachment: fix that diff type is not saved in user preference.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8763 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-04 08:26:28 +00:00
Toshi MARUYAMA ce67da88c4 Merged r8760 from trunk
scm: git: add test that diff type is saved in user preference.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8762 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-04 08:26:00 +00:00
Toshi MARUYAMA f5cb899efe Merged r8757 from trunk
Korean translation for 1.3-stable updated by Ki Won Kim.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8759 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-02 23:39:17 +00:00
Jean-Philippe Lang 0efc84dad7 Merged r8752 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8753 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-02-02 19:46:49 +00:00
Jean-Philippe Lang e41bec7999 Merged r8641 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8642 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-01-14 09:40:49 +00:00
Toshi MARUYAMA 2eef4795c0 Merged r8600 from trunk
remove trailing white-spaces from config/locales/uk.yml.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8608 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-01-10 08:27:50 +00:00
Toshi MARUYAMA 53cc9148d4 Merged r8599 from trunk
fix Ukrainian "default_tracker_bug".

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8607 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-01-10 08:27:15 +00:00
Toshi MARUYAMA d8d1895315 1.3-stable: svn propset "svn:eol-style" native Arabic translation files (#9943)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8591 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-01-09 15:13:33 +00:00
Toshi MARUYAMA 77b58fc351 Merged r8571, r8572, r8573, r8574, r8578, r8582, r8588, r8589 from trunk
Arabic translations (#9943).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8590 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-01-09 15:07:48 +00:00
Toshi MARUYAMA 5e8ee66d69 Merged r8509 from trunk
Spanish "field_subject" translation changed by Gonzalo Fernández-Victorio.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8512 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-01-05 01:29:13 +00:00
Toshi MARUYAMA f1c23ac295 Merged r8508 from trunk
Spanish translation for 1.3-stable updated by Gonzalo Fernández-Victorio.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8511 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-01-05 01:28:49 +00:00
Toshi MARUYAMA 695596aa8a Merged r8391 from trunk
pt-BR translation updated by Enderson Maia.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8392 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-26 23:32:11 +00:00
Etienne Massip 0de2718d97 Merged r8238 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8240 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-15 22:08:48 +00:00
Jean-Philippe Lang e56dd42e64 CHANGELOG updated.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8176 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 14:48:48 +00:00
Jean-Philippe Lang a0c34ab916 Merged r8166 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8174 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 14:41:48 +00:00
Jean-Philippe Lang c8a6b01e9f Backported r8168 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8173 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 14:39:26 +00:00
Jean-Philippe Lang ff02412e75 Merged r8130 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8161 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 12:20:47 +00:00
Jean-Philippe Lang 166c3c15dd Merged r8112 from trunk (#9748).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8160 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 12:08:55 +00:00
Jean-Philippe Lang 65a7c7e96c Merged r8113 from trunk (#9738).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8159 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 12:07:13 +00:00
Jean-Philippe Lang 9a8cf5fa60 Merged r8103 from trunk (#9737).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8114 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 21:47:24 +00:00
Toshi MARUYAMA bf0008cd4e Merged r8106 from trunk
remove trailing white-spaces from calendar-hr.js

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8110 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 01:25:19 +00:00
Toshi MARUYAMA b849b9a47e Merged r8105 from trunk
convert calendar-hr.js (Croatian) from Windows-1250 to UTF-8.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8109 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 01:24:36 +00:00
Toshi MARUYAMA 011d9587a5 Merged r8099 from trunk
pdf: move note number to the head of line for single issue's PDF.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8100 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-06 03:49:39 +00:00
Jean-Philippe Lang b7d813047b Merged r8096 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8097 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-05 19:55:23 +00:00
Toshi MARUYAMA 08784f551e Merged r8090 from trunk
pdf: add note number for single issue's PDF.

Contributed by Akiko T.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8091 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-05 07:39:55 +00:00
Jean-Philippe Lang faad143c03 Merged r8030 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8032 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-02 22:31:14 +00:00
Toshi MARUYAMA 08b6d7b654 Merged r8022 from trunk
scm: git: mercurial: not show revision graph in sub directory.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8023 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-01 23:46:26 +00:00
Toshi MARUYAMA 18a30fd942 Merged r8015 from trunk
remove trailing white-spaces except SQL from extra/svn/Redmine.pm.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8016 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-01 05:31:19 +00:00
Toshi MARUYAMA 2813b93370 Merged r8013 from trunk
Japanese translation updated by Go MAEDA.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8014 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-01 04:13:04 +00:00
Toshi MARUYAMA 930cf0b487 Merged r8002 from trunk
Bulgarian translation updated by Ivan Cenov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8004 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-30 08:25:27 +00:00
Toshi MARUYAMA 27ac3193e3 Merged r8001 from trunk
pdf: fix textilized outputs of coderay line numbers.

Contributed by Jun NAITOH.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8003 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-30 08:25:01 +00:00
Jean-Philippe Lang 80f5d3d041 Set version to 1.3.0.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@7994 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-29 21:05:08 +00:00
Jean-Philippe Lang a3f8fbaee6 1.3-stable branch added.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@7992 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-29 21:02:26 +00:00
1554 changed files with 52224 additions and 37264 deletions

No files matched your search

-3
View File
@@ -5,7 +5,6 @@
/config/database.yml /config/database.yml
/config/email.yml /config/email.yml
/config/initializers/session_store.rb /config/initializers/session_store.rb
/config/initializers/secret_token.rb
/coverage /coverage
/db/*.db /db/*.db
/db/*.sqlite3 /db/*.sqlite3
@@ -19,11 +18,9 @@
/public/plugin_assets /public/plugin_assets
/tmp/* /tmp/*
/tmp/cache/* /tmp/cache/*
/tmp/pdf/*
/tmp/sessions/* /tmp/sessions/*
/tmp/sockets/* /tmp/sockets/*
/tmp/test/* /tmp/test/*
/vendor/cache
/vendor/rails /vendor/rails
*.rbc *.rbc
-3
View File
@@ -7,7 +7,6 @@ config/configuration.yml
config/database.yml config/database.yml
config/email.yml config/email.yml
config/initializers/session_store.rb config/initializers/session_store.rb
config/initializers/secret_token.rb
coverage coverage
db/*.db db/*.db
db/*.sqlite3 db/*.sqlite3
@@ -21,11 +20,9 @@ public/dispatch.*
public/plugin_assets public/plugin_assets
tmp/* tmp/*
tmp/cache/* tmp/cache/*
tmp/pdf/*
tmp/sessions/* tmp/sessions/*
tmp/sockets/* tmp/sockets/*
tmp/test/* tmp/test/*
vendor/cache
vendor/rails vendor/rails
*.rbc *.rbc
-92
View File
@@ -1,92 +0,0 @@
source 'http://rubygems.org'
gem 'rails', '3.2.11'
gem "jquery-rails", "~> 2.0.2"
gem "i18n", "~> 0.6.0"
gem "coderay", "~> 1.0.6"
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
gem "builder", "3.0.0"
# Optional gem for LDAP authentication
group :ldap do
gem "net-ldap", "~> 0.3.1"
end
# Optional gem for OpenID authentication
group :openid do
gem "ruby-openid", "~> 2.1.4", :require => "openid"
gem "rack-openid"
end
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
platforms :mri, :mingw do
group :rmagick do
# RMagick 2 supports ruby 1.9
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support
# different requirements for the same gem on different platforms
gem "rmagick", ">= 2.0.0"
end
end
# Database gems
platforms :mri, :mingw do
group :postgresql do
gem "pg", ">= 0.11.0"
end
group :sqlite do
gem "sqlite3"
end
end
platforms :mri_18, :mingw_18 do
group :mysql do
gem "mysql", "~> 2.8.1"
end
end
platforms :mri_19, :mingw_19 do
group :mysql do
gem "mysql2", "~> 0.3.11"
end
end
platforms :jruby do
gem "jruby-openssl"
group :mysql do
gem "activerecord-jdbcmysql-adapter"
end
group :postgresql do
gem "activerecord-jdbcpostgresql-adapter"
end
group :sqlite do
gem "activerecord-jdbcsqlite3-adapter"
end
end
group :development do
gem "rdoc", ">= 2.4.2"
gem "yard"
end
group :test do
gem "shoulda", "~> 2.11"
# Shoulda does not work nice on Ruby 1.9.3 and seems to need test-unit explicitely.
gem "test-unit", :platforms => [:mri_19]
gem "mocha", "0.12.3"
end
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
if File.exists?(local_gemfile)
puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v`
instance_eval File.read(local_gemfile)
end
# Load plugins' Gemfiles
Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file|
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
instance_eval File.read(file)
end
+12 -4
View File
@@ -1,7 +1,15 @@
#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake, # Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. # for example lib/tasks/switchtower.rake, and they will automatically be available to Rake.
require File.expand_path('../config/application', __FILE__) require(File.join(File.dirname(__FILE__), 'config', 'boot'))
RedmineApp::Application.load_tasks require 'rake'
require 'rake/testtask'
begin
require 'rdoc/task'
rescue LoadError
# RDoc is not available
end
require 'tasks/rails'
+28 -41
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -29,9 +29,6 @@ class AccountController < ApplicationController
else else
authenticate_user authenticate_user
end end
rescue AuthSourceException => e
logger.error "An error occured when authenticating #{params[:username]}: #{e.message}"
render_error :message => e.message
end end
# Log out current user and redirect to welcome page # Log out current user and redirect to welcome page
@@ -40,26 +37,19 @@ class AccountController < ApplicationController
redirect_to home_url redirect_to home_url
end end
# Lets user choose a new password # Enable user to choose a new password
def lost_password def lost_password
redirect_to(home_url) && return unless Setting.lost_password? redirect_to(home_url) && return unless Setting.lost_password?
if params[:token] if params[:token]
@token = Token.find_by_action_and_value("recovery", params[:token].to_s) @token = Token.find_by_action_and_value("recovery", params[:token])
if @token.nil? || @token.expired? redirect_to(home_url) && return unless @token and !@token.expired?
redirect_to home_url
return
end
@user = @token.user @user = @token.user
unless @user && @user.active?
redirect_to home_url
return
end
if request.post? if request.post?
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
if @user.save if @user.save
@token.destroy @token.destroy
flash[:notice] = l(:notice_account_password_updated) flash[:notice] = l(:notice_account_password_updated)
redirect_to signin_path redirect_to :action => 'login'
return return
end end
end end
@@ -67,23 +57,17 @@ class AccountController < ApplicationController
return return
else else
if request.post? if request.post?
user = User.find_by_mail(params[:mail].to_s) user = User.find_by_mail(params[:mail])
# user not found or not active # user not found in db
unless user && user.active? (flash.now[:error] = l(:notice_account_unknown_email); return) unless user
flash.now[:error] = l(:notice_account_unknown_email) # user uses an external authentification
return (flash.now[:error] = l(:notice_can_t_change_password); return) if user.auth_source_id
end
# user cannot change its password
unless user.change_password_allowed?
flash.now[:error] = l(:notice_can_t_change_password)
return
end
# create a new token for password recovery # create a new token for password recovery
token = Token.new(:user => user, :action => "recovery") token = Token.new(:user => user, :action => "recovery")
if token.save if token.save
Mailer.lost_password(token).deliver Mailer.deliver_lost_password(token)
flash[:notice] = l(:notice_account_lost_email_sent) flash[:notice] = l(:notice_account_lost_email_sent)
redirect_to signin_path redirect_to :action => 'login'
return return
end end
end end
@@ -97,9 +81,7 @@ class AccountController < ApplicationController
session[:auth_source_registration] = nil session[:auth_source_registration] = nil
@user = User.new(:language => Setting.default_language) @user = User.new(:language => Setting.default_language)
else else
user_params = params[:user] || {} @user = User.new(params[:user])
@user = User.new
@user.safe_attributes = user_params
@user.admin = false @user.admin = false
@user.register @user.register
if session[:auth_source_registration] if session[:auth_source_registration]
@@ -114,9 +96,7 @@ class AccountController < ApplicationController
end end
else else
@user.login = params[:user][:login] @user.login = params[:user][:login]
unless user_params[:identity_url].present? && user_params[:password].blank? && user_params[:password_confirmation].blank? @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
@user.password, @user.password_confirmation = user_params[:password], user_params[:password_confirmation]
end
case Setting.self_registration case Setting.self_registration
when '1' when '1'
@@ -142,11 +122,19 @@ class AccountController < ApplicationController
token.destroy token.destroy
flash[:notice] = l(:notice_account_activated) flash[:notice] = l(:notice_account_activated)
end end
redirect_to signin_path redirect_to :action => 'login'
end end
private private
def logout_user
if User.current.logged?
cookies.delete :autologin
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
self.logged_user = nil
end
end
def authenticate_user def authenticate_user
if Setting.openid? && using_open_id? if Setting.openid? && using_open_id?
open_id_authenticate(params[:openid_url]) open_id_authenticate(params[:openid_url])
@@ -169,7 +157,7 @@ class AccountController < ApplicationController
end end
def open_id_authenticate(openid_url) def open_id_authenticate(openid_url)
authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url, :method => :post) do |result, identity_url, registration| authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url) do |result, identity_url, registration|
if result.successful? if result.successful?
user = User.find_or_initialize_by_identity_url(identity_url) user = User.find_or_initialize_by_identity_url(identity_url)
if user.new_record? if user.new_record?
@@ -210,7 +198,6 @@ class AccountController < ApplicationController
end end
def successful_authentication(user) def successful_authentication(user)
logger.info "Successful authentication for '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}"
# Valid user # Valid user
self.logged_user = user self.logged_user = user
# generate a key and set cookie if autologin # generate a key and set cookie if autologin
@@ -252,9 +239,9 @@ class AccountController < ApplicationController
def register_by_email_activation(user, &block) def register_by_email_activation(user, &block)
token = Token.new(:user => user, :action => "register") token = Token.new(:user => user, :action => "register")
if user.save and token.save if user.save and token.save
Mailer.register(token).deliver Mailer.deliver_register(token)
flash[:notice] = l(:notice_account_register_done) flash[:notice] = l(:notice_account_register_done)
redirect_to signin_path redirect_to :action => 'login'
else else
yield if block_given? yield if block_given?
end end
@@ -282,7 +269,7 @@ class AccountController < ApplicationController
def register_manually_by_administrator(user, &block) def register_manually_by_administrator(user, &block)
if user.save if user.save
# Sends an email to the administrators # Sends an email to the administrators
Mailer.account_activation_request(user).deliver Mailer.deliver_account_activation_request(user)
account_pending account_pending
else else
yield if block_given? yield if block_given?
@@ -291,6 +278,6 @@ class AccountController < ApplicationController
def account_pending def account_pending
flash[:notice] = l(:notice_account_pending) flash[:notice] = l(:notice_account_pending)
redirect_to signin_path redirect_to :action => 'login'
end end
end end
+3 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -40,10 +40,10 @@ class ActivitiesController < ApplicationController
events = @activity.events(@date_from, @date_to) events = @activity.events(@date_from, @date_to)
if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, events.size, User.current, current_language]) if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, User.current, current_language])
respond_to do |format| respond_to do |format|
format.html { format.html {
@events_by_day = events.group_by {|event| User.current.time_to_date(event.event_datetime)} @events_by_day = events.group_by(&:event_date)
render :layout => false if request.xhr? render :layout => false if request.xhr?
} }
format.atom { format.atom {
+11 -13
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,10 +17,6 @@
class AdminController < ApplicationController class AdminController < ApplicationController
layout 'admin' layout 'admin'
menu_item :projects, :only => :projects
menu_item :plugins, :only => :plugins
menu_item :info, :only => :info
before_filter :require_admin before_filter :require_admin
helper :sort helper :sort
include SortHelper include SortHelper
@@ -30,12 +26,14 @@ class AdminController < ApplicationController
end end
def projects def projects
@status = params[:status] || 1 @status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
scope = Project.status(@status) unless params[:name].blank?
scope = scope.like(params[:name]) if params[:name].present? name = "%#{params[:name].strip.downcase}%"
c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name]
@projects = scope.all(:order => 'lft') end
@projects = Project.find :all, :order => 'lft',
:conditions => c.conditions
render :action => "projects", :layout => false if request.xhr? render :action => "projects", :layout => false if request.xhr?
end end
@@ -63,7 +61,7 @@ class AdminController < ApplicationController
# Force ActionMailer to raise delivery errors so we can catch it # Force ActionMailer to raise delivery errors so we can catch it
ActionMailer::Base.raise_delivery_errors = true ActionMailer::Base.raise_delivery_errors = true
begin begin
@test = Mailer.test_email(User.current).deliver @test = Mailer.deliver_test(User.current)
flash[:notice] = l(:notice_email_sent, User.current.mail) flash[:notice] = l(:notice_email_sent, User.current.mail)
rescue Exception => e rescue Exception => e
flash[:error] = l(:notice_email_error, e.message) flash[:error] = l(:notice_email_error, e.message)
@@ -77,7 +75,7 @@ class AdminController < ApplicationController
@checklist = [ @checklist = [
[:text_default_administrator_account_changed, User.default_admin_account_changed?], [:text_default_administrator_account_changed, User.default_admin_account_changed?],
[:text_file_repository_writable, File.writable?(Attachment.storage_path)], [:text_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)], [:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
[:text_rmagick_available, Object.const_defined?(:Magick)] [:text_rmagick_available, Object.const_defined?(:Magick)]
] ]
end end
+91 -125
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -23,58 +23,38 @@ class Unauthorized < Exception; end
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Redmine::I18n include Redmine::I18n
class_attribute :accept_api_auth_actions
class_attribute :accept_rss_auth_actions
class_attribute :model_object
layout 'base' layout 'base'
exempt_from_layout 'builder', 'rsb'
protect_from_forgery protect_from_forgery
def handle_unverified_request def handle_unverified_request
super super
cookies.delete(:autologin) cookies.delete(:autologin)
end end
# Remove broken cookie after upgrade from 0.8.x (#4292)
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
# TODO: remove it when Rails is fixed
before_filter :delete_broken_cookies
def delete_broken_cookies
if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
cookies.delete '_redmine_session'
redirect_to home_path
return false
end
end
before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization before_filter :user_setup, :check_if_login_required, :set_localization
filter_parameter_logging :password
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
rescue_from ::Unauthorized, :with => :deny_access rescue_from ::Unauthorized, :with => :deny_access
rescue_from ::ActionView::MissingTemplate, :with => :missing_template
include Redmine::Search::Controller include Redmine::Search::Controller
include Redmine::MenuManager::MenuController include Redmine::MenuManager::MenuController
helper Redmine::MenuManager::MenuHelper helper Redmine::MenuManager::MenuHelper
def session_expiration Redmine::Scm::Base.all.each do |scm|
if session[:user_id] require_dependency "repository/#{scm.underscore}"
if session_expired? && !try_to_autologin
reset_session
flash[:error] = l(:error_session_expired)
redirect_to signin_url
else
session[:atime] = Time.now.utc.to_i
end
end
end
def session_expired?
if Setting.session_lifetime?
unless session[:ctime] && (Time.now.utc.to_i - session[:ctime].to_i <= Setting.session_lifetime.to_i * 60)
return true
end
end
if Setting.session_timeout?
unless session[:atime] && (Time.now.utc.to_i - session[:atime].to_i <= Setting.session_timeout.to_i * 60)
return true
end
end
false
end
def start_user_session(user)
session[:user_id] = user.id
session[:ctime] = Time.now.utc.to_i
session[:atime] = Time.now.utc.to_i
end end
def user_setup def user_setup
@@ -82,48 +62,33 @@ class ApplicationController < ActionController::Base
Setting.check_cache Setting.check_cache
# Find the current user # Find the current user
User.current = find_current_user User.current = find_current_user
logger.info(" Current user: " + (User.current.logged? ? "#{User.current.login} (id=#{User.current.id})" : "anonymous")) if logger
end end
# Returns the current user or nil if no user is logged in # Returns the current user or nil if no user is logged in
# and starts a session if needed # and starts a session if needed
def find_current_user def find_current_user
user = nil
unless api_request?
if session[:user_id] if session[:user_id]
# existing session # existing session
user = (User.active.find(session[:user_id]) rescue nil) (User.active.find(session[:user_id]) rescue nil)
elsif autologin_user = try_to_autologin elsif cookies[:autologin] && Setting.autologin?
user = autologin_user # auto-login feature starts a new session
user = User.try_to_autologin(cookies[:autologin])
session[:user_id] = user.id if user
user
elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth? elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
# RSS key authentication does not start a session # RSS key authentication does not start a session
user = User.find_by_rss_key(params[:key]) User.find_by_rss_key(params[:key])
end elsif Setting.rest_api_enabled? && accept_api_auth?
end
if user.nil? && Setting.rest_api_enabled? && accept_api_auth?
if (key = api_key_from_request) if (key = api_key_from_request)
# Use API key # Use API key
user = User.find_by_api_key(key) User.find_by_api_key(key)
else else
# HTTP Basic, either username/password or API key/random # HTTP Basic, either username/password or API key/random
authenticate_with_http_basic do |username, password| authenticate_with_http_basic do |username, password|
user = User.try_to_login(username, password) || User.find_by_api_key(username) User.try_to_login(username, password) || User.find_by_api_key(username)
end end
end end
end end
user
end
def try_to_autologin
if cookies[:autologin] && Setting.autologin?
# auto-login feature starts a new session
user = User.try_to_autologin(cookies[:autologin])
if user
reset_session
start_user_session(user)
end
user
end
end end
# Sets the logged in user # Sets the logged in user
@@ -131,21 +96,12 @@ class ApplicationController < ActionController::Base
reset_session reset_session
if user && user.is_a?(User) if user && user.is_a?(User)
User.current = user User.current = user
start_user_session(user) session[:user_id] = user.id
else else
User.current = User.anonymous User.current = User.anonymous
end end
end end
# Logs out current user
def logout_user
if User.current.logged?
cookies.delete :autologin
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
self.logged_user = nil
end
end
# check if login is globally required to access the application # check if login is globally required to access the application
def check_if_login_required def check_if_login_required
# no check needed if user is already logged in # no check needed if user is already logged in
@@ -253,7 +209,7 @@ class ApplicationController < ActionController::Base
end end
def find_model_object def find_model_object
model = self.class.model_object model = self.class.read_inheritable_attribute('model_object')
if model if model
@object = model.find(params[:id]) @object = model.find(params[:id])
self.instance_variable_set('@' + controller_name.singularize, @object) if @object self.instance_variable_set('@' + controller_name.singularize, @object) if @object
@@ -263,7 +219,7 @@ class ApplicationController < ActionController::Base
end end
def self.model_object(model) def self.model_object(model)
self.model_object = model write_inheritable_attribute('model_object', model)
end end
# Filter for bulk issue operations # Filter for bulk issue operations
@@ -280,11 +236,20 @@ class ApplicationController < ActionController::Base
render_404 render_404
end end
# Check if project is unique before bulk operations
def check_project_uniqueness
unless @project
# TODO: let users bulk edit/move/destroy issues from different projects
render_error 'Can not bulk edit/move/destroy issues from different projects'
return false
end
end
# make sure that the user is a member of the project (or admin) if project is private # make sure that the user is a member of the project (or admin) if project is private
# used as a before_filter for actions that do not require any particular permission on the project # used as a before_filter for actions that do not require any particular permission on the project
def check_project_privacy def check_project_privacy
if @project && !@project.archived? if @project && @project.active?
if @project.visible? if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
true true
else else
deny_access deny_access
@@ -297,16 +262,12 @@ class ApplicationController < ActionController::Base
end end
def back_url def back_url
url = params[:back_url] params[:back_url] || request.env['HTTP_REFERER']
if url.nil? && referer = request.env['HTTP_REFERER']
url = CGI.unescape(referer.to_s)
end
url
end end
def redirect_back_or_default(default) def redirect_back_or_default(default)
back_url = params[:back_url].to_s back_url = CGI.unescape(params[:back_url].to_s)
if back_url.present? if !back_url.blank?
begin begin
uri = URI.parse(back_url) uri = URI.parse(back_url)
# do not redirect user to another host or to the login or register page # do not redirect user to another host or to the login or register page
@@ -315,7 +276,6 @@ class ApplicationController < ActionController::Base
return return
end end
rescue URI::InvalidURIError rescue URI::InvalidURIError
logger.warn("Could not redirect to invalid URL #{back_url}")
# redirect to default # redirect to default
end end
end end
@@ -323,19 +283,6 @@ class ApplicationController < ActionController::Base
false false
end end
# Redirects to the request referer if present, redirects to args or call block otherwise.
def redirect_to_referer_or(*args, &block)
redirect_to :back
rescue ::ActionController::RedirectBackError
if args.any?
redirect_to *args
elsif block_given?
block.call
else
raise "#redirect_to_referer_or takes arguments or a block"
end
end
def render_403(options={}) def render_403(options={})
@project = nil @project = nil
render_error({:message => :notice_not_authorized, :status => 403}.merge(options)) render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
@@ -359,17 +306,13 @@ class ApplicationController < ActionController::Base
format.html { format.html {
render :template => 'common/error', :layout => use_layout, :status => @status render :template => 'common/error', :layout => use_layout, :status => @status
} }
format.any { head @status } format.atom { head @status }
format.xml { head @status }
format.js { head @status }
format.json { head @status }
end end
end end
# Handler for ActionView::MissingTemplate exception
def missing_template
logger.warn "Missing template, responding with 404"
@project = nil
render_404
end
# Filter for actions that provide an API response # Filter for actions that provide an API response
# but have no HTML representation for non admin users # but have no HTML representation for non admin users
def require_admin_or_api_request def require_admin_or_api_request
@@ -406,11 +349,23 @@ class ApplicationController < ActionController::Base
:content_type => 'application/atom+xml' :content_type => 'application/atom+xml'
end end
# TODO: remove in Redmine 1.4
def self.accept_key_auth(*actions)
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
accept_rss_auth(*actions)
end
# TODO: remove in Redmine 1.4
def accept_key_auth_actions
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
self.class.accept_rss_auth
end
def self.accept_rss_auth(*actions) def self.accept_rss_auth(*actions)
if actions.any? if actions.any?
self.accept_rss_auth_actions = actions write_inheritable_attribute('accept_rss_auth_actions', actions)
else else
self.accept_rss_auth_actions || [] read_inheritable_attribute('accept_rss_auth_actions') || []
end end
end end
@@ -420,9 +375,9 @@ class ApplicationController < ActionController::Base
def self.accept_api_auth(*actions) def self.accept_api_auth(*actions)
if actions.any? if actions.any?
self.accept_api_auth_actions = actions write_inheritable_attribute('accept_api_auth_actions', actions)
else else
self.accept_api_auth_actions || [] read_inheritable_attribute('accept_api_auth_actions') || []
end end
end end
@@ -502,9 +457,9 @@ class ApplicationController < ActionController::Base
# Returns the API key present in the request # Returns the API key present in the request
def api_key_from_request def api_key_from_request
if params[:key].present? if params[:key].present?
params[:key].to_s params[:key]
elsif request.headers["X-Redmine-API-Key"].present? elsif request.headers["X-Redmine-API-Key"].present?
request.headers["X-Redmine-API-Key"].to_s request.headers["X-Redmine-API-Key"]
end end
end end
@@ -536,25 +491,36 @@ class ApplicationController < ActionController::Base
render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator." render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
end end
# Renders a 200 response for successfull updates or deletions via the API
def render_api_ok
# head :ok would return a response body with one space
render :text => '', :status => :ok, :layout => nil
end
# Renders API response on validation failure # Renders API response on validation failure
def render_validation_errors(objects) def render_validation_errors(object)
if objects.is_a?(Array) options = { :status => :unprocessable_entity, :layout => false }
@error_messages = objects.map {|object| object.errors.full_messages}.flatten options.merge!(case params[:format]
when 'xml'; { :xml => object.errors }
when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance
else else
@error_messages = objects.errors.full_messages raise "Unknown format #{params[:format]} in #render_validation_errors"
end end
render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil )
render options
end end
# Overrides #_include_layout? so that #render with no arguments # 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 # doesn't use the layout for api requests
def _include_layout?(*args) def pick_layout(*args)
api_request? ? false : super api_request? ? nil : super
end end
end end
+9 -48
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -16,12 +16,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AttachmentsController < ApplicationController class AttachmentsController < ApplicationController
before_filter :find_project, :except => :upload before_filter :find_project
before_filter :file_readable, :read_authorize, :only => [:show, :download, :thumbnail] before_filter :file_readable, :read_authorize, :except => :destroy
before_filter :delete_authorize, :only => :destroy before_filter :delete_authorize, :only => :destroy
before_filter :authorize_global, :only => :upload
accept_api_auth :show, :download, :upload accept_api_auth :show, :download
def show def show
respond_to do |format| respond_to do |format|
@@ -52,58 +51,20 @@ class AttachmentsController < ApplicationController
@attachment.increment_download @attachment.increment_download
end end
if stale?(:etag => @attachment.digest)
# images are sent inline # images are sent inline
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename), send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
:type => detect_content_type(@attachment), :type => detect_content_type(@attachment),
:disposition => (@attachment.image? ? 'inline' : 'attachment') :disposition => (@attachment.image? ? 'inline' : 'attachment')
end
end
def thumbnail
if @attachment.thumbnailable? && thumbnail = @attachment.thumbnail(:size => params[:size])
if stale?(:etag => thumbnail)
send_file thumbnail,
:filename => filename_for_content_disposition(@attachment.filename),
:type => detect_content_type(@attachment),
:disposition => 'inline'
end
else
# No thumbnail for the attachment or thumbnail could not be created
render :nothing => true, :status => 404
end
end
def upload
# Make sure that API users get used to set this content type
# as it won't trigger Rails' automatic parsing of the request body for parameters
unless request.content_type == 'application/octet-stream'
render :nothing => true, :status => 406
return
end
@attachment = Attachment.new(:file => request.raw_post)
@attachment.author = User.current
@attachment.filename = Redmine::Utils.random_hex(16)
if @attachment.save
respond_to do |format|
format.api { render :action => 'upload', :status => :created }
end
else
respond_to do |format|
format.api { render_validation_errors(@attachment) }
end
end
end end
verify :method => :delete, :only => :destroy
def destroy def destroy
if @attachment.container.respond_to?(:init_journal)
@attachment.container.init_journal(User.current)
end
# Make sure association callbacks are called # Make sure association callbacks are called
@attachment.container.attachments.delete(@attachment) @attachment.container.attachments.delete(@attachment)
redirect_to_referer_or project_path(@project) redirect_to :back
rescue ::ActionController::RedirectBackError
redirect_to :controller => 'projects', :action => 'show', :id => @project
end end
private private
+23 -12
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,31 +17,36 @@
class AuthSourcesController < ApplicationController class AuthSourcesController < ApplicationController
layout 'admin' layout 'admin'
menu_item :ldap_authentication
before_filter :require_admin before_filter :require_admin
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :template => :index }
def index def index
@auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 10 @auth_source_pages, @auth_sources = paginate auth_source_class.name.tableize, :per_page => 10
render "auth_sources/index"
end end
def new def new
klass_name = params[:type] || 'AuthSourceLdap' @auth_source = auth_source_class.new
@auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source]) render 'auth_sources/new'
end end
def create def create
@auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source]) @auth_source = auth_source_class.new(params[:auth_source])
if @auth_source.save if @auth_source.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index' redirect_to :action => 'index'
else else
render :action => 'new' render 'auth_sources/new'
end end
end end
def edit def edit
@auth_source = AuthSource.find(params[:id]) @auth_source = AuthSource.find(params[:id])
render 'auth_sources/edit'
end end
def update def update
@@ -50,17 +55,17 @@ class AuthSourcesController < ApplicationController
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index' redirect_to :action => 'index'
else else
render :action => 'edit' render 'auth_sources/edit'
end end
end end
def test_connection def test_connection
@auth_source = AuthSource.find(params[:id]) @auth_method = AuthSource.find(params[:id])
begin begin
@auth_source.test_connection @auth_method.test_connection
flash[:notice] = l(:notice_successful_connection) flash[:notice] = l(:notice_successful_connection)
rescue Exception => e rescue => text
flash[:error] = l(:error_unable_to_connect, e.message) flash[:error] = l(:error_unable_to_connect, text.message)
end end
redirect_to :action => 'index' redirect_to :action => 'index'
end end
@@ -73,4 +78,10 @@ class AuthSourcesController < ApplicationController
end end
redirect_to :action => 'index' redirect_to :action => 'index'
end end
protected
def auth_source_class
AuthSource
end
end end
+9 -26
View File
@@ -1,44 +1,27 @@
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AutoCompletesController < ApplicationController class AutoCompletesController < ApplicationController
before_filter :find_project before_filter :find_project
def issues def issues
@issues = [] @issues = []
q = (params[:q] || params[:term]).to_s.strip q = params[:q].to_s
if q.present? query = (params[:scope] == "all" && Setting.cross_project_issue_relations?) ? Issue : @project.issues
scope = (params[:scope] == "all" || @project.nil? ? Issue : @project.issues).visible
if q.match(/^\d+$/) if q.match(/^\d+$/)
@issues << scope.find_by_id(q.to_i) @issues << query.visible.find_by_id(q.to_i)
end
unless q.blank?
@issues += query.visible.find(:all, :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%"], :limit => 10)
end end
@issues += scope.where("LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%").order("#{Issue.table_name}.id DESC").limit(10).all
@issues.compact! @issues.compact!
end
render :layout => false render :layout => false
end end
private private
def find_project def find_project
if params[:project_id].present? project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
@project = Project.find(params[:project_id]) @project = Project.find(project_id)
end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
end end
+21 -21
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,15 +17,18 @@
class BoardsController < ApplicationController class BoardsController < ApplicationController
default_search_scope :messages default_search_scope :messages
before_filter :find_project_by_project_id, :find_board_if_available, :authorize before_filter :find_project, :find_board_if_available, :authorize
accept_rss_auth :index, :show accept_rss_auth :index, :show
helper :messages
include MessagesHelper
helper :sort helper :sort
include SortHelper include SortHelper
helper :watchers helper :watchers
include WatchersHelper
def index def index
@boards = @project.boards.includes(:last_message => :author).all @boards = @project.boards
# show the board if there is only one # show the board if there is only one
if @boards.size == 1 if @boards.size == 1
@board = @boards.first @board = @boards.first
@@ -43,11 +46,11 @@ class BoardsController < ApplicationController
@topic_count = @board.topics.count @topic_count = @board.topics.count
@topic_pages = Paginator.new self, @topic_count, per_page_option, params['page'] @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
@topics = @board.topics.reorder("#{Message.table_name}.sticky DESC").order(sort_clause).all( @topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '),
:include => [:author, {:last_reply => :author}], :include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page, :limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset) :offset => @topic_pages.current.offset
@message = Message.new(:board => @board) @message = Message.new
render :action => 'show', :layout => !request.xhr? render :action => 'show', :layout => !request.xhr?
} }
format.atom { format.atom {
@@ -59,31 +62,22 @@ class BoardsController < ApplicationController
end end
end end
def new verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
@board = @project.boards.build
@board.safe_attributes = params[:board]
end
def create def new
@board = @project.boards.build @board = Board.new
@board.safe_attributes = params[:board] @board.safe_attributes = params[:board]
if @board.save @board.project = @project
if request.post? && @board.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to_settings_in_projects redirect_to_settings_in_projects
else
render :action => 'new'
end end
end end
def edit def edit
end
def update
@board.safe_attributes = params[:board] @board.safe_attributes = params[:board]
if @board.save if request.post? && @board.save
redirect_to_settings_in_projects redirect_to_settings_in_projects
else
render :action => 'edit'
end end
end end
@@ -97,6 +91,12 @@ private
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards' redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end end
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
def find_board_if_available def find_board_if_available
@board = @project.boards.find(params[:id]) if params[:id] @board = @project.boards.find(params[:id]) if params[:id]
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+3 -19
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CommentsController < ApplicationController class CommentsController < ApplicationController
default_search_scope :news default_search_scope :news
model_object News model_object News
@@ -22,9 +5,8 @@ class CommentsController < ApplicationController
before_filter :find_project_from_association before_filter :find_project_from_association
before_filter :authorize before_filter :authorize
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
raise Unauthorized unless @news.commentable?
@comment = Comment.new @comment = Comment.new
@comment.safe_attributes = params[:comment] @comment.safe_attributes = params[:comment]
@comment.author = User.current @comment.author = User.current
@@ -35,6 +17,7 @@ class CommentsController < ApplicationController
redirect_to :controller => 'news', :action => 'show', :id => @news redirect_to :controller => 'news', :action => 'show', :id => @news
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
@news.comments.find(params[:comment_id]).destroy @news.comments.find(params[:comment_id]).destroy
redirect_to :controller => 'news', :action => 'show', :id => @news redirect_to :controller => 'news', :action => 'show', :id => @news
@@ -50,4 +33,5 @@ class CommentsController < ApplicationController
@comment = nil @comment = nil
@news @news
end end
end end
+11 -37
View File
@@ -1,32 +1,20 @@
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ContextMenusController < ApplicationController class ContextMenusController < ApplicationController
helper :watchers helper :watchers
helper :issues helper :issues
def issues def issues
@issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project) @issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project)
if (@issues.size == 1) if (@issues.size == 1)
@issue = @issues.first @issue = @issues.first
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
else
@allowed_statuses = @issues.map do |i|
i.new_statuses_allowed_to(User.current)
end.inject do |memo,s|
memo & s
end
end end
@issue_ids = @issues.map(&:id).sort
@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
@projects = @issues.collect(&:project).compact.uniq @projects = @issues.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1 @project = @projects.first if @projects.size == 1
@@ -46,28 +34,14 @@ class ContextMenusController < ApplicationController
@trackers = @project.trackers @trackers = @project.trackers
else else
#when multiple projects, we only keep the intersection of each set #when multiple projects, we only keep the intersection of each set
@assignables = @projects.map(&:assignable_users).reduce(:&) @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
@trackers = @projects.map(&:trackers).reduce(:&) @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
end end
@versions = @projects.map {|p| p.shared_versions.open}.reduce(:&)
@priorities = IssuePriority.active.reverse @priorities = IssuePriority.active.reverse
@statuses = IssueStatus.find(:all, :order => 'position')
@back = back_url @back = back_url
@options_by_custom_field = {}
if @can[:edit]
custom_fields = @issues.map(&:available_custom_fields).reduce(:&).select do |f|
%w(bool list user version).include?(f.field_format) && !f.multiple?
end
custom_fields.each do |field|
values = field.possible_values_options(@projects)
if values.any?
@options_by_custom_field[field] = values
end
end
end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
render :layout => false render :layout => false
end end
+12 -26
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,8 +19,6 @@ class CustomFieldsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin
before_filter :build_new_custom_field, :only => [:new, :create]
before_filter :find_custom_field, :only => [:edit, :update, :destroy]
def index def index
@custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name } @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
@@ -28,51 +26,39 @@ class CustomFieldsController < ApplicationController
end end
def new def new
@custom_field = begin
if params[:type].to_s.match(/.+CustomField$/)
params[:type].to_s.constantize.new(params[:custom_field])
end end
rescue
end
(redirect_to(:action => 'index'); return) unless @custom_field.is_a?(CustomField)
def create
if request.post? and @custom_field.save if request.post? and @custom_field.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field) call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
redirect_to :action => 'index', :tab => @custom_field.class.name redirect_to :action => 'index', :tab => @custom_field.class.name
else else
render :action => 'new' @trackers = Tracker.find(:all, :order => 'position')
end end
end end
def edit def edit
end @custom_field = CustomField.find(params[:id])
if request.post? and @custom_field.update_attributes(params[:custom_field])
def update
if request.put? and @custom_field.update_attributes(params[:custom_field])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field) call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
redirect_to :action => 'index', :tab => @custom_field.class.name redirect_to :action => 'index', :tab => @custom_field.class.name
else else
render :action => 'edit' @trackers = Tracker.find(:all, :order => 'position')
end end
end end
def destroy def destroy
@custom_field.destroy @custom_field = CustomField.find(params[:id]).destroy
redirect_to :action => 'index', :tab => @custom_field.class.name redirect_to :action => 'index', :tab => @custom_field.class.name
rescue rescue
flash[:error] = l(:error_can_not_delete_custom_field) flash[:error] = l(:error_can_not_delete_custom_field)
redirect_to :action => 'index' redirect_to :action => 'index'
end end
private
def build_new_custom_field
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
if @custom_field.nil?
render_404
end
end
def find_custom_field
@custom_field = CustomField.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+17 -23
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,9 +18,9 @@
class DocumentsController < ApplicationController class DocumentsController < ApplicationController
default_search_scope :documents default_search_scope :documents
model_object Document model_object Document
before_filter :find_project_by_project_id, :only => [:index, :new, :create] before_filter :find_project, :only => [:index, :new]
before_filter :find_model_object, :except => [:index, :new, :create] before_filter :find_model_object, :except => [:index, :new]
before_filter :find_project_from_association, :except => [:index, :new, :create] before_filter :find_project_from_association, :except => [:index, :new]
before_filter :authorize before_filter :authorize
helper :attachments helper :attachments
@@ -49,36 +49,25 @@ class DocumentsController < ApplicationController
def new def new
@document = @project.documents.build @document = @project.documents.build
@document.safe_attributes = params[:document] @document.safe_attributes = params[:document]
end if request.post? and @document.save
attachments = Attachment.attach_files(@document, params[:attachments])
def create
@document = @project.documents.build
@document.safe_attributes = params[:document]
@document.save_attachments(params[:attachments])
if @document.save
render_attachment_warning_if_needed(@document) render_attachment_warning_if_needed(@document)
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index', :project_id => @project redirect_to :action => 'index', :project_id => @project
else
render :action => 'new'
end end
end end
def edit def edit
end @categories = DocumentCategory.active #TODO: use it in the views
def update
@document.safe_attributes = params[:document] @document.safe_attributes = params[:document]
if request.put? and @document.save if request.post? and @document.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @document redirect_to :action => 'show', :id => @document
else
render :action => 'edit'
end end
end end
def destroy def destroy
@document.destroy if request.delete? @document.destroy
redirect_to :controller => 'documents', :action => 'index', :project_id => @project redirect_to :controller => 'documents', :action => 'index', :project_id => @project
end end
@@ -86,9 +75,14 @@ class DocumentsController < ApplicationController
attachments = Attachment.attach_files(@document, params[:attachments]) attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document) render_attachment_warning_if_needed(@document)
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added') Mailer.deliver_attachments_added(attachments[:files]) if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
Mailer.attachments_added(attachments[:files]).deliver
end
redirect_to :action => 'show', :id => @document redirect_to :action => 'show', :id => @document
end end
private
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+22 -22
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,19 +19,28 @@ class EnumerationsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin
before_filter :build_new_enumeration, :only => [:new, :create]
before_filter :find_enumeration, :only => [:edit, :update, :destroy]
helper :custom_fields helper :custom_fields
include CustomFieldsHelper
def index def index
end end
verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :action => :index }
def new def new
begin
@enumeration = params[:type].constantize.new
rescue NameError
@enumeration = Enumeration.new
end
end end
def create def create
if request.post? && @enumeration.save @enumeration = Enumeration.new(params[:enumeration])
@enumeration.type = params[:enumeration][:type]
if @enumeration.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index', :type => @enumeration.type redirect_to :action => 'index', :type => @enumeration.type
else else
@@ -40,10 +49,13 @@ class EnumerationsController < ApplicationController
end end
def edit def edit
@enumeration = Enumeration.find(params[:id])
end end
def update def update
if request.put? && @enumeration.update_attributes(params[:enumeration]) @enumeration = Enumeration.find(params[:id])
@enumeration.type = params[:enumeration][:type] if params[:enumeration][:type]
if @enumeration.update_attributes(params[:enumeration])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index', :type => @enumeration.type redirect_to :action => 'index', :type => @enumeration.type
else else
@@ -52,6 +64,7 @@ class EnumerationsController < ApplicationController
end end
def destroy def destroy
@enumeration = Enumeration.find(params[:id])
if !@enumeration.in_use? if !@enumeration.in_use?
# No associated objects # No associated objects
@enumeration.destroy @enumeration.destroy
@@ -64,22 +77,9 @@ class EnumerationsController < ApplicationController
return return
end end
end end
@enumerations = @enumeration.class.all - [@enumeration] @enumerations = @enumeration.class.find(:all) - [@enumeration]
end #rescue
# flash[:error] = 'Unable to delete enumeration'
private # redirect_to :action => 'index'
def build_new_enumeration
class_name = params[:enumeration] && params[:enumeration][:type] || params[:type]
@enumeration = Enumeration.new_subclass_instance(class_name, params[:enumeration])
if @enumeration.nil?
render_404
end
end
def find_enumeration
@enumeration = Enumeration.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end end
end end
+1 -18
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class FilesController < ApplicationController class FilesController < ApplicationController
menu_item :files menu_item :files
@@ -46,7 +29,7 @@ class FilesController < ApplicationController
render_attachment_warning_if_needed(container) render_attachment_warning_if_needed(container)
if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added') if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
Mailer.attachments_added(attachments[:files]).deliver Mailer.deliver_attachments_added(attachments[:files])
end end
redirect_to project_files_path(@project) redirect_to project_files_path(@project)
end end
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+68 -36
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,34 +19,51 @@ class GroupsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin
before_filter :find_group, :except => [:index, :new, :create]
accept_api_auth :index, :show, :create, :update, :destroy, :add_users, :remove_user
helper :custom_fields helper :custom_fields
# GET /groups
# GET /groups.xml
def index def index
@groups = Group.sorted.all @groups = Group.find(:all, :order => 'lastname')
respond_to do |format| respond_to do |format|
format.html format.html # index.html.erb
format.api format.xml { render :xml => @groups }
end end
end end
# GET /groups/1
# GET /groups/1.xml
def show def show
@group = Group.find(params[:id])
respond_to do |format| respond_to do |format|
format.html format.html # show.html.erb
format.api format.xml { render :xml => @group }
end end
end end
# GET /groups/new
# GET /groups/new.xml
def new def new
@group = Group.new @group = Group.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @group }
end
end end
# GET /groups/1/edit
def edit
@group = Group.find(params[:id], :include => :projects)
end
# POST /groups
# POST /groups.xml
def create def create
@group = Group.new @group = Group.new(params[:group])
@group.safe_attributes = params[:group]
respond_to do |format| respond_to do |format|
if @group.save if @group.save
@@ -54,87 +71,102 @@ class GroupsController < ApplicationController
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to(params[:continue] ? new_group_path : groups_path) redirect_to(params[:continue] ? new_group_path : groups_path)
} }
format.api { render :action => 'show', :status => :created, :location => group_url(@group) } format.xml { render :xml => @group, :status => :created, :location => @group }
else else
format.html { render :action => "new" } format.html { render :action => "new" }
format.api { render_validation_errors(@group) } format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
end end
end end
end end
def edit # PUT /groups/1
end # PUT /groups/1.xml
def update def update
@group.safe_attributes = params[:group] @group = Group.find(params[:id])
respond_to do |format| respond_to do |format|
if @group.save if @group.update_attributes(params[:group])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
format.html { redirect_to(groups_path) } format.html { redirect_to(groups_path) }
format.api { render_api_ok } format.xml { head :ok }
else else
format.html { render :action => "edit" } format.html { render :action => "edit" }
format.api { render_validation_errors(@group) } format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
end end
end end
end end
# DELETE /groups/1
# DELETE /groups/1.xml
def destroy def destroy
@group = Group.find(params[:id])
@group.destroy @group.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to(groups_url) } format.html { redirect_to(groups_url) }
format.api { render_api_ok } format.xml { head :ok }
end end
end end
def add_users def add_users
@users = User.find_all_by_id(params[:user_id] || params[:user_ids]) @group = Group.find(params[:id])
@group.users << @users if request.post? users = User.find_all_by_id(params[:user_ids])
@group.users << users if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js format.js {
format.api { render_api_ok } render(:update) {|page|
page.replace_html "tab-content-users", :partial => 'groups/users'
users.each {|user| page.visual_effect(:highlight, "user-#{user.id}") }
}
}
end end
end end
def remove_user def remove_user
@group = Group.find(params[:id])
@group.users.delete(User.find(params[:user_id])) if request.delete? @group.users.delete(User.find(params[:user_id])) if request.delete?
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js format.js { render(:update) {|page| page.replace_html "tab-content-users", :partial => 'groups/users'} }
format.api { render_api_ok }
end end
end end
def autocomplete_for_user def autocomplete_for_user
@group = Group.find(params[:id])
@users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100) @users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100)
render :layout => false render :layout => false
end end
def edit_membership def edit_membership
@group = Group.find(params[:id])
@membership = Member.edit_membership(params[:membership_id], params[:membership], @group) @membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
@membership.save if request.post? @membership.save if request.post?
respond_to do |format| respond_to do |format|
if @membership.valid?
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
format.js format.js {
render(:update) {|page|
page.replace_html "tab-content-memberships", :partial => 'groups/memberships'
page.visual_effect(:highlight, "member-#{@membership.id}")
}
}
else
format.js {
render(:update) {|page|
page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
}
}
end
end end
end end
def destroy_membership def destroy_membership
@group = Group.find(params[:id])
Member.find(params[:membership_id]).destroy if request.post? Member.find(params[:membership_id]).destroy if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
format.js format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'groups/memberships'} }
end end
end end
private
def find_group
@group = Group.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+22 -11
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,7 +20,7 @@ class IssueCategoriesController < ApplicationController
model_object IssueCategory model_object IssueCategory
before_filter :find_model_object, :except => [:index, :new, :create] before_filter :find_model_object, :except => [:index, :new, :create]
before_filter :find_project_from_association, :except => [:index, :new, :create] before_filter :find_project_from_association, :except => [:index, :new, :create]
before_filter :find_project_by_project_id, :only => [:index, :new, :create] before_filter :find_project, :only => [:index, :new, :create]
before_filter :authorize before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy accept_api_auth :index, :show, :create, :update, :destroy
@@ -41,13 +41,9 @@ class IssueCategoriesController < ApplicationController
def new def new
@category = @project.issue_categories.build @category = @project.issue_categories.build
@category.safe_attributes = params[:issue_category] @category.safe_attributes = params[:issue_category]
respond_to do |format|
format.html
format.js
end
end end
verify :method => :post, :only => :create
def create def create
@category = @project.issue_categories.build @category = @project.issue_categories.build
@category.safe_attributes = params[:issue_category] @category.safe_attributes = params[:issue_category]
@@ -57,13 +53,20 @@ class IssueCategoriesController < ApplicationController
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
end end
format.js format.js do
# IE doesn't support the replace_html rjs method for select box options
render(:update) {|page| page.replace "issue_category_id",
content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
}
end
format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) } format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) }
end end
else else
respond_to do |format| respond_to do |format|
format.html { render :action => 'new'} format.html { render :action => 'new'}
format.js { render :action => 'new'} format.js do
render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
end
format.api { render_validation_errors(@category) } format.api { render_validation_errors(@category) }
end end
end end
@@ -72,6 +75,7 @@ class IssueCategoriesController < ApplicationController
def edit def edit
end end
verify :method => :put, :only => :update
def update def update
@category.safe_attributes = params[:issue_category] @category.safe_attributes = params[:issue_category]
if @category.save if @category.save
@@ -80,7 +84,7 @@ class IssueCategoriesController < ApplicationController
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
} }
format.api { render_api_ok } format.api { head :ok }
end end
else else
respond_to do |format| respond_to do |format|
@@ -90,6 +94,7 @@ class IssueCategoriesController < ApplicationController
end end
end end
verify :method => :delete, :only => :destroy
def destroy def destroy
@issue_count = @category.issues.size @issue_count = @category.issues.size
if @issue_count == 0 || params[:todo] || api_request? if @issue_count == 0 || params[:todo] || api_request?
@@ -100,7 +105,7 @@ class IssueCategoriesController < ApplicationController
@category.destroy(reassign_to) @category.destroy(reassign_to)
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' } format.html { redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' }
format.api { render_api_ok } format.api { head :ok }
end end
return return
end end
@@ -114,4 +119,10 @@ private
super super
@category = @object @category = @object
end end
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+87
View File
@@ -0,0 +1,87 @@
# 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueMovesController < ApplicationController
menu_item :issues
default_search_scope :issues
before_filter :find_issues, :check_project_uniqueness
before_filter :authorize
def new
prepare_for_issue_move
render :layout => false if request.xhr?
end
def create
prepare_for_issue_move
if request.post?
new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
unsaved_issue_ids = []
moved_issues = []
@issues.each do |issue|
issue.reload
issue.init_journal(User.current)
issue.current_journal.notes = @notes if @notes.present?
call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy })
if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params)})
moved_issues << r
else
unsaved_issue_ids << issue.id
end
end
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
if params[:follow]
if @issues.size == 1 && moved_issues.size == 1
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
else
redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
end
else
redirect_to :controller => 'issues', :action => 'index', :project_id => @project
end
return
end
end
private
def prepare_for_issue_move
@issues.sort!
@copy = params[:copy_options] && params[:copy_options][:copy]
@allowed_projects = Issue.allowed_target_projects_on_move
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
@target_project ||= @project
@trackers = @target_project.trackers
@available_statuses = Workflow.available_statuses(@project)
@notes = params[:notes]
@notes ||= ''
end
def extract_changed_attributes_for_move(params)
changed_attributes = {}
[:assigned_to_id, :status_id, :start_date, :due_date, :priority_id].each do |valid_attribute|
unless params[valid_attribute].blank?
changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
end
end
changed_attributes
end
end
+16 -7
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -39,19 +39,27 @@ class IssueRelationsController < ApplicationController
end end
end end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
@relation = IssueRelation.new(params[:relation]) @relation = IssueRelation.new(params[:relation])
@relation.issue_from = @issue @relation.issue_from = @issue
if params[:relation] && m = params[:relation][:issue_to_id].to_s.strip.match(/^#?(\d+)$/) if params[:relation] && m = params[:relation][:issue_to_id].to_s.match(/^#?(\d+)$/)
@relation.issue_to = Issue.visible.find_by_id(m[1].to_i) @relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
end end
saved = @relation.save saved = @relation.save
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js { format.js do
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } @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?
page << "$('relation_delay').value = ''"
page << "$('relation_issue_to_id').value = ''"
end
end
end
format.api { format.api {
if saved if saved
render :action => 'show', :status => :created, :location => relation_url(@relation) render :action => 'show', :status => :created, :location => relation_url(@relation)
@@ -62,14 +70,15 @@ class IssueRelationsController < ApplicationController
end end
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
raise Unauthorized unless @relation.deletable? raise Unauthorized unless @relation.deletable?
@relation.destroy @relation.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to issue_path } # TODO : does this really work since @issue is always nil? What is it useful to? format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js format.js { render(:update) {|page| page.remove "relation-#{@relation.id}"} }
format.api { render_api_ok } format.api { head :ok }
end end
end end
+2 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -62,6 +62,7 @@ class IssueStatusesController < ApplicationController
end end
end end
verify :method => :delete, :only => :destroy, :redirect_to => { :action => :index }
def destroy def destroy
IssueStatus.find(params[:id]).destroy IssueStatus.find(params[:id]).destroy
redirect_to :action => 'index' redirect_to :action => 'index'
+48 -145
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,7 +20,8 @@ class IssuesController < ApplicationController
default_search_scope :issues default_search_scope :issues
before_filter :find_issue, :only => [:show, :edit, :update] before_filter :find_issue, :only => [:show, :edit, :update]
before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy] before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
before_filter :check_project_uniqueness, :only => [:move, :perform_move]
before_filter :find_project, :only => [:new, :create] before_filter :find_project, :only => [:new, :create]
before_filter :authorize, :except => [:index] before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => [:index] before_filter :find_optional_project, :only => [:index]
@@ -50,8 +51,17 @@ class IssuesController < ApplicationController
include SortHelper include SortHelper
include IssuesHelper include IssuesHelper
helper :timelog helper :timelog
helper :gantt
include Redmine::Export::PDF include Redmine::Export::PDF
verify :method => [:post, :delete],
:only => :destroy,
:render => { :nothing => true, :status => :method_not_allowed }
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def index def index
retrieve_query retrieve_query
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
@@ -103,8 +113,10 @@ class IssuesController < ApplicationController
@journals.each_with_index {|j,i| j.indice = i+1} @journals.each_with_index {|j,i| j.indice = i+1}
@journals.reverse! if User.current.wants_comments_in_reverse_order? @journals.reverse! if User.current.wants_comments_in_reverse_order?
if User.current.allowed_to?(:view_changesets, @project)
@changesets = @issue.changesets.visible.all @changesets = @issue.changesets.visible.all
@changesets.reverse! if User.current.wants_comments_in_reverse_order? @changesets.reverse! if User.current.wants_comments_in_reverse_order?
end
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
@allowed_statuses = @issue.new_statuses_allowed_to(User.current) @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@@ -112,10 +124,7 @@ class IssuesController < ApplicationController
@priorities = IssuePriority.active @priorities = IssuePriority.active
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
respond_to do |format| respond_to do |format|
format.html { format.html { render :template => 'issues/show' }
retrieve_previous_and_next_issue_ids
render :template => 'issues/show'
}
format.api format.api
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' } 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") } format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
@@ -127,20 +136,20 @@ class IssuesController < ApplicationController
def new def new
respond_to do |format| respond_to do |format|
format.html { render :action => 'new', :layout => !request.xhr? } format.html { render :action => 'new', :layout => !request.xhr? }
format.js { render :partial => 'update_form' } format.js { render :partial => 'attributes' }
end end
end end
def create def create
call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue }) call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
if @issue.save if @issue.save
attachments = Attachment.attach_files(@issue, params[:attachments])
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue}) call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
respond_to do |format| respond_to do |format|
format.html { format.html {
render_attachment_warning_if_needed(@issue) render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject)) flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
redirect_to(params[:continue] ? { :action => 'new', :project_id => @issue.project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } : 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 }) { :action => 'show', :id => @issue })
} }
format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) } format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
@@ -155,7 +164,9 @@ class IssuesController < ApplicationController
end end
def edit def edit
return unless update_issue_from_params update_issue_from_params
@journal = @issue.current_journal
respond_to do |format| respond_to do |format|
format.html { } format.html { }
@@ -164,27 +175,21 @@ class IssuesController < ApplicationController
end end
def update def update
return unless update_issue_from_params update_issue_from_params
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
saved = false
begin
saved = @issue.save_issue_with_child_records(params, @time_entry)
rescue ActiveRecord::StaleObjectError
@conflict = true
if params[:last_journal_id]
@conflict_journals = @issue.journals_after(params[:last_journal_id]).all
end
end
if saved if @issue.save_issue_with_child_records(params, @time_entry)
render_attachment_warning_if_needed(@issue) render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default({:action => 'show', :id => @issue}) } format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
format.api { render_api_ok } format.api { head :ok }
end end
else else
render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
@journal = @issue.current_journal
respond_to do |format| respond_to do |format|
format.html { render :action => 'edit' } format.html { render :action => 'edit' }
format.api { render_validation_errors(@issue) } format.api { render_validation_errors(@issue) }
@@ -192,86 +197,33 @@ class IssuesController < ApplicationController
end end
end end
# Bulk edit/copy a set of issues # Bulk edit a set of issues
def bulk_edit def bulk_edit
@issues.sort! @issues.sort!
@copy = params[:copy].present? @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
@notes = params[:notes] @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
@assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
if User.current.allowed_to?(:move_issues, @projects) @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
@allowed_projects = Issue.allowed_target_projects_on_move
if params[:issue]
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
if @target_project
target_projects = [@target_project]
end
end
end
target_projects ||= @projects
if @copy
@available_statuses = [IssueStatus.default]
else
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
end
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
@assignables = target_projects.map(&:assignable_users).reduce(:&)
@trackers = target_projects.map(&:trackers).reduce(:&)
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
@categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
if @copy
@attachments_present = @issues.detect {|i| i.attachments.any?}.present?
@subtasks_present = @issues.detect {|i| !i.leaf?}.present?
end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
render :layout => false if request.xhr?
end end
def bulk_update def bulk_update
@issues.sort! @issues.sort!
@copy = params[:copy].present?
attributes = parse_params_for_bulk_issue_attributes(params) attributes = parse_params_for_bulk_issue_attributes(params)
unsaved_issue_ids = [] unsaved_issue_ids = []
moved_issues = []
if @copy && params[:copy_subtasks].present?
# Descendant issues will be copied with the parent task
# Don't copy them twice
@issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
end
@issues.each do |issue| @issues.each do |issue|
issue.reload issue.reload
if @copy
issue = issue.copy({},
:attachments => params[:copy_attachments].present?,
:subtasks => params[:copy_subtasks].present?
)
end
journal = issue.init_journal(User.current, params[:notes]) journal = issue.init_journal(User.current, params[:notes])
issue.safe_attributes = attributes issue.safe_attributes = attributes
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
if issue.save unless issue.save
moved_issues << issue
else
# Keep unsaved issue ids to display them in flash error # Keep unsaved issue ids to display them in flash error
unsaved_issue_ids << issue.id unsaved_issue_ids << issue.id
end end
end end
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
if params[:follow]
if @issues.size == 1 && moved_issues.size == 1
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
elsif moved_issues.map(&:project).uniq.size == 1
redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first
end
else
redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project}) redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
end end
end
def destroy def destroy
@hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
@@ -303,7 +255,7 @@ class IssuesController < ApplicationController
end end
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default(:action => 'index', :project_id => @project) } format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
format.api { render_api_ok } format.api { head :ok }
end end
end end
@@ -322,58 +274,25 @@ private
end end
def find_project def find_project
project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id]) project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
@project = Project.find(project_id) @project = Project.find(project_id)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def retrieve_previous_and_next_issue_ids
retrieve_query_from_session
if @query
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns, 'issues_index_sort')
limit = 500
issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
if (idx = issue_ids.index(@issue.id)) && idx < limit
if issue_ids.size < 500
@issue_position = idx + 1
@issue_count = issue_ids.size
end
@prev_issue_id = issue_ids[idx - 1] if idx > 0
@next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
end
end
end
# Used by #edit and #update to set some common instance variables # Used by #edit and #update to set some common instance variables
# from the params # from the params
# TODO: Refactor, not everything in here is needed by #edit # TODO: Refactor, not everything in here is needed by #edit
def update_issue_from_params def update_issue_from_params
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@priorities = IssuePriority.active
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@time_entry.attributes = params[:time_entry] @time_entry.attributes = params[:time_entry]
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil) @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
@issue.init_journal(User.current, @notes) @issue.init_journal(User.current, @notes)
@issue.safe_attributes = params[:issue]
issue_attributes = params[:issue]
if issue_attributes && params[:conflict_resolution]
case params[:conflict_resolution]
when 'overwrite'
issue_attributes = issue_attributes.dup
issue_attributes.delete(:lock_version)
when 'add_notes'
issue_attributes = {}
when 'cancel'
redirect_to issue_path(@issue)
return false
end
end
@issue.safe_attributes = issue_attributes
@priorities = IssuePriority.active
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
true
end end
# TODO: Refactor, lots of extra code in here # TODO: Refactor, lots of extra code in here
@@ -381,24 +300,14 @@ private
def build_new_issue_from_params def build_new_issue_from_params
if params[:id].blank? if params[:id].blank?
@issue = Issue.new @issue = Issue.new
if params[:copy_from] @issue.copy_from(params[:copy_from]) if params[:copy_from]
begin
@copy_from = Issue.visible.find(params[:copy_from])
@copy_attachments = params[:copy_attachments].present? || request.get?
@copy_subtasks = params[:copy_subtasks].present? || request.get?
@issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks)
rescue ActiveRecord::RecordNotFound
render_404
return
end
end
@issue.project = @project @issue.project = @project
else else
@issue = @project.issues.visible.find(params[:id]) @issue = @project.issues.visible.find(params[:id])
end end
@issue.project = @project @issue.project = @project
@issue.author ||= User.current @issue.author = User.current
# Tracker must be set before custom field values # Tracker must be set before custom field values
@issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first) @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
if @issue.tracker.nil? if @issue.tracker.nil?
@@ -406,11 +315,14 @@ private
return false return false
end end
@issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date? @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
if params[:issue].is_a?(Hash)
@issue.safe_attributes = params[:issue] @issue.safe_attributes = params[:issue]
if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
@issue.watcher_user_ids = params[:issue]['watcher_user_ids']
end
end
@priorities = IssuePriority.active @priorities = IssuePriority.active
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, true) @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
@available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
end end
def check_for_default_issue_status def check_for_default_issue_status
@@ -423,16 +335,7 @@ private
def parse_params_for_bulk_issue_attributes(params) def parse_params_for_bulk_issue_attributes(params)
attributes = (params[:issue] || {}).reject {|k,v| v.blank?} attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
if custom = attributes[:custom_field_values] attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
custom.reject! {|k,v| v.blank?}
custom.keys.each do |k|
if custom[k].is_a?(Array)
custom[k] << '' if custom[k].delete('__none__')
else
custom[k] = '' if custom[k] == '__none__'
end
end
end
attributes attributes
end end
end end
+11 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -67,8 +67,16 @@ class JournalsController < ApplicationController
end end
# Replaces pre blocks with [...] # Replaces pre blocks with [...]
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]') text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
@content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> " content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
@content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
render(:update) { |page|
page.<< "$('notes').value = \"#{escape_javascript content}\";"
page.show 'update'
page << "Form.Element.focus('notes');"
page << "Element.scrollTo('update');"
page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
}
end end
def edit def edit
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -15,13 +15,11 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require File.expand_path('../../../../test_helper', __FILE__) class LdapAuthSourcesController < AuthSourcesController
class Redmine::InfoTest < ActiveSupport::TestCase protected
def test_environment
env = Redmine::Info.environment
assert_kind_of String, env def auth_source_class
assert_match 'Redmine version', env AuthSourceLdap
end end
end end
+5 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,6 +18,10 @@
class MailHandlerController < ActionController::Base class MailHandlerController < ActionController::Base
before_filter :check_credential before_filter :check_credential
verify :method => :post,
:only => :index,
:render => { :nothing => true, :status => 405 }
# Submits an incoming email to MailHandler # Submits an incoming email to MailHandler
def index def index
options = params.dup options = params.dup
+50 -65
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,102 +17,87 @@
class MembersController < ApplicationController class MembersController < ApplicationController
model_object Member model_object Member
before_filter :find_model_object, :except => [:index, :create, :autocomplete] before_filter :find_model_object, :except => [:new, :autocomplete_for_member]
before_filter :find_project_from_association, :except => [:index, :create, :autocomplete] before_filter :find_project_from_association, :except => [:new, :autocomplete_for_member]
before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete] before_filter :find_project, :only => [:new, :autocomplete_for_member]
before_filter :authorize before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
def index def new
@offset, @limit = api_offset_and_limit
@member_count = @project.member_principals.count
@member_pages = Paginator.new self, @member_count, @limit, params['page']
@offset ||= @member_pages.current.offset
@members = @project.member_principals.all(
:order => "#{Member.table_name}.id",
:limit => @limit,
:offset => @offset
)
respond_to do |format|
format.html { head 406 }
format.api
end
end
def show
respond_to do |format|
format.html { head 406 }
format.api
end
end
def create
members = [] members = []
if params[:membership] if params[:member] && request.post?
if params[:membership][:user_ids] attrs = params[:member].dup
attrs = params[:membership].dup if (user_ids = attrs.delete(:user_ids))
user_ids = attrs.delete(:user_ids)
user_ids.each do |user_id| user_ids.each do |user_id|
members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id) members << Member.new(:role_ids => params[:member][:role_ids], :user_id => user_id)
end end
else else
members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id]) members << Member.new(:role_ids => params[:member][:role_ids], :user_id => params[:member][:user_id])
end end
@project.members << members @project.members << members
end end
respond_to do |format| respond_to do |format|
if members.present? && members.all? {|m| m.valid? }
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project } format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js { @members = members }
format.api { format.js {
@member = members.first render(:update) {|page|
if @member.valid? page.replace_html "tab-content-members", :partial => 'projects/settings/members'
render :action => 'show', :status => :created, :location => membership_url(@member) page << 'hideOnLoad()'
else members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
render_validation_errors(@member)
end
} }
}
else
format.js {
render(:update) {|page|
errors = members.collect {|m|
m.errors.full_messages
}.flatten.uniq
page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', ')))
}
}
end
end end
end end
def update def edit
if params[:membership] if params[:member]
@member.role_ids = params[:membership][:role_ids] @member.role_ids = params[:member][:role_ids]
end end
saved = @member.save if request.post? and @member.save
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project } format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js format.js {
format.api { render(:update) {|page|
if saved page.replace_html "tab-content-members", :partial => 'projects/settings/members'
render_api_ok page << 'hideOnLoad()'
else page.visual_effect(:highlight, "member-#{@member.id}")
render_validation_errors(@member)
end
} }
}
end
end end
end end
def destroy def destroy
if request.delete? && @member.deletable? if request.post? && @member.deletable?
@member.destroy @member.destroy
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project } format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js format.js { render(:update) {|page|
format.api { page.replace_html "tab-content-members", :partial => 'projects/settings/members'
if @member.destroyed? page << 'hideOnLoad()'
render_api_ok }
else
head :unprocessable_entity
end
} }
end end
end end
def autocomplete def autocomplete_for_member
@principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100) @principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100)
render :layout => false render :layout => false
end end
end end
+26 -21
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -22,7 +22,9 @@ class MessagesController < ApplicationController
before_filter :find_message, :except => [:new, :preview] before_filter :find_message, :except => [:new, :preview]
before_filter :authorize, :except => [:preview, :edit, :destroy] before_filter :authorize, :except => [:preview, :edit, :destroy]
helper :boards verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
verify :xhr => true, :only => :quote
helper :watchers helper :watchers
helper :attachments helper :attachments
include AttachmentsHelper include AttachmentsHelper
@@ -55,13 +57,11 @@ class MessagesController < ApplicationController
@message.author = User.current @message.author = User.current
@message.board = @board @message.board = @board
@message.safe_attributes = params[:message] @message.safe_attributes = params[:message]
if request.post? if request.post? && @message.save
@message.save_attachments(params[:attachments])
if @message.save
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message}) call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
attachments = Attachment.attach_files(@message, params[:attachments])
render_attachment_warning_if_needed(@message) render_attachment_warning_if_needed(@message)
redirect_to board_message_path(@board, @message) redirect_to :action => 'show', :id => @message
end
end end
end end
@@ -77,7 +77,7 @@ class MessagesController < ApplicationController
attachments = Attachment.attach_files(@reply, params[:attachments]) attachments = Attachment.attach_files(@reply, params[:attachments])
render_attachment_warning_if_needed(@reply) render_attachment_warning_if_needed(@reply)
end end
redirect_to board_message_path(@board, @topic, :r => @reply) redirect_to :action => 'show', :id => @topic, :r => @reply
end end
# Edit a message # Edit a message
@@ -89,35 +89,40 @@ class MessagesController < ApplicationController
render_attachment_warning_if_needed(@message) render_attachment_warning_if_needed(@message)
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
@message.reload @message.reload
redirect_to board_message_path(@message.board, @message.root, :r => (@message.parent_id && @message.id)) redirect_to :action => 'show', :board_id => @message.board, :id => @message.root, :r => (@message.parent_id && @message.id)
end end
end end
# Delete a messages # Delete a messages
def destroy def destroy
(render_403; return false) unless @message.destroyable_by?(User.current) (render_403; return false) unless @message.destroyable_by?(User.current)
r = @message.to_param
@message.destroy @message.destroy
if @message.parent redirect_to @message.parent.nil? ?
redirect_to board_message_path(@board, @message.parent, :r => r) { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
else { :action => 'show', :id => @message.parent, :r => @message }
redirect_to project_board_path(@project, @board)
end
end end
def quote def quote
@subject = @message.subject user = @message.author
@subject = "RE: #{@subject}" unless @subject.starts_with?('RE:') text = @message.content
subject = @message.subject.gsub('"', '\"')
@content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> " subject = "RE: #{subject}" unless subject.starts_with?('RE:')
@content << @message.content.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
render(:update) { |page|
page << "$('message_subject').value = \"#{subject}\";"
page.<< "$('message_content').value = \"#{content}\";"
page.show 'reply'
page << "Form.Element.focus('message_content');"
page << "Element.scrollTo('reply');"
page << "$('message_content').scrollTop = $('message_content').scrollHeight - $('message_content').clientHeight;"
}
end end
def preview def preview
message = @board.messages.find_by_id(params[:id]) message = @board.messages.find_by_id(params[:id])
@attachements = message.attachments if message @attachements = message.attachments if message
@text = (params[:message] || params[:reply])[:content] @text = (params[:message] || params[:reply])[:content]
@previewed = message
render :partial => 'common/preview' render :partial => 'common/preview'
end end
+8 -28
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -35,6 +35,9 @@ class MyController < ApplicationController
'right' => ['issuesreportedbyme'] 'right' => ['issuesreportedbyme']
}.freeze }.freeze
verify :xhr => true,
:only => [:add_block, :remove_block, :order_blocks]
def index def index
page page
render :action => 'page' render :action => 'page'
@@ -65,24 +68,6 @@ class MyController < ApplicationController
end end
end end
# Destroys user's account
def destroy
@user = User.current
unless @user.own_account_deletable?
redirect_to :action => 'account'
return
end
if request.post? && params[:confirm]
@user.destroy
if @user.destroyed?
logout_user
flash[:notice] = l(:notice_account_deleted)
end
redirect_to home_path
end
end
# Manage user's password # Manage user's password
def password def password
@user = User.current @user = User.current
@@ -135,11 +120,7 @@ class MyController < ApplicationController
@user = User.current @user = User.current
@blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
@block_options = [] @block_options = []
BLOCKS.each do |k, v| BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
unless %w(top left right).detect {|f| (@blocks[f] ||= []).include?(k)}
@block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]
end
end
end end
# Add a block to user's page # Add a block to user's page
@@ -156,7 +137,7 @@ class MyController < ApplicationController
layout['top'].unshift block layout['top'].unshift block
@user.pref[:my_page_layout] = layout @user.pref[:my_page_layout] = layout
@user.pref.save @user.pref.save
redirect_to :action => 'page_layout' render :partial => "block", :locals => {:user => @user, :block_name => block}
end end
# Remove a block to user's page # Remove a block to user's page
@@ -169,7 +150,7 @@ class MyController < ApplicationController
%w(top left right).each {|f| (layout[f] ||= []).delete block } %w(top left right).each {|f| (layout[f] ||= []).delete block }
@user.pref[:my_page_layout] = layout @user.pref[:my_page_layout] = layout
@user.pref.save @user.pref.save
redirect_to :action => 'page_layout' render :nothing => true
end end
# Change blocks order on user's page # Change blocks order on user's page
@@ -179,8 +160,7 @@ class MyController < ApplicationController
group = params[:group] group = params[:group]
@user = User.current @user = User.current
if group.is_a?(String) if group.is_a?(String)
group_items = (params["blocks"] || []).collect(&:underscore) group_items = (params["list-#{group}"] || []).collect(&:underscore)
group_items.each {|s| s.sub!(/^block_/, '')}
if group_items and group_items.is_a? Array if group_items and group_items.is_a? Array
layout = @user.pref[:my_page_layout] || {} layout = @user.pref[:my_page_layout] || {}
# remove group blocks if they are presents in other groups # remove group blocks if they are presents in other groups
+10 -8
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,14 +20,13 @@ class NewsController < ApplicationController
model_object News model_object News
before_filter :find_model_object, :except => [:new, :create, :index] before_filter :find_model_object, :except => [:new, :create, :index]
before_filter :find_project_from_association, :except => [:new, :create, :index] before_filter :find_project_from_association, :except => [:new, :create, :index]
before_filter :find_project_by_project_id, :only => [:new, :create] before_filter :find_project, :only => [:new, :create]
before_filter :authorize, :except => [:index] before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => :index before_filter :find_optional_project, :only => :index
accept_rss_auth :index accept_rss_auth :index
accept_api_auth :index accept_api_auth :index
helper :watchers helper :watchers
helper :attachments
def index def index
case params[:format] case params[:format]
@@ -69,24 +68,22 @@ class NewsController < ApplicationController
def create def create
@news = News.new(:project => @project, :author => User.current) @news = News.new(:project => @project, :author => User.current)
@news.safe_attributes = params[:news] @news.safe_attributes = params[:news]
@news.save_attachments(params[:attachments]) if request.post?
if @news.save if @news.save
render_attachment_warning_if_needed(@news)
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'news', :action => 'index', :project_id => @project redirect_to :controller => 'news', :action => 'index', :project_id => @project
else else
render :action => 'new' render :action => 'new'
end end
end end
end
def edit def edit
end end
def update def update
@news.safe_attributes = params[:news] @news.safe_attributes = params[:news]
@news.save_attachments(params[:attachments]) if request.put? and @news.save
if @news.save
render_attachment_warning_if_needed(@news)
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @news redirect_to :action => 'show', :id => @news
else else
@@ -100,6 +97,11 @@ class NewsController < ApplicationController
end end
private private
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
def find_optional_project def find_optional_project
return true unless params[:project_id] return true unless params[:project_id]
+2 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -26,8 +26,7 @@ class PreviewsController < ApplicationController
if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n") if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n")
@description = nil @description = nil
end end
# params[:notes] is useful for preview of notes in issue history @notes = params[:notes]
@notes = params[:notes] || (params[:issue] ? params[:issue][:notes] : nil)
else else
@description = (params[:issue] ? params[:issue][:description] : nil) @description = (params[:issue] ? params[:issue][:description] : nil)
end end
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ProjectEnumerationsController < ApplicationController class ProjectEnumerationsController < ApplicationController
before_filter :find_project_by_project_id before_filter :find_project_by_project_id
before_filter :authorize before_filter :authorize
+31 -25
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -29,7 +29,7 @@ class ProjectsController < ApplicationController
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller| after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
if controller.request.post? if controller.request.post?
controller.send :expire_action, :controller => 'welcome', :action => 'robots' controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
end end
end end
@@ -48,11 +48,7 @@ class ProjectsController < ApplicationController
def index def index
respond_to do |format| respond_to do |format|
format.html { format.html {
scope = Project @projects = Project.visible.find(:all, :order => 'lft')
unless params[:closed]
scope = scope.active
end
@projects = scope.visible.order('lft').all
} }
format.api { format.api {
@offset, @limit = api_offset_and_limit @offset, @limit = api_offset_and_limit
@@ -69,14 +65,15 @@ class ProjectsController < ApplicationController
def new def new
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.sorted.all @trackers = Tracker.all
@project = Project.new @project = Project.new
@project.safe_attributes = params[:project] @project.safe_attributes = params[:project]
end end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.sorted.all @trackers = Tracker.all
@project = Project.new @project = Project.new
@project.safe_attributes = params[:project] @project.safe_attributes = params[:project]
@@ -109,7 +106,7 @@ class ProjectsController < ApplicationController
def copy def copy
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.sorted.all @trackers = Tracker.all
@root_projects = Project.find(:all, @root_projects = Project.find(:all,
:conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}", :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
:order => 'name') :order => 'name')
@@ -156,8 +153,12 @@ class ProjectsController < ApplicationController
cond = @project.project_condition(Setting.display_subprojects_issues?) cond = @project.project_condition(Setting.display_subprojects_issues?)
@open_issues_by_tracker = Issue.visible.open.where(cond).count(:group => :tracker) @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
@total_issues_by_tracker = Issue.visible.where(cond).count(:group => :tracker) :include => [:project, :status, :tracker],
:conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
@total_issues_by_tracker = Issue.visible.count(:group => :tracker,
:include => [:project, :status, :tracker],
:conditions => cond)
if User.current.allowed_to?(:view_time_entries, @project) if User.current.allowed_to?(:view_time_entries, @project)
@total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f @total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
@@ -175,13 +176,16 @@ class ProjectsController < ApplicationController
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@issue_category ||= IssueCategory.new @issue_category ||= IssueCategory.new
@member ||= @project.members.new @member ||= @project.members.new
@trackers = Tracker.sorted.all @trackers = Tracker.all
@repository ||= @project.repository
@wiki ||= @project.wiki @wiki ||= @project.wiki
end end
def edit def edit
end end
# TODO: convert to PUT only
verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update def update
@project.safe_attributes = params[:project] @project.safe_attributes = params[:project]
if validate_parent_id && @project.save if validate_parent_id && @project.save
@@ -191,7 +195,7 @@ class ProjectsController < ApplicationController
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project redirect_to :action => 'settings', :id => @project
} }
format.api { render_api_ok } format.api { head :ok }
end end
else else
respond_to do |format| respond_to do |format|
@@ -204,6 +208,7 @@ class ProjectsController < ApplicationController
end end
end end
verify :method => :post, :only => :modules, :render => {:nothing => true, :status => :method_not_allowed }
def modules def modules
@project.enabled_module_names = params[:enabled_module_names] @project.enabled_module_names = params[:enabled_module_names]
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
@@ -224,24 +229,18 @@ class ProjectsController < ApplicationController
redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status])) redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end end
def close
@project.close
redirect_to project_path(@project)
end
def reopen
@project.reopen
redirect_to project_path(@project)
end
# Delete @project # Delete @project
def destroy def destroy
@project_to_destroy = @project @project_to_destroy = @project
if request.get?
# display confirmation view
else
if api_request? || params[:confirm] if api_request? || params[:confirm]
@project_to_destroy.destroy @project_to_destroy.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'admin', :action => 'projects' } format.html { redirect_to :controller => 'admin', :action => 'projects' }
format.api { render_api_ok } format.api { head :ok }
end
end end
end end
# hide project in layout # hide project in layout
@@ -249,6 +248,13 @@ class ProjectsController < ApplicationController
end end
private private
def find_optional_project
return true unless params[:id]
@project = Project.find(params[:id])
authorize
rescue ActiveRecord::RecordNotFound
render_404
end
# Validates parent_id param according to user's permissions # Validates parent_id param according to user's permissions
# TODO: move it to Project model in a validation that depends on User.current # TODO: move it to Project model in a validation that depends on User.current
+4 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -50,6 +50,7 @@ class QueriesController < ApplicationController
build_query_from_params build_query_from_params
end end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
@query = Query.new(params[:query]) @query = Query.new(params[:query])
@query.user = User.current @query.user = User.current
@@ -69,6 +70,7 @@ class QueriesController < ApplicationController
def edit def edit
end end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update def update
@query.attributes = params[:query] @query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all] @query.project = nil if params[:query_is_for_all]
@@ -84,6 +86,7 @@ class QueriesController < ApplicationController
end end
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
@query.destroy @query.destroy
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
+3 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -22,7 +22,7 @@ class ReportsController < ApplicationController
def issue_report def issue_report
@trackers = @project.trackers @trackers = @project.trackers
@versions = @project.shared_versions.sort @versions = @project.shared_versions.sort
@priorities = IssuePriority.all.reverse @priorities = IssuePriority.all
@categories = @project.issue_categories @categories = @project.issue_categories
@assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort @assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort
@authors = @project.users.sort @authors = @project.users.sort
@@ -53,7 +53,7 @@ class ReportsController < ApplicationController
@report_title = l(:field_version) @report_title = l(:field_version)
when "priority" when "priority"
@field = "priority_id" @field = "priority_id"
@rows = IssuePriority.all.reverse @rows = IssuePriority.all
@data = Issue.by_priority(@project) @data = Issue.by_priority(@project)
@report_title = l(:field_priority) @report_title = l(:field_priority)
when "category" when "category"
+64 -114
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,77 +18,52 @@
require 'SVG/Graph/Bar' require 'SVG/Graph/Bar'
require 'SVG/Graph/BarHorizontal' require 'SVG/Graph/BarHorizontal'
require 'digest/sha1' require 'digest/sha1'
require 'redmine/scm/adapters/abstract_adapter'
class ChangesetNotFound < Exception; end class ChangesetNotFound < Exception; end
class InvalidRevisionParam < Exception; end class InvalidRevisionParam < Exception; end
class RepositoriesController < ApplicationController class RepositoriesController < ApplicationController
menu_item :repository menu_item :repository
menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers] menu_item :settings, :only => :edit
default_search_scope :changesets default_search_scope :changesets
before_filter :find_project_by_project_id, :only => [:new, :create] before_filter :find_repository, :except => :edit
before_filter :find_repository, :only => [:edit, :update, :destroy, :committers] before_filter :find_project, :only => :edit
before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
before_filter :authorize before_filter :authorize
accept_rss_auth :revisions accept_rss_auth :revisions
rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
def new
scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
@repository = Repository.factory(scm)
@repository.is_default = @project.repository.nil?
@repository.project = @project
end
def create
attrs = pickup_extra_info
@repository = Repository.factory(params[:repository_scm])
@repository.safe_attributes = params[:repository]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end
@repository.project = @project
if request.post? && @repository.save
redirect_to settings_project_path(@project, :tab => 'repositories')
else
render :action => 'new'
end
end
def edit def edit
@repository = @project.repository
if !@repository && !params[:repository_scm].blank?
@repository = Repository.factory(params[:repository_scm])
@repository.project = @project if @repository
end end
if request.post? && @repository
def update p1 = params[:repository]
attrs = pickup_extra_info
@repository.safe_attributes = attrs[:attrs]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end
@repository.project = @project
if request.put? && @repository.save
redirect_to settings_project_path(@project, :tab => 'repositories')
else
render :action => 'edit'
end
end
def pickup_extra_info
p = {} p = {}
p_extra = {} p_extra = {}
params[:repository].each do |k, v| p1.each do |k, v|
if k =~ /^extra_/ if k =~ /^extra_/
p_extra[k] = v p_extra[k] = v
else else
p[k] = v p[k] = v
end end
end end
{:attrs => p, :attrs_extra => p_extra} @repository.attributes = p
@repository.merge_extra_info(p_extra)
@repository.save
end
render(:update) do |page|
page.replace_html "tab-content-repository",
:partial => 'projects/settings/repository'
if @repository && !@project.repository
@project.reload # needed to reload association
page.replace_html "main-menu", render_main_menu(@project)
end
end
end end
private :pickup_extra_info
def committers def committers
@committers = @repository.committers @committers = @repository.committers
@@ -101,13 +76,16 @@ class RepositoriesController < ApplicationController
# Build a hash with repository usernames as keys and corresponding user ids as values # Build a hash with repository usernames as keys and corresponding user ids as values
@repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h} @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project, :tab => 'repositories') redirect_to :action => 'committers', :id => @project
end end
end end
def destroy def destroy
@repository.destroy if request.delete? @repository.destroy
redirect_to settings_project_path(@project, :tab => 'repositories') redirect_to :controller => 'projects',
:action => 'settings',
:id => @project,
:tab => 'repository'
end end
def show def show
@@ -121,7 +99,6 @@ class RepositoriesController < ApplicationController
(show_error_not_found; return) unless @entries (show_error_not_found; return) unless @entries
@changesets = @repository.latest_changesets(@path, @rev) @changesets = @repository.latest_changesets(@path, @rev)
@properties = @repository.properties(@path, @rev) @properties = @repository.properties(@path, @rev)
@repositories = @project.repositories
render :action => 'show' render :action => 'show'
end end
end end
@@ -152,15 +129,7 @@ class RepositoriesController < ApplicationController
end end
end end
def raw
entry_and_raw(true)
end
def entry def entry
entry_and_raw(false)
end
def entry_and_raw(is_raw)
@entry = @repository.entry(@path, @rev) @entry = @repository.entry(@path, @rev)
(show_error_not_found; return) unless @entry (show_error_not_found; return) unless @entry
@@ -169,14 +138,13 @@ class RepositoriesController < ApplicationController
@content = @repository.cat(@path, @rev) @content = @repository.cat(@path, @rev)
(show_error_not_found; return) unless @content (show_error_not_found; return) unless @content
if is_raw || if 'raw' == params[:format] ||
(@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) ||
! is_entry_text_data?(@content, @path) ! is_entry_text_data?(@content, @path)
# Force the download # Force the download
send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) } send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
send_type = Redmine::MimeType.of(@path) send_type = Redmine::MimeType.of(@path)
send_opt[:type] = send_type.to_s if send_type send_opt[:type] = send_type.to_s if send_type
send_opt[:disposition] = (Redmine::MimeType.is_type?('image', @path) && !is_raw ? 'inline' : 'attachment')
send_data @content, send_opt send_data @content, send_opt
else else
# Prevent empty lines when displaying a file with Windows style eol # Prevent empty lines when displaying a file with Windows style eol
@@ -186,7 +154,6 @@ class RepositoriesController < ApplicationController
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
end end
end end
private :entry_and_raw
def is_entry_text_data?(ent, path) def is_entry_text_data?(ent, path)
# UTF-16 contains "\x00". # UTF-16 contains "\x00".
@@ -219,32 +186,16 @@ class RepositoriesController < ApplicationController
end end
def revision def revision
raise ChangesetNotFound if @rev.blank?
@changeset = @repository.find_changeset_by_name(@rev)
raise ChangesetNotFound unless @changeset
respond_to do |format| respond_to do |format|
format.html format.html
format.js {render :layout => false} format.js {render :layout => false}
end end
end rescue ChangesetNotFound
show_error_not_found
# Adds a related issue to a changeset
# POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
def add_related_issue
@issue = @changeset.find_referenced_issue_by_id(params[:issue_id])
if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
@issue = nil
end
if @issue
@changeset.issues << @issue
end
end
# Removes a related issue from a changeset
# DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
def remove_related_issue
@issue = Issue.visible.find_by_id(params[:issue_id])
if @issue
@changeset.issues.delete(@issue)
end
end end
def diff def diff
@@ -299,24 +250,14 @@ class RepositoriesController < ApplicationController
private private
def find_repository
@repository = Repository.find(params[:id])
@project = @repository.project
rescue ActiveRecord::RecordNotFound
render_404
end
REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
def find_project_repository def find_repository
@project = Project.find(params[:id]) @project = Project.find(params[:id])
if params[:repository_id].present?
@repository = @project.repositories.find_by_identifier_param(params[:repository_id])
else
@repository = @project.repository @repository = @project.repository
end
(render_404; return false) unless @repository (render_404; return false) unless @repository
@path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s @path = params[:path].join('/') unless params[:path].nil?
@path ||= ''
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
@rev_to = params[:rev_to] @rev_to = params[:rev_to]
@@ -331,13 +272,6 @@ class RepositoriesController < ApplicationController
show_error_not_found show_error_not_found
end end
def find_changeset
if @rev.present?
@changeset = @repository.find_changeset_by_name(@rev)
end
show_error_not_found unless @changeset
end
def show_error_not_found def show_error_not_found
render_error :message => l(:error_scm_not_found), :status => 404 render_error :message => l(:error_scm_not_found), :status => 404
end end
@@ -351,17 +285,17 @@ class RepositoriesController < ApplicationController
@date_to = Date.today @date_to = Date.today
@date_from = @date_to << 11 @date_from = @date_to << 11
@date_from = Date.civil(@date_from.year, @date_from.month, 1) @date_from = Date.civil(@date_from.year, @date_from.month, 1)
commits_by_day = Changeset.count( commits_by_day = repository.changesets.count(
:all, :group => :commit_date, :all, :group => :commit_date,
:conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
commits_by_month = [0] * 12 commits_by_month = [0] * 12
commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
changes_by_day = Change.count( changes_by_day = repository.changes.count(
:all, :group => :commit_date, :include => :changeset, :all, :group => :commit_date,
:conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
changes_by_month = [0] * 12 changes_by_month = [0] * 12
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
fields = [] fields = []
12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)} 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
@@ -392,10 +326,10 @@ class RepositoriesController < ApplicationController
end end
def graph_commits_per_author(repository) def graph_commits_per_author(repository)
commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id]) commits_by_author = repository.changesets.count(:all, :group => :committer)
commits_by_author.to_a.sort! {|x, y| x.last <=> y.last} commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id]) changes_by_author = repository.changes.count(:all, :group => :committer)
h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o} h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
fields = commits_by_author.collect {|r| r.first} fields = commits_by_author.collect {|r| r.first}
@@ -431,3 +365,19 @@ class RepositoriesController < ApplicationController
graph.burn graph.burn
end end
end end
class Date
def months_ago(date = Date.today)
(date.year - self.year)*12 + (date.month - self.month)
end
def weeks_ago(date = Date.today)
(date.year - self.year)*52 + (date.cweek - self.cweek)
end
end
class String
def with_leading_slash
starts_with?('/') ? self : "/#{self}"
end
end
+15 -39
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,60 +18,44 @@
class RolesController < ApplicationController class RolesController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin, :except => :index before_filter :require_admin
before_filter :require_admin_or_api_request, :only => :index
before_filter :find_role, :only => [:edit, :update, :destroy] verify :method => :post, :only => [ :destroy ],
accept_api_auth :index :redirect_to => { :action => :index }
def index def index
respond_to do |format|
format.html {
@role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position' @role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
render :action => "index", :layout => false if request.xhr? render :action => "index", :layout => false if request.xhr?
}
format.api {
@roles = Role.givable.all
}
end
end end
def new def new
# Prefills the form with 'Non member' role permissions by default # Prefills the form with 'Non member' role permissions
@role = Role.new(params[:role] || {:permissions => Role.non_member.permissions}) @role = Role.new(params[:role] || {:permissions => Role.non_member.permissions})
if params[:copy].present? && @copy_from = Role.find_by_id(params[:copy])
@role.copy_from(@copy_from)
end
@roles = Role.sorted.all
end
def create
@role = Role.new(params[:role])
if request.post? && @role.save if request.post? && @role.save
# workflow copy # workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from])) if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from]))
@role.workflow_rules.copy(copy_from) @role.workflows.copy(copy_from)
end end
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index' redirect_to :action => 'index'
else else
@roles = Role.sorted.all @permissions = @role.setable_permissions
render :action => 'new' @roles = Role.find :all, :order => 'builtin, position'
end end
end end
def edit def edit
end @role = Role.find(params[:id])
if request.post? and @role.update_attributes(params[:role])
def update
if request.put? and @role.update_attributes(params[:role])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index' redirect_to :action => 'index'
else else
render :action => 'edit' @permissions = @role.setable_permissions
end end
end end
def destroy def destroy
@role = Role.find(params[:id])
@role.destroy @role.destroy
redirect_to :action => 'index' redirect_to :action => 'index'
rescue rescue
@@ -79,8 +63,8 @@ class RolesController < ApplicationController
redirect_to :action => 'index' redirect_to :action => 'index'
end end
def permissions def report
@roles = Role.sorted.all @roles = Role.find(:all, :order => 'builtin, position')
@permissions = Redmine::AccessControl.permissions.select { |p| !p.public? } @permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
if request.post? if request.post?
@roles.each do |role| @roles.each do |role|
@@ -91,12 +75,4 @@ class RolesController < ApplicationController
redirect_to :action => 'index' redirect_to :action => 'index'
end end
end end
private
def find_role
@role = Role.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -34,7 +34,7 @@ class SearchController < ApplicationController
when 'my_projects' when 'my_projects'
User.current.memberships.collect(&:project) User.current.memberships.collect(&:project)
when 'subprojects' when 'subprojects'
@project ? (@project.self_and_descendants.active.all) : nil @project ? (@project.self_and_descendants.active) : nil
else else
@project @project
end end
+3 -4
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,7 +17,6 @@
class SettingsController < ApplicationController class SettingsController < ApplicationController
layout 'admin' layout 'admin'
menu_item :plugins, :only => :plugin
before_filter :require_admin before_filter :require_admin
@@ -52,12 +51,12 @@ class SettingsController < ApplicationController
def plugin def plugin
@plugin = Redmine::Plugin.find(params[:id]) @plugin = Redmine::Plugin.find(params[:id])
if request.post? if request.post?
Setting.send "plugin_#{@plugin.id}=", params[:settings] Setting["plugin_#{@plugin.id}"] = params[:settings]
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'plugin', :id => @plugin.id redirect_to :action => 'plugin', :id => @plugin.id
else else
@partial = @plugin.settings[:partial] @partial = @plugin.settings[:partial]
@settings = Setting.send "plugin_#{@plugin.id}" @settings = Setting["plugin_#{@plugin.id}"]
end end
rescue Redmine::PluginNotFound rescue Redmine::PluginNotFound
render_404 render_404
+10 -26
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,16 +19,9 @@ class SysController < ActionController::Base
before_filter :check_enabled before_filter :check_enabled
def projects def projects
p = Project.active.has_module(:repository).find( p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
:all,
:include => :repository,
:order => "#{Project.table_name}.identifier"
)
# extra_info attribute from repository breaks activeresource client # extra_info attribute from repository breaks activeresource client
render :xml => p.to_xml( render :xml => p.to_xml(:only => [:id, :identifier, :name, :is_public, :status], :include => {:repository => {:only => [:id, :url]}})
:only => [:id, :identifier, :name, :is_public, :status],
:include => {:repository => {:only => [:id, :url]}}
)
end end
def create_project_repository def create_project_repository
@@ -37,10 +30,9 @@ class SysController < ActionController::Base
render :nothing => true, :status => 409 render :nothing => true, :status => 409
else else
logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}." logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
repository = Repository.factory(params[:vendor], params[:repository]) project.repository = Repository.factory(params[:vendor], params[:repository])
repository.project = project if project.repository && project.repository.save
if repository.save render :xml => project.repository.to_xml(:only => [:id, :url]), :status => 201
render :xml => {repository.class.name.underscore.gsub('/', '-') => {:id => repository.id, :url => repository.url}}, :status => 201
else else
render :nothing => true, :status => 422 render :nothing => true, :status => 422
end end
@@ -49,22 +41,14 @@ class SysController < ActionController::Base
def fetch_changesets def fetch_changesets
projects = [] projects = []
scope = Project.active.has_module(:repository)
if params[:id] if params[:id]
project = nil projects << Project.active.has_module(:repository).find(params[:id])
if params[:id].to_s =~ /^\d*$/
project = scope.find(params[:id])
else else
project = scope.find_by_identifier(params[:id]) projects = Project.active.has_module(:repository).find(:all, :include => :repository)
end
raise ActiveRecord::RecordNotFound unless project
projects << project
else
projects = scope.all
end end
projects.each do |project| projects.each do |project|
project.repositories.each do |repository| if project.repository
repository.fetch_changesets project.repository.fetch_changesets
end end
end end
render :nothing => true, :status => 200 render :nothing => true, :status => 200
@@ -0,0 +1,209 @@
class TimeEntryReportsController < ApplicationController
menu_item :issues
before_filter :find_optional_project
before_filter :load_available_criterias
helper :sort
include SortHelper
helper :issues
helper :timelog
include TimelogHelper
helper :custom_fields
include CustomFieldsHelper
def report
@criterias = params[:criterias] || []
@criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
@criterias.uniq!
@criterias = @criterias[0,3]
@columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month'
retrieve_date_range
unless @criterias.empty?
sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
sql_condition = ''
if @project.nil?
sql_condition = Project.allowed_to_condition(User.current, :view_time_entries)
elsif @issue.nil?
sql_condition = @project.project_condition(Setting.display_subprojects_issues?)
else
sql_condition = "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
end
sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
sql << " FROM #{TimeEntry.table_name}"
sql << time_report_joins
sql << " WHERE"
sql << " (%s) AND" % sql_condition
sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)]
sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
@hours = ActiveRecord::Base.connection.select_all(sql)
@hours.each do |row|
case @columns
when 'year'
row['year'] = row['tyear']
when 'month'
row['month'] = "#{row['tyear']}-#{row['tmonth']}"
when 'week'
row['week'] = "#{row['tyear']}-#{row['tweek']}"
when 'day'
row['day'] = "#{row['spent_on']}"
end
end
@total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
@periods = []
# Date#at_beginning_of_ not supported in Rails 1.2.x
date_from = @from.to_time
# 100 columns max
while date_from <= @to.to_time && @periods.length < 100
case @columns
when 'year'
@periods << "#{date_from.year}"
date_from = (date_from + 1.year).at_beginning_of_year
when 'month'
@periods << "#{date_from.year}-#{date_from.month}"
date_from = (date_from + 1.month).at_beginning_of_month
when 'week'
@periods << "#{date_from.year}-#{date_from.to_date.cweek}"
date_from = (date_from + 7.day).at_beginning_of_week
when 'day'
@periods << "#{date_from.to_date}"
date_from = date_from + 1.day
end
end
end
respond_to do |format|
format.html { render :layout => !request.xhr? }
format.csv { send_data(report_to_csv(@criterias, @periods, @hours), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
end
end
private
# TODO: duplicated in TimelogController
def find_optional_project
if !params[:issue_id].blank?
@issue = Issue.find(params[:issue_id])
@project = @issue.project
elsif !params[:project_id].blank?
@project = Project.find(params[:project_id])
end
deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
end
# Retrieves the date range based on predefined ranges or specific from/to param dates
# TODO: duplicated in TimelogController
def retrieve_date_range
@free_period = false
@from, @to = nil, nil
if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
case params[:period].to_s
when 'today'
@from = @to = Date.today
when 'yesterday'
@from = @to = Date.today - 1
when 'current_week'
@from = Date.today - (Date.today.cwday - 1)%7
@to = @from + 6
when 'last_week'
@from = Date.today - 7 - (Date.today.cwday - 1)%7
@to = @from + 6
when '7_days'
@from = Date.today - 7
@to = Date.today
when 'current_month'
@from = Date.civil(Date.today.year, Date.today.month, 1)
@to = (@from >> 1) - 1
when 'last_month'
@from = Date.civil(Date.today.year, Date.today.month, 1) << 1
@to = (@from >> 1) - 1
when '30_days'
@from = Date.today - 30
@to = Date.today
when 'current_year'
@from = Date.civil(Date.today.year, 1, 1)
@to = Date.civil(Date.today.year, 12, 31)
end
elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
@free_period = true
else
# default
end
@from, @to = @to, @from if @from && @to && @from > @to
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
end
def load_available_criterias
@available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
:klass => Project,
:label => :label_project},
'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
:klass => Version,
:label => :label_version},
'category' => {:sql => "#{Issue.table_name}.category_id",
:klass => IssueCategory,
:label => :field_category},
'member' => {:sql => "#{TimeEntry.table_name}.user_id",
:klass => User,
:label => :label_member},
'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
:klass => Tracker,
:label => :label_tracker},
'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
:klass => TimeEntryActivity,
:label => :label_activity},
'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
:klass => Issue,
:label => :label_issue}
}
# Add list and boolean custom fields as available criterias
custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields)
custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)",
:format => cf.field_format,
:label => cf.name}
end if @project
# Add list and boolean time entry custom fields
TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)",
:format => cf.field_format,
:label => cf.name}
end
# Add list and boolean time entry activity custom fields
TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id)",
:format => cf.field_format,
:label => cf.name}
end
call_hook(:controller_timelog_available_criterias, { :available_criterias => @available_criterias, :project => @project })
@available_criterias
end
def time_report_joins
sql = ''
sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
# TODO: rename hook
call_hook(:controller_timelog_time_report_joins, {:sql => sql} )
sql
end
end
+60 -80
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,16 +17,11 @@
class TimelogController < ApplicationController class TimelogController < ApplicationController
menu_item :issues menu_item :issues
before_filter :find_project, :only => [:new, :create]
before_filter :find_project_for_new_time_entry, :only => [:create]
before_filter :find_time_entry, :only => [:show, :edit, :update] before_filter :find_time_entry, :only => [:show, :edit, :update]
before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy] before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
before_filter :authorize, :except => [:new, :index, :report] before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => [:index]
before_filter :find_optional_project, :only => [:index, :report]
before_filter :find_optional_project_for_new_time_entry, :only => [:new]
before_filter :authorize_global, :only => [:new, :index, :report]
accept_rss_auth :index accept_rss_auth :index
accept_api_auth :index, :show, :create, :update, :destroy accept_api_auth :index, :show, :create, :update, :destroy
@@ -39,76 +34,67 @@ class TimelogController < ApplicationController
def index def index
sort_init 'spent_on', 'desc' sort_init 'spent_on', 'desc'
sort_update 'spent_on' => ['spent_on', "#{TimeEntry.table_name}.created_on"], sort_update 'spent_on' => 'spent_on',
'user' => 'user_id', 'user' => 'user_id',
'activity' => 'activity_id', 'activity' => 'activity_id',
'project' => "#{Project.table_name}.name", 'project' => "#{Project.table_name}.name",
'issue' => 'issue_id', 'issue' => 'issue_id',
'hours' => 'hours' 'hours' => 'hours'
retrieve_date_range cond = ARCondition.new
scope = TimeEntry.visible.spent_between(@from, @to)
if @issue if @issue
scope = scope.on_issue(@issue) cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
elsif @project elsif @project
scope = scope.on_project(@project, Setting.display_subprojects_issues?) cond << @project.project_condition(Setting.display_subprojects_issues?)
end end
retrieve_date_range
cond << ['spent_on BETWEEN ? AND ?', @from, @to]
respond_to do |format| respond_to do |format|
format.html { format.html {
# Paginate results # Paginate results
@entry_count = scope.count @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
@entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
@entries = scope.all( @entries = TimeEntry.visible.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}], :include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions,
:order => sort_clause, :order => sort_clause,
:limit => @entry_pages.items_per_page, :limit => @entry_pages.items_per_page,
:offset => @entry_pages.current.offset :offset => @entry_pages.current.offset)
) @total_hours = TimeEntry.visible.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f
@total_hours = scope.sum(:hours).to_f
render :layout => !request.xhr? render :layout => !request.xhr?
} }
format.api { format.api {
@entry_count = scope.count @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
@offset, @limit = api_offset_and_limit @offset, @limit = api_offset_and_limit
@entries = scope.all( @entries = TimeEntry.visible.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}], :include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions,
:order => sort_clause, :order => sort_clause,
:limit => @limit, :limit => @limit,
:offset => @offset :offset => @offset)
)
} }
format.atom { format.atom {
entries = scope.all( entries = TimeEntry.visible.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}], :include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions,
:order => "#{TimeEntry.table_name}.created_on DESC", :order => "#{TimeEntry.table_name}.created_on DESC",
:limit => Setting.feeds_limit.to_i :limit => Setting.feeds_limit.to_i)
)
render_feed(entries, :title => l(:label_spent_time)) render_feed(entries, :title => l(:label_spent_time))
} }
format.csv { format.csv {
# Export all entries # Export all entries
@entries = scope.all( @entries = TimeEntry.visible.find(:all,
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:order => sort_clause :conditions => cond.conditions,
) :order => sort_clause)
send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv') send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
} }
end end
end end
def report
retrieve_date_range
@report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to)
respond_to do |format|
format.html { render :layout => !request.xhr? }
format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
end
end
def show def show
respond_to do |format| respond_to do |format|
# TODO: Implement html response # TODO: Implement html response
@@ -120,8 +106,12 @@ class TimelogController < ApplicationController
def new def new
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.safe_attributes = params[:time_entry] @time_entry.safe_attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
render :action => 'edit'
end end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.safe_attributes = params[:time_entry] @time_entry.safe_attributes = params[:time_entry]
@@ -131,26 +121,14 @@ class TimelogController < ApplicationController
if @time_entry.save if @time_entry.save
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_update)
if params[:continue]
if params[:project_id]
redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue,
:time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url]
else
redirect_to :action => 'new',
:time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url]
end
else
redirect_back_or_default :action => 'index', :project_id => @time_entry.project redirect_back_or_default :action => 'index', :project_id => @time_entry.project
end
} }
format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) } format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
end end
else else
respond_to do |format| respond_to do |format|
format.html { render :action => 'new' } format.html { render :action => 'edit' }
format.api { render_validation_errors(@time_entry) } format.api { render_validation_errors(@time_entry) }
end end
end end
@@ -158,8 +136,11 @@ class TimelogController < ApplicationController
def edit def edit
@time_entry.safe_attributes = params[:time_entry] @time_entry.safe_attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
end end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update def update
@time_entry.safe_attributes = params[:time_entry] @time_entry.safe_attributes = params[:time_entry]
@@ -171,7 +152,7 @@ class TimelogController < ApplicationController
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_back_or_default :action => 'index', :project_id => @time_entry.project redirect_back_or_default :action => 'index', :project_id => @time_entry.project
} }
format.api { render_api_ok } format.api { head :ok }
end end
else else
respond_to do |format| respond_to do |format|
@@ -203,31 +184,32 @@ class TimelogController < ApplicationController
redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first}) redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
destroyed = TimeEntry.transaction do
@time_entries.each do |t| @time_entries.each do |t|
begin
unless t.destroy && t.destroyed? unless t.destroy && t.destroyed?
raise ActiveRecord::Rollback respond_to do |format|
format.html {
flash[:error] = l(:notice_unable_delete_time_entry)
redirect_to :back
}
format.api { render_validation_errors(t) }
end end
return
end
rescue ::ActionController::RedirectBackError
redirect_to :action => 'index', :project_id => @projects.first
return
end end
end end
respond_to do |format| respond_to do |format|
format.html { format.html {
if destroyed
flash[:notice] = l(:notice_successful_delete) flash[:notice] = l(:notice_successful_delete)
else
flash[:error] = l(:notice_unable_delete_time_entry)
end
redirect_back_or_default(:action => 'index', :project_id => @projects.first) redirect_back_or_default(:action => 'index', :project_id => @projects.first)
} }
format.api { format.api { head :ok }
if destroyed
render_api_ok
else
render_validation_errors(@time_entries)
end
}
end end
end end
@@ -263,25 +245,20 @@ private
end end
end end
def find_optional_project_for_new_time_entry def find_project
if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
@project = Project.find(project_id)
end
if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present? if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
@issue = Issue.find(issue_id) @issue = Issue.find(issue_id)
@project ||= @issue.project @project = @issue.project
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
end end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def find_project_for_new_time_entry
find_optional_project_for_new_time_entry
if @project.nil?
render_404
end
end
def find_optional_project def find_optional_project
if !params[:issue_id].blank? if !params[:issue_id].blank?
@issue = Issue.find(params[:issue_id]) @issue = Issue.find(params[:issue_id])
@@ -289,6 +266,7 @@ private
elsif !params[:project_id].blank? elsif !params[:project_id].blank?
@project = Project.find(params[:project_id]) @project = Project.find(params[:project_id])
end end
deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
end end
# Retrieves the date range based on predefined ranges or specific from/to param dates # Retrieves the date range based on predefined ranges or specific from/to param dates
@@ -333,6 +311,8 @@ private
end end
@from, @to = @to, @from if @from && @to && @from > @to @from, @to = @to, @from if @from && @to && @from > @to
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
end end
def parse_params_for_bulk_time_entry_attributes(params) def parse_params_for_bulk_time_entry_attributes(params)
+4 -21
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -29,7 +29,7 @@ class TrackersController < ApplicationController
render :action => "index", :layout => false if request.xhr? render :action => "index", :layout => false if request.xhr?
} }
format.api { format.api {
@trackers = Tracker.sorted.all @trackers = Tracker.all
} }
end end
end end
@@ -45,7 +45,7 @@ class TrackersController < ApplicationController
if request.post? and @tracker.save if request.post? and @tracker.save
# workflow copy # workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from])) if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
@tracker.workflow_rules.copy(copy_from) @tracker.workflows.copy(copy_from)
end end
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index' redirect_to :action => 'index'
@@ -71,6 +71,7 @@ class TrackersController < ApplicationController
render :action => 'edit' render :action => 'edit'
end end
verify :method => :delete, :only => :destroy, :redirect_to => { :action => :index }
def destroy def destroy
@tracker = Tracker.find(params[:id]) @tracker = Tracker.find(params[:id])
unless @tracker.issues.empty? unless @tracker.issues.empty?
@@ -80,22 +81,4 @@ class TrackersController < ApplicationController
end end
redirect_to :action => 'index' redirect_to :action => 'index'
end end
def fields
if request.post? && params[:trackers]
params[:trackers].each do |tracker_id, tracker_params|
tracker = Tracker.find_by_id(tracker_id)
if tracker
tracker.core_fields = tracker_params[:core_fields]
tracker.custom_field_ids = tracker_params[:custom_field_ids]
tracker.save
end
end
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'fields'
return
end
@trackers = Tracker.sorted.all
@custom_fields = IssueCustomField.all.sort
end
end end
+45 -19
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -38,17 +38,23 @@ class UsersController < ApplicationController
@limit = per_page_option @limit = per_page_option
end end
@status = params[:status] || 1 scope = User
scope = scope.in_group(params[:group_id].to_i) if params[:group_id].present?
scope = User.logged.status(@status) @status = params[:status] ? params[:status].to_i : 1
scope = scope.like(params[:name]) if params[:name].present? c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
scope = scope.in_group(params[:group_id]) if params[:group_id].present?
@user_count = scope.count unless params[:name].blank?
name = "%#{params[:name].strip.downcase}%"
c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
end
@user_count = scope.count(:conditions => c.conditions)
@user_pages = Paginator.new self, @user_count, @limit, params['page'] @user_pages = Paginator.new self, @user_count, @limit, params['page']
@offset ||= @user_pages.current.offset @offset ||= @user_pages.current.offset
@users = scope.find :all, @users = scope.find :all,
:order => sort_clause, :order => sort_clause,
:conditions => c.conditions,
:limit => @limit, :limit => @limit,
:offset => @offset :offset => @offset
@@ -86,6 +92,7 @@ class UsersController < ApplicationController
@auth_sources = AuthSource.find(:all) @auth_sources = AuthSource.find(:all)
end end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
@user.safe_attributes = params[:user] @user.safe_attributes = params[:user]
@@ -93,17 +100,19 @@ class UsersController < ApplicationController
@user.login = params[:user][:login] @user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
if @user.save # TODO: Similar to My#account
@user.pref.attributes = params[:pref] @user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1') @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
if @user.save
@user.pref.save @user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information] Mailer.deliver_account_information(@user, params[:user][:password]) if params[:send_information]
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user))) flash[:notice] = l(:notice_successful_create)
redirect_to(params[:continue] ? redirect_to(params[:continue] ?
{:controller => 'users', :action => 'new'} : {:controller => 'users', :action => 'new'} :
{:controller => 'users', :action => 'edit', :id => @user} {:controller => 'users', :action => 'edit', :id => @user}
@@ -128,6 +137,7 @@ class UsersController < ApplicationController
@membership ||= Member.new @membership ||= Member.new
end end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update def update
@user.admin = params[:user][:admin] if params[:user][:admin] @user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login] @user.login = params[:user][:login] if params[:user][:login]
@@ -146,17 +156,17 @@ class UsersController < ApplicationController
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
if was_activated if was_activated
Mailer.account_activated(@user).deliver Mailer.deliver_account_activated(@user)
elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil? elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
Mailer.account_information(@user, params[:user][:password]).deliver Mailer.deliver_account_information(@user, params[:user][:password])
end end
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to_referer_or edit_user_path(@user) redirect_to :back
} }
format.api { render_api_ok } format.api { head :ok }
end end
else else
@auth_sources = AuthSource.find(:all) @auth_sources = AuthSource.find(:all)
@@ -169,33 +179,49 @@ class UsersController < ApplicationController
format.api { render_validation_errors(@user) } format.api { render_validation_errors(@user) }
end end
end end
rescue ::ActionController::RedirectBackError
redirect_to :controller => 'users', :action => 'edit', :id => @user
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
@user.destroy @user.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default(users_url) } format.html { redirect_to(users_url) }
format.api { render_api_ok } format.api { head :ok }
end end
end end
def edit_membership def edit_membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user) @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save @membership.save if request.post?
respond_to do |format| respond_to do |format|
if @membership.valid?
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
format.js format.js {
render(:update) {|page|
page.replace_html "tab-content-memberships", :partial => 'users/memberships'
page.visual_effect(:highlight, "member-#{@membership.id}")
}
}
else
format.js {
render(:update) {|page|
page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
}
}
end
end end
end end
def destroy_membership def destroy_membership
@membership = Member.find(params[:membership_id]) @membership = Member.find(params[:membership_id])
if @membership.deletable? if request.post? && @membership.deletable?
@membership.destroy @membership.destroy
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
format.js format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
end end
end end
+28 -22
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -39,19 +39,17 @@ class VersionsController < ApplicationController
@versions = @project.shared_versions || [] @versions = @project.shared_versions || []
@versions += @project.rolled_up_versions.visible if @with_subprojects @versions += @project.rolled_up_versions.visible if @with_subprojects
@versions = @versions.uniq.sort @versions = @versions.uniq.sort
unless params[:completed] @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
@completed_versions = @versions.select {|version| version.closed? || version.completed? }
@versions -= @completed_versions
end
@issues_by_version = {} @issues_by_version = {}
if @selected_tracker_ids.any? && @versions.any? unless @selected_tracker_ids.empty?
issues = Issue.visible.all( @versions.each do |version|
:include => [:project, :status, :tracker, :priority, :fixed_version], issues = version.fixed_issues.visible.find(:all,
:conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)}, :include => [:project, :status, :tracker, :priority],
:order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id" :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids},
) :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
@issues_by_version = issues.group_by(&:fixed_version) @issues_by_version[version] = issues
end
end end
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?} @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
} }
@@ -74,15 +72,15 @@ class VersionsController < ApplicationController
def new def new
@version = @project.versions.build @version = @project.versions.build
@version.safe_attributes = params[:version] if params[:version]
attributes = params[:version].dup
respond_to do |format| attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
format.html @version.safe_attributes = attributes
format.js
end end
end end
def create def create
# TODO: refactor with code above in #new
@version = @project.versions.build @version = @project.versions.build
if params[:version] if params[:version]
attributes = params[:version].dup attributes = params[:version].dup
@@ -97,7 +95,12 @@ class VersionsController < ApplicationController
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end end
format.js format.js do
# IE doesn't support the replace_html rjs method for select box options
render(:update) {|page| page.replace "issue_fixed_version_id",
content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
}
end
format.api do format.api do
render :action => 'show', :status => :created, :location => version_url(@version) render :action => 'show', :status => :created, :location => version_url(@version)
end end
@@ -105,7 +108,9 @@ class VersionsController < ApplicationController
else else
respond_to do |format| respond_to do |format|
format.html { render :action => 'new' } format.html { render :action => 'new' }
format.js { render :action => 'new' } format.js do
render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
end
format.api { render_validation_errors(@version) } format.api { render_validation_errors(@version) }
end end
end end
@@ -126,7 +131,7 @@ class VersionsController < ApplicationController
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
} }
format.api { render_api_ok } format.api { head :ok }
end end
else else
respond_to do |format| respond_to do |format|
@@ -144,12 +149,13 @@ class VersionsController < ApplicationController
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
if @version.fixed_issues.empty? if @version.fixed_issues.empty?
@version.destroy @version.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project } format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project }
format.api { render_api_ok } format.api { head :ok }
end end
else else
respond_to do |format| respond_to do |format|
@@ -165,7 +171,7 @@ class VersionsController < ApplicationController
def status_by def status_by
respond_to do |format| respond_to do |format|
format.html { render :action => 'show' } format.html { render :action => 'show' }
format.js format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
end end
end end
+28 -30
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,6 +20,10 @@ class WatchersController < ApplicationController
before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch] before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
before_filter :authorize, :only => [:new, :destroy] before_filter :authorize, :only => [:new, :destroy]
verify :method => :post,
:only => [ :watch, :unwatch ],
:render => { :nothing => true, :status => :method_not_allowed }
def watch def watch
if @watched.respond_to?(:visible?) && !@watched.visible?(User.current) if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
render_403 render_403
@@ -33,54 +37,39 @@ class WatchersController < ApplicationController
end end
def new def new
end @watcher = Watcher.new(params[:watcher])
@watcher.watchable = @watched
def create @watcher.save if request.post?
if params[:watcher].is_a?(Hash) && request.post?
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
user_ids.each do |user_id|
Watcher.create(:watchable => @watched, :user_id => user_id)
end
end
respond_to do |format| respond_to do |format|
format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}} format.html { redirect_to :back }
format.js format.js do
render :update do |page|
page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
end end
end end
def append
if params[:watcher].is_a?(Hash)
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
@users = User.active.find_all_by_id(user_ids)
end end
rescue ::ActionController::RedirectBackError
render :text => 'Watcher added.', :layout => true
end end
def destroy def destroy
@watched.set_watcher(User.find(params[:user_id]), false) if request.post? @watched.set_watcher(User.find(params[:user_id]), false) if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
format.js format.js do
render :update do |page|
page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
end end
end end
def autocomplete_for_user
@users = User.active.like(params[:q]).find(:all, :limit => 100)
if @watched
@users -= @watched.watcher_users
end end
render :layout => false
end end
private private
def find_project def find_project
if params[:object_type] && params[:object_id]
klass = Object.const_get(params[:object_type].camelcase) klass = Object.const_get(params[:object_type].camelcase)
return false unless klass.respond_to?('watched_by') return false unless klass.respond_to?('watched_by')
@watched = klass.find(params[:object_id]) @watched = klass.find(params[:object_id])
@project = @watched.project @project = @watched.project
elsif params[:project_id]
@project = Project.visible.find_by_param(params[:project_id])
end
rescue rescue
render_404 render_404
end end
@@ -88,8 +77,17 @@ private
def set_watcher(user, watching) def set_watcher(user, watching)
@watched.set_watcher(user, watching) @watched.set_watcher(user, watching)
respond_to do |format| respond_to do |format|
format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}} format.html { redirect_to :back }
format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => @watched} } format.js do
render(:update) do |page|
c = watcher_css(@watched)
page.select(".#{c}").each do |item|
page.replace_html item, watcher_link(@watched, user)
end end
end end
end end
end
rescue ::ActionController::RedirectBackError
render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
end
end
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+21 -21
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -73,7 +73,7 @@ class WikiController < ApplicationController
@content = @page.content_for_version(params[:version]) @content = @page.content_for_version(params[:version])
if User.current.allowed_to?(:export_wiki_pages, @project) if User.current.allowed_to?(:export_wiki_pages, @project)
if params[:format] == 'pdf' if params[:format] == 'pdf'
send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf") send_data(wiki_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
return return
elsif params[:format] == 'html' elsif params[:format] == 'html'
export = render_to_string :action => 'export', :layout => false export = render_to_string :action => 'export', :layout => false
@@ -95,12 +95,7 @@ class WikiController < ApplicationController
# edit an existing page or a new one # edit an existing page or a new one
def edit def edit
return render_403 unless editable? return render_403 unless editable?
if @page.new_record? @page.content = WikiContent.new(:page => @page) if @page.new_record?
@page.content = WikiContent.new(:page => @page)
if params[:parent].present?
@page.parent = @page.wiki.find_page(params[:parent].to_s)
end
end
@content = @page.content_for_version(params[:version]) @content = @page.content_for_version(params[:version])
@content.text = initial_page_content(@page) if @content.text.blank? @content.text = initial_page_content(@page) if @content.text.blank?
@@ -118,17 +113,25 @@ class WikiController < ApplicationController
end end
end end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
# Creates a new page or updates an existing one # Creates a new page or updates an existing one
def update def update
return render_403 unless editable? return render_403 unless editable?
@page.content = WikiContent.new(:page => @page) if @page.new_record? @page.content = WikiContent.new(:page => @page) if @page.new_record?
@page.safe_attributes = params[:wiki_page]
@content = @page.content_for_version(params[:version]) @content = @page.content_for_version(params[:version])
@content.text = initial_page_content(@page) if @content.text.blank? @content.text = initial_page_content(@page) if @content.text.blank?
# don't keep previous comment # don't keep previous comment
@content.comments = nil @content.comments = nil
if !@page.new_record? && params[:content].present? && @content.text == params[:content][:text]
attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page)
# don't save if text wasn't changed
redirect_to :action => 'show', :project_id => @project, :id => @page.title
return
end
@content.comments = params[:content][:comments] @content.comments = params[:content][:comments]
@text = params[:content][:text] @text = params[:content][:text]
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@@ -140,8 +143,8 @@ class WikiController < ApplicationController
@content.text = @text @content.text = @text
end end
@content.author = User.current @content.author = User.current
# if page is new @page.save will also save content, but not if page isn't a new record
if @page.save_with_content if (@page.new_record? ? @page.save : @content.save)
attachments = Attachment.attach_files(@page, params[:attachments]) attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page) render_attachment_warning_if_needed(@page)
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
@@ -154,8 +157,6 @@ class WikiController < ApplicationController
# Optimistic locking exception # Optimistic locking exception
flash.now[:error] = l(:notice_locking_conflict) flash.now[:error] = l(:notice_locking_conflict)
render :action => 'edit' render :action => 'edit'
rescue ActiveRecord::RecordNotSaved
render :action => 'edit'
end end
# rename a page # rename a page
@@ -170,6 +171,7 @@ class WikiController < ApplicationController
end end
end end
verify :method => :post, :only => :protect, :redirect_to => { :action => :show }
def protect def protect
@page.update_attribute :protected, params[:protected] @page.update_attribute :protected, params[:protected]
redirect_to :action => 'show', :project_id => @project, :id => @page.title redirect_to :action => 'show', :project_id => @project, :id => @page.title
@@ -199,6 +201,7 @@ class WikiController < ApplicationController
render_404 unless @annotate render_404 unless @annotate
end end
verify :method => :delete, :only => [:destroy], :redirect_to => { :action => :show }
# Removes a wiki page and its history # Removes a wiki page and its history
# Children can be either set as root pages, removed or reassigned to another parent page # Children can be either set as root pages, removed or reassigned to another parent page
def destroy def destroy
@@ -228,17 +231,14 @@ class WikiController < ApplicationController
redirect_to :action => 'index', :project_id => @project redirect_to :action => 'index', :project_id => @project
end end
# Export wiki to a single pdf or html file # Export wiki to a single html file
def export def export
@pages = @wiki.pages.all(:order => 'title', :include => [:content, :attachments]) if User.current.allowed_to?(:export_wiki_pages, @project)
respond_to do |format| @pages = @wiki.pages.find :all, :order => 'title'
format.html {
export = render_to_string :action => 'export_multiple', :layout => false export = render_to_string :action => 'export_multiple', :layout => false
send_data(export, :type => 'text/html', :filename => "wiki.html") send_data(export, :type => 'text/html', :filename => "wiki.html")
} else
format.pdf { redirect_to :action => 'show', :project_id => @project, :id => nil
send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
}
end end
end end
+2 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -24,6 +24,7 @@ class WikisController < ApplicationController
@wiki = @project.wiki || Wiki.new(:project => @project) @wiki = @project.wiki || Wiki.new(:project => @project)
@wiki.safe_attributes = params[:wiki] @wiki.safe_attributes = params[:wiki]
@wiki.save if request.post? @wiki.save if request.post?
render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
end end
# Delete a project's wiki # Delete a project's wiki
+16 -42
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,27 +18,30 @@
class WorkflowsController < ApplicationController class WorkflowsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin, :find_roles, :find_trackers before_filter :require_admin
before_filter :find_roles
before_filter :find_trackers
def index def index
@workflow_counts = WorkflowTransition.count_by_tracker_and_role @workflow_counts = Workflow.count_by_tracker_and_role
end end
def edit def edit
@role = Role.find_by_id(params[:role_id]) if params[:role_id] @role = Role.find_by_id(params[:role_id])
@tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id] @tracker = Tracker.find_by_id(params[:tracker_id])
if request.post? if request.post?
WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
(params[:issue_status] || []).each { |status_id, transitions| (params[:issue_status] || []).each { |status_id, transitions|
transitions.each { |new_status_id, options| transitions.each { |new_status_id, options|
author = options.is_a?(Array) && options.include?('author') && !options.include?('always') author = options.is_a?(Array) && options.include?('author') && !options.include?('always')
assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always') assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always')
WorkflowTransition.create(:role_id => @role.id, :tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee) @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 if @role.save
redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only] flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker
return return
end end
end end
@@ -47,10 +50,10 @@ class WorkflowsController < ApplicationController
if @tracker && @used_statuses_only && @tracker.issue_statuses.any? if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
@statuses = @tracker.issue_statuses @statuses = @tracker.issue_statuses
end end
@statuses ||= IssueStatus.sorted.all @statuses ||= IssueStatus.find(:all, :order => 'position')
if @tracker && @role && @statuses.any? if @tracker && @role && @statuses.any?
workflows = WorkflowTransition.where(:role_id => @role.id, :tracker_id => @tracker.id).all workflows = Workflow.all(:conditions => {:role_id => @role.id, :tracker_id => @tracker.id})
@workflows = {} @workflows = {}
@workflows['always'] = workflows.select {|w| !w.author && !w.assignee} @workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
@workflows['author'] = workflows.select {|w| w.author} @workflows['author'] = workflows.select {|w| w.author}
@@ -58,35 +61,6 @@ class WorkflowsController < ApplicationController
end end
end end
def permissions
@role = Role.find_by_id(params[:role_id]) if params[:role_id]
@tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id]
if request.post? && @role && @tracker
WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {})
redirect_to :action => 'permissions', :role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]
return
end
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
@statuses = @tracker.issue_statuses
end
@statuses ||= IssueStatus.sorted.all
if @role && @tracker
@fields = (Tracker::CORE_FIELDS_ALL - @tracker.disabled_core_fields).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
@custom_fields = @tracker.custom_fields
@permissions = WorkflowPermission.where(:tracker_id => @tracker.id, :role_id => @role.id).all.inject({}) do |h, w|
h[w.old_status_id] ||= {}
h[w.old_status_id][w.field_name] = w.rule
h
end
@statuses.each {|status| @permissions[status.id] ||= {}}
end
end
def copy def copy
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any' if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
@@ -109,7 +83,7 @@ class WorkflowsController < ApplicationController
elsif @target_trackers.nil? || @target_roles.nil? elsif @target_trackers.nil? || @target_roles.nil?
flash.now[:error] = l(:error_workflow_copy_target) flash.now[:error] = l(:error_workflow_copy_target)
else else
WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles) Workflow.copy(@source_tracker, @source_role, @target_trackers, @target_roles)
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role
end end
@@ -119,10 +93,10 @@ class WorkflowsController < ApplicationController
private private
def find_roles def find_roles
@roles = Role.sorted.all @roles = Role.find(:all, :order => 'builtin, position')
end end
def find_trackers def find_trackers
@trackers = Tracker.sorted.all @trackers = Tracker.find(:all, :order => 'position')
end end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+2 -4
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,8 +20,6 @@
module AdminHelper module AdminHelper
def project_status_options_for_select(selected) def project_status_options_for_select(selected)
options_for_select([[l(:label_all), ''], options_for_select([[l(:label_all), ''],
[l(:project_status_active), '1'], [l(:status_active), 1]], selected)
[l(:project_status_closed), '5'],
[l(:project_status_archived), '9']], selected.to_s)
end end
end end
+159 -361
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -43,6 +43,12 @@ module ApplicationHelper
link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
end end
# Display a link to remote if user is authorized
def link_to_remote_if_authorized(name, options = {}, html_options = nil)
url = options[:url] || {}
link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
end
# Displays a link to user's account page if active # Displays a link to user's account page if active
def link_to_user(user, options={}) def link_to_user(user, options={})
if user.is_a?(User) if user.is_a?(User)
@@ -79,8 +85,8 @@ module ApplicationHelper
s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
:class => issue.css_classes, :class => issue.css_classes,
:title => title :title => title
s << h(": #{subject}") if subject s << ": #{h subject}" if subject
s = h("#{issue.project} - ") + s if options[:project] s = "#{h issue.project} - " + s if options[:project]
s s
end end
@@ -91,29 +97,21 @@ module ApplicationHelper
def link_to_attachment(attachment, options={}) def link_to_attachment(attachment, options={})
text = options.delete(:text) || attachment.filename text = options.delete(:text) || attachment.filename
action = options.delete(:download) ? 'download' : 'show' action = options.delete(:download) ? 'download' : 'show'
opt_only_path = {}
opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
options.delete(:only_path)
link_to(h(text), link_to(h(text),
{:controller => 'attachments', :action => action, {:controller => 'attachments', :action => action,
:id => attachment, :filename => attachment.filename}.merge(opt_only_path), :id => attachment, :filename => attachment.filename },
options) options)
end end
# Generates a link to a SCM revision # Generates a link to a SCM revision
# Options: # Options:
# * :text - Link text (default to the formatted revision) # * :text - Link text (default to the formatted revision)
def link_to_revision(revision, repository, options={}) def link_to_revision(revision, project, options={})
if repository.is_a?(Project)
repository = repository.repository
end
text = options.delete(:text) || format_revision(revision) text = options.delete(:text) || format_revision(revision)
rev = revision.respond_to?(:identifier) ? revision.identifier : revision rev = revision.respond_to?(:identifier) ? revision.identifier : revision
link_to(
h(text), link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
{:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev}, :title => l(:label_revision_id, format_revision(revision)))
:title => l(:label_revision_id, format_revision(revision))
)
end end
# Generates a link to a message # Generates a link to a message
@@ -122,7 +120,7 @@ module ApplicationHelper
h(truncate(message.subject, :length => 60)), h(truncate(message.subject, :length => 60)),
{ :controller => 'messages', :action => 'show', { :controller => 'messages', :action => 'show',
:board_id => message.board_id, :board_id => message.board_id,
:id => (message.parent_id || message.id), :id => message.root,
:r => (message.parent_id && message.id), :r => (message.parent_id && message.id),
:anchor => (message.parent_id ? "message-#{message.id}" : nil) :anchor => (message.parent_id ? "message-#{message.id}" : nil)
}.merge(options), }.merge(options),
@@ -139,23 +137,17 @@ module ApplicationHelper
# link_to_project(project, {}, :class => "project") # => html options with default url (project overview) # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
# #
def link_to_project(project, options={}, html_options = nil) def link_to_project(project, options={}, html_options = nil)
if project.archived? if project.active?
h(project)
else
url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
link_to(h(project), url, html_options) link_to(h(project), url, html_options)
else
h(project)
end end
end end
def thumbnail_tag(attachment)
link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
{:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
:title => attachment.filename
end
def toggle_link(name, id, options={}) def toggle_link(name, id, options={})
onclick = "$('##{id}').toggle(); " onclick = "Element.toggle('#{id}'); "
onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
onclick << "return false;" onclick << "return false;"
link_to(name, "#", :onclick => onclick) link_to(name, "#", :onclick => onclick)
end end
@@ -168,17 +160,21 @@ module ApplicationHelper
})) }))
end end
def prompt_to_remote(name, text, param, url, html_options = {})
html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
link_to name, {}, html_options
end
def format_activity_title(text) def format_activity_title(text)
h(truncate_single_line(text, :length => 100)) h(truncate_single_line(text, :length => 100))
end end
def format_activity_day(date) def format_activity_day(date)
date == User.current.today ? l(:label_today).titleize : format_date(date) date == Date.today ? l(:label_today).titleize : format_date(date)
end end
def format_activity_description(text) def format_activity_description(text)
h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
).gsub(/[\r\n]+/, "<br />").html_safe
end end
def format_version_name(version) def format_version_name(version)
@@ -195,39 +191,6 @@ module ApplicationHelper
end end
end end
# Renders a tree of projects as a nested set of unordered lists
# The given collection may be a subset of the whole project tree
# (eg. some intermediate nodes are private and can not be seen)
def render_project_nested_lists(projects)
s = ''
if projects.any?
ancestors = []
original_project = @project
projects.sort_by(&:lft).each do |project|
# set the project environment to please macros.
@project = project
if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
else
ancestors.pop
s << "</li>"
while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
ancestors.pop
s << "</ul></li>\n"
end
end
classes = (ancestors.empty? ? 'root' : 'child')
s << "<li class='#{classes}'><div class='#{classes}'>"
s << h(block_given? ? yield(project) : project.name)
s << "</div>\n"
ancestors << project
end
s << ("</li></ul>\n" * ancestors.size)
@project = original_project
end
s.html_safe
end
def render_page_hierarchy(pages, node=nil, options={}) def render_page_hierarchy(pages, node=nil, options={})
content = '' content = ''
if pages[node] if pages[node]
@@ -248,7 +211,7 @@ module ApplicationHelper
def render_flash_messages def render_flash_messages
s = '' s = ''
flash.each do |k,v| flash.each do |k,v|
s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}") s << content_tag('div', v, :class => "flash #{k}")
end end
s.html_safe s.html_safe
end end
@@ -265,24 +228,23 @@ module ApplicationHelper
# Renders the project quick-jump box # Renders the project quick-jump box
def render_project_jump_box def render_project_jump_box
return unless User.current.logged? return unless User.current.logged?
projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq projects = User.current.memberships.collect(&:project).compact.uniq
if projects.any? if projects.any?
options = s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
("<option value=''>#{ l(:label_jump_to_a_project) }</option>" + "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
'<option value="" disabled="disabled">---</option>').html_safe '<option value="" disabled="disabled">---</option>'
s << project_tree_options_for_select(projects, :selected => @project) do |p|
options << project_tree_options_for_select(projects, :selected => @project) do |p| { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
{ :value => project_path(:id => p, :jump => current_menu_item) }
end end
s << '</select>'
select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') s.html_safe
end end
end end
def project_tree_options_for_select(projects, options = {}) def project_tree_options_for_select(projects, options = {})
s = '' s = ''
project_tree(projects) do |project, level| project_tree(projects) do |project, level|
name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
tag_options = {:value => project.id} tag_options = {:value => project.id}
if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
tag_options[:selected] = 'selected' tag_options[:selected] = 'selected'
@@ -302,6 +264,30 @@ module ApplicationHelper
Project.project_tree(projects, &block) Project.project_tree(projects, &block)
end end
def project_nested_ul(projects, &block)
s = ''
if projects.any?
ancestors = []
projects.sort_by(&:lft).each do |project|
if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
s << "<ul>\n"
else
ancestors.pop
s << "</li>"
while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
ancestors.pop
s << "</ul></li>\n"
end
end
s << "<li>"
s << yield(project).to_s
ancestors << project
end
s << ("</li></ul>\n" * ancestors.size)
end
s.html_safe
end
def principals_check_box_tags(name, principals) def principals_check_box_tags(name, principals)
s = '' s = ''
principals.sort.each do |principal| principals.sort.each do |principal|
@@ -313,9 +299,6 @@ module ApplicationHelper
# Returns a string for users/groups option tags # Returns a string for users/groups option tags
def principals_options_for_select(collection, selected=nil) def principals_options_for_select(collection, selected=nil)
s = '' s = ''
if collection.include?(User.current)
s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
end
groups = '' groups = ''
collection.sort.each do |element| collection.sort.each do |element|
selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
@@ -324,7 +307,7 @@ module ApplicationHelper
unless groups.empty? unless groups.empty?
s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>) s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
end end
s.html_safe s
end end
# Truncates and returns the string as a single line # Truncates and returns the string as a single line
@@ -342,10 +325,6 @@ module ApplicationHelper
end end
end end
def anchor(text)
text.to_s.gsub(' ', '_')
end
def html_hours(text) def html_hours(text)
text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
end end
@@ -357,25 +336,18 @@ module ApplicationHelper
def time_tag(time) def time_tag(time)
text = distance_of_time_in_words(Time.now, time) text = distance_of_time_in_words(Time.now, time)
if @project if @project
link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
else else
content_tag('acronym', text, :title => format_time(time)) content_tag('acronym', text, :title => format_time(time))
end end
end end
def syntax_highlight_lines(name, content)
lines = []
syntax_highlight(name, content).each_line { |line| lines << line }
lines
end
def syntax_highlight(name, content) def syntax_highlight(name, content)
Redmine::SyntaxHighlighting.highlight_by_filename(content, name) Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
end end
def to_path_param(path) def to_path_param(path)
str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/") path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
str.blank? ? nil : str
end end
def pagination_links_full(paginator, count=nil, options={}) def pagination_links_full(paginator, count=nil, options={})
@@ -404,7 +376,7 @@ module ApplicationHelper
unless count.nil? unless count.nil?
html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
if per_page_links != false && links = per_page_links(paginator.items_per_page, count) if per_page_links != false && links = per_page_links(paginator.items_per_page)
html << " | #{links}" html << " | #{links}"
end end
end end
@@ -412,23 +384,11 @@ module ApplicationHelper
html.html_safe html.html_safe
end end
def per_page_links(selected=nil, item_count=nil) def per_page_links(selected=nil)
values = Setting.per_page_options_array links = Setting.per_page_options_array.collect do |n|
if item_count && values.any?
if item_count > values.first
max = values.detect {|value| value >= item_count} || item_count
else
max = item_count
end
values = values.select {|value| value <= max || value == selected}
end
if values.empty? || (values.size == 1 && values.first == selected)
return nil
end
links = values.collect do |n|
n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
end end
l(:label_display_per_page, links.join(', ')) links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
end end
def reorder_links(name, url, method = :post) def reorder_links(name, url, method = :post)
@@ -497,8 +457,8 @@ module ApplicationHelper
css << 'theme-' + theme.name css << 'theme-' + theme.name
end end
css << 'controller-' + controller_name css << 'controller-' + params[:controller]
css << 'action-' + action_name css << 'action-' + params[:action]
css.join(' ') css.join(' ')
end end
@@ -527,8 +487,6 @@ module ApplicationHelper
project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
only_path = options.delete(:only_path) == false ? false : true only_path = options.delete(:only_path) == false ? false : true
text = text.dup
macros = catch_macros(text)
text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
@parsed_headings = [] @parsed_headings = []
@@ -536,8 +494,8 @@ module ApplicationHelper
@current_section = 0 if options[:edit_section_links] @current_section = 0 if options[:edit_section_links]
parse_sections(text, project, obj, attr, only_path, options) parse_sections(text, project, obj, attr, only_path, options)
text = parse_non_pre_blocks(text, obj, macros) do |text| text = parse_non_pre_blocks(text) do |text|
[:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
send method_name, text, project, obj, attr, only_path, options send method_name, text, project, obj, attr, only_path, options
end end
end end
@@ -547,10 +505,10 @@ module ApplicationHelper
replace_toc(text, @parsed_headings) replace_toc(text, @parsed_headings)
end end
text.html_safe text
end end
def parse_non_pre_blocks(text, obj, macros) def parse_non_pre_blocks(text)
s = StringScanner.new(text) s = StringScanner.new(text)
tags = [] tags = []
parsed = '' parsed = ''
@@ -559,9 +517,6 @@ module ApplicationHelper
text, full_tag, closing, tag = s[1], s[2], s[3], s[4] text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
if tags.empty? if tags.empty?
yield text yield text
inject_macros(text, obj, macros) if macros.any?
else
inject_macros(text, obj, macros, false) if macros.any?
end end
parsed << text parsed << text
if tag if tag
@@ -579,7 +534,7 @@ module ApplicationHelper
while tag = tags.pop while tag = tags.pop
parsed << "</#{tag}>" parsed << "</#{tag}>"
end end
parsed parsed.html_safe
end end
def parse_inline_attachments(text, project, obj, attr, only_path, options) def parse_inline_attachments(text, project, obj, attr, only_path, options)
@@ -596,9 +551,9 @@ module ApplicationHelper
if !desc.blank? && alttext.blank? if !desc.blank? && alttext.blank?
alt = " title=\"#{desc}\" alt=\"#{desc}\"" alt = " title=\"#{desc}\" alt=\"#{desc}\""
end end
"src=\"#{image_url}\"#{alt}" "src=\"#{image_url}\"#{alt}".html_safe
else else
m m.html_safe
end end
end end
end end
@@ -642,18 +597,16 @@ module ApplicationHelper
when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
else else
wiki_page_id = page.present? ? Wiki.titleize(page) : nil wiki_page_id = page.present? ? Wiki.titleize(page) : nil
parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
:id => wiki_page_id, :anchor => anchor, :parent => parent)
end end
end end
link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
else else
# project or wiki doesn't exist # project or wiki doesn't exist
all all.html_safe
end end
else else
all all.html_safe
end end
end end
end end
@@ -691,35 +644,26 @@ module ApplicationHelper
# identifier:version:1.0.0 # identifier:version:1.0.0
# identifier:source:some/file # identifier:source:some/file
def parse_redmine_links(text, project, obj, attr, only_path, options) def parse_redmine_links(text, project, obj, attr, only_path, options)
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m| text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
link = nil link = nil
if project_identifier if project_identifier
project = Project.visible.find_by_identifier(project_identifier) project = Project.visible.find_by_identifier(project_identifier)
end end
if esc.nil? if esc.nil?
if prefix.nil? && sep == 'r' if prefix.nil? && sep == 'r'
if project
repository = nil
if repo_identifier
repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
else
repository = project.repository
end
# project.changesets.visible raises an SQL error because of a double join on repositories # project.changesets.visible raises an SQL error because of a double join on repositories
if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision}, link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
:class => 'changeset', :class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100)) :title => truncate_single_line(changeset.comments, :length => 100))
end end
end
elsif sep == '#' elsif sep == '#'
oid = identifier.to_i oid = identifier.to_i
case prefix case prefix
when nil when nil
if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) if issue = Issue.visible.find_by_id(oid, :include => :status)
anchor = comment_id ? "note-#{comment_id}" : nil link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
:class => issue.css_classes, :class => issue.css_classes,
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
end end
@@ -776,34 +720,23 @@ module ApplicationHelper
link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
:class => 'news' :class => 'news'
end end
when 'commit', 'source', 'export' when 'commit'
if project if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
repository = nil link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
repo_prefix, repo_identifier, name = $1, $2, $3
repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
else
repository = project.repository
end
if prefix == 'commit'
if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
:class => 'changeset', :class => 'changeset',
:title => truncate_single_line(h(changeset.comments), :length => 100) :title => truncate_single_line(h(changeset.comments), :length => 100)
end end
else when 'source', 'export'
if repository && User.current.allowed_to?(:browse_repository, project) if project && project.repository && User.current.allowed_to?(:browse_repository, project)
name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
path, rev, anchor = $1, $3, $5 path, rev, anchor = $1, $3, $5
link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param, link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
:path => to_path_param(path), :path => to_path_param(path),
:rev => rev, :rev => rev,
:anchor => anchor}, :anchor => anchor,
:format => (prefix == 'export' ? 'raw' : nil)},
:class => (prefix == 'export' ? 'source download' : 'source') :class => (prefix == 'export' ? 'source download' : 'source')
end end
end
repo_prefix = nil
end
when 'attachment' when 'attachment'
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
if attachments && attachment = attachments.detect {|a| a.filename == name } if attachments && attachment = attachments.detect {|a| a.filename == name }
@@ -817,24 +750,23 @@ module ApplicationHelper
end end
end end
end end
(leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")) (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
end end
end end
HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE) HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
def parse_sections(text, project, obj, attr, only_path, options) def parse_sections(text, project, obj, attr, only_path, options)
return unless options[:edit_section_links] return unless options[:edit_section_links]
text.gsub!(HEADING_RE) do text.gsub!(HEADING_RE) do
heading = $1
@current_section += 1 @current_section += 1
if @current_section > 1 if @current_section > 1
content_tag('div', content_tag('div',
link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
:class => 'contextual', :class => 'contextual',
:title => l(:button_edit_section)) + heading.html_safe :title => l(:button_edit_section)) + $1
else else
heading $1
end end
end end
end end
@@ -860,57 +792,31 @@ module ApplicationHelper
end end
end end
MACROS_RE = /( MACROS_RE = /
(!)? # escaping (!)? # escaping
( (
\{\{ # opening tag \{\{ # opening tag
([\w]+) # macro name ([\w]+) # macro name
(\(([^\n\r]*?)\))? # optional arguments (\(([^\}]*)\))? # optional arguments
([\n\r].*?[\n\r])? # optional block of text
\}\} # closing tag \}\} # closing tag
) )
)/mx unless const_defined?(:MACROS_RE) /x unless const_defined?(:MACROS_RE)
MACRO_SUB_RE = /( # Macros substitution
\{\{ def parse_macros(text, project, obj, attr, only_path, options)
macro\((\d+)\)
\}\}
)/x unless const_defined?(:MACRO_SUB_RE)
# Extracts macros from text
def catch_macros(text)
macros = {}
text.gsub!(MACROS_RE) do text.gsub!(MACROS_RE) do
all, macro = $1, $4.downcase esc, all, macro = $1, $2, $3.downcase
if macro_exists?(macro) || all =~ MACRO_SUB_RE args = ($5 || '').split(',').each(&:strip)
index = macros.size if esc.nil?
macros[index] = all begin
"{{macro(#{index})}}" exec_macro(macro, obj, args)
rescue => e
"<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
end || all
else else
all all
end end
end end
macros
end
# Executes and replaces macros in text
def inject_macros(text, obj, macros, execute=true)
text.gsub!(MACRO_SUB_RE) do
all, index = $1, $2.to_i
orig = macros.delete(index)
if execute && orig && orig =~ MACROS_RE
esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
if esc.nil?
h(exec_macro(macro, obj, args, block) || all)
else
h(all)
end
elsif orig
h(orig)
else
h(all)
end
end
end end
TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
@@ -918,8 +824,6 @@ module ApplicationHelper
# Renders the TOC with given headings # Renders the TOC with given headings
def replace_toc(text, headings) def replace_toc(text, headings)
text.gsub!(TOC_RE) do text.gsub!(TOC_RE) do
# Keep only the 4 first levels
headings = headings.select{|level, anchor, item| level <= 4}
if headings.empty? if headings.empty?
'' ''
else else
@@ -967,83 +871,26 @@ module ApplicationHelper
content_tag("label", label_text) content_tag("label", label_text)
end end
def labelled_tabular_form_for(*args, &proc)
args << {} unless args.last.is_a?(Hash)
options = args.last
options[:html] ||= {}
options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
options.merge!({:builder => TabularFormBuilder})
form_for(*args, &proc)
end
def labelled_form_for(*args, &proc) def labelled_form_for(*args, &proc)
args << {} unless args.last.is_a?(Hash) args << {} unless args.last.is_a?(Hash)
options = args.last options = args.last
if args.first.is_a?(Symbol) options.merge!({:builder => TabularFormBuilder})
options.merge!(:as => args.shift)
end
options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
form_for(*args, &proc) form_for(*args, &proc)
end end
def labelled_fields_for(*args, &proc)
args << {} unless args.last.is_a?(Hash)
options = args.last
options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
fields_for(*args, &proc)
end
def labelled_remote_form_for(*args, &proc)
ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
args << {} unless args.last.is_a?(Hash)
options = args.last
options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
form_for(*args, &proc)
end
def error_messages_for(*objects)
html = ""
objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
errors = objects.map {|o| o.errors.full_messages}.flatten
if errors.any?
html << "<div id='errorExplanation'><ul>\n"
errors.each do |error|
html << "<li>#{h error}</li>\n"
end
html << "</ul></div>\n"
end
html.html_safe
end
def delete_link(url, options={})
options = {
:method => :delete,
:data => {:confirm => l(:text_are_you_sure)},
:class => 'icon icon-del'
}.merge(options)
link_to l(:button_delete), url, options
end
def preview_link(url, form, target='preview', options={})
content_tag 'a', l(:label_preview), {
:href => "#",
:onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
:accesskey => accesskey(:preview)
}.merge(options)
end
def link_to_function(name, function, html_options={})
content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
end
# Helper to render JSON in views
def raw_json(arg)
arg.to_json.to_s.gsub('/', '\/').html_safe
end
def back_url
url = params[:back_url]
if url.nil? && referer = request.env['HTTP_REFERER']
url = CGI.unescape(referer.to_s)
end
url
end
def back_url_hidden_field_tag def back_url_hidden_field_tag
url = back_url back_url = params[:back_url] || request.env['HTTP_REFERER']
hidden_field_tag('back_url', url, :id => nil) unless url.blank? back_url = CGI.unescape(back_url.to_s)
hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
end end
def check_all_links(form_name) def check_all_links(form_name)
@@ -1087,90 +934,54 @@ module ApplicationHelper
end end
@context_menu_included = true @context_menu_included = true
end end
javascript_tag "contextMenuInit('#{ url_for(url) }')" javascript_tag "new ContextMenu('#{ url_for(url) }')"
end
def context_menu_link(name, url, options={})
options[:class] ||= ''
if options.delete(:selected)
options[:class] << ' icon-checked disabled'
options[:disabled] = true
end
if options.delete(:disabled)
options.delete(:method)
options.delete(:confirm)
options.delete(:onclick)
options[:class] << ' disabled'
url = '#'
end
link_to h(name), url, options
end end
def calendar_for(field_id) def calendar_for(field_id)
include_calendar_headers_tags include_calendar_headers_tags
javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });") image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
end end
def include_calendar_headers_tags def include_calendar_headers_tags
unless @calendar_headers_tags_included unless @calendar_headers_tags_included
@calendar_headers_tags_included = true @calendar_headers_tags_included = true
content_for :header_tags do content_for :header_tags do
start_of_week = Setting.start_of_week start_of_week = case Setting.start_of_week.to_i
start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank? when 1
# Redmine uses 1..7 (monday..sunday) in settings and locales 'Calendar._FD = 1;' # Monday
# JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 when 7
start_of_week = start_of_week.to_i % 7 'Calendar._FD = 0;' # Sunday
when 6
tags = javascript_tag( 'Calendar._FD = 6;' # Saturday
"var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
"showOn: 'button', buttonImageOnly: true, buttonImage: '" +
path_to_image('/images/calendar.png') +
"', showButtonPanel: true};")
jquery_locale = l('jquery.locale', :default => current_language.to_s)
unless jquery_locale == 'en'
tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
end
tags
end
end
end
# Overrides Rails' stylesheet_link_tag with themes and plugins support.
# Examples:
# stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
# stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
#
def stylesheet_link_tag(*sources)
options = sources.last.is_a?(Hash) ? sources.pop : {}
plugin = options.delete(:plugin)
sources = sources.map do |source|
if plugin
"/plugin_assets/#{plugin}/stylesheets/#{source}"
elsif current_theme && current_theme.stylesheets.include?(source)
current_theme.stylesheet_path(source)
else else
source '' # use language
end
end
super sources, options
end end
# Overrides Rails' image_tag with themes and plugins support. javascript_include_tag('calendar/calendar') +
# Examples: javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
# image_tag('image.png') # => picks image.png from the current theme or defaults javascript_tag(start_of_week) +
# image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets javascript_include_tag('calendar/calendar-setup') +
# stylesheet_link_tag('calendar')
def image_tag(source, options={})
if plugin = options.delete(:plugin)
source = "/plugin_assets/#{plugin}/images/#{source}"
elsif current_theme && current_theme.images.include?(source)
source = current_theme.image_path(source)
end
super source, options
end
# Overrides Rails' javascript_include_tag with plugins support
# Examples:
# javascript_include_tag('scripts') # => picks scripts.js from defaults
# javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
#
def javascript_include_tag(*sources)
options = sources.last.is_a?(Hash) ? sources.pop : {}
if plugin = options.delete(:plugin)
sources = sources.map do |source|
if plugin
"/plugin_assets/#{plugin}/javascripts/#{source}"
else
source
end end
end end
end end
super sources, options
end
def content_for(name, content = nil, &block) def content_for(name, content = nil, &block)
@has_content ||= {} @has_content ||= {}
@@ -1182,14 +993,6 @@ module ApplicationHelper
(@has_content && @has_content[name]) || false (@has_content && @has_content[name]) || false
end end
def sidebar_content?
has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
end
def view_layouts_base_sidebar_hook_response
@view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
end
def email_delivery_enabled? def email_delivery_enabled?
!!ActionMailer::Base.perform_deliveries !!ActionMailer::Base.perform_deliveries
end end
@@ -1198,7 +1001,7 @@ module ApplicationHelper
# +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
def avatar(user, options = { }) def avatar(user, options = { })
if Setting.gravatar_enabled? if Setting.gravatar_enabled?
options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default}) options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
email = nil email = nil
if user.respond_to?(:mail) if user.respond_to?(:mail)
email = user.mail email = user.mail
@@ -1212,19 +1015,14 @@ module ApplicationHelper
end end
def sanitize_anchor_name(anchor) def sanitize_anchor_name(anchor)
if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
else
# TODO: remove when ruby1.8 is no longer supported
anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
end end
end
# Returns the javascript tags that are included in the html layout head # Returns the javascript tags that are included in the html layout head
def javascript_heads def javascript_heads
tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application') tags = javascript_include_tag(:defaults)
unless User.current.pref.warn_on_leaving_unsaved == '0' unless User.current.pref.warn_on_leaving_unsaved == '0'
tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
end end
tags tags
end end
+3 -5
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -21,14 +21,12 @@ module AttachmentsHelper
# Displays view/delete links to the attachments of the given object # Displays view/delete links to the attachments of the given object
# Options: # Options:
# :author -- author names are not displayed if set to false # :author -- author names are not displayed if set to false
# :thumbails -- display thumbnails if enabled in settings
def link_to_attachments(container, options = {}) def link_to_attachments(container, options = {})
options.assert_valid_keys(:author, :thumbnails) options.assert_valid_keys(:author)
if container.attachments.any? if container.attachments.any?
options = {:deletable => container.attachments_deletable?, :author => true}.merge(options) options = {:deletable => container.attachments_deletable?, :author => true}.merge(options)
render :partial => 'attachments/links', render :partial => 'attachments/links', :locals => {:attachments => container.attachments, :options => options}
:locals => {:attachments => container.attachments, :options => options, :thumbnails => (options[:thumbnails] && Setting.thumbnails_enabled?)}
end end
end end
+1 -4
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,7 +18,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module AuthSourcesHelper module AuthSourcesHelper
def auth_source_partial_name(auth_source)
"form_#{auth_source.class.name.underscore}"
end
end end
+1 -21
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,24 +18,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module BoardsHelper module BoardsHelper
def board_breadcrumb(item)
board = item.is_a?(Message) ? item.board : item
links = [link_to(l(:label_board_plural), project_boards_path(item.project))]
boards = board.ancestors.reverse
if item.is_a?(Message)
boards << board
end
links += boards.map {|ancestor| link_to(h(ancestor.name), project_board_path(ancestor.project, ancestor))}
breadcrumb links
end
def boards_options_for_select(boards)
options = []
Board.board_tree(boards) do |board, level|
label = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
label << board.name
options << [label, board.id]
end
options
end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
-43
View File
@@ -1,43 +0,0 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ContextMenusHelper
def context_menu_link(name, url, options={})
options[:class] ||= ''
if options.delete(:selected)
options[:class] << ' icon-checked disabled'
options[:disabled] = true
end
if options.delete(:disabled)
options.delete(:method)
options.delete(:data)
options[:onclick] = 'return false;'
options[:class] << ' disabled'
url = '#'
end
link_to h(name), url, options
end
def bulk_update_custom_field_context_menu_link(field, text, value)
context_menu_link h(text),
{:controller => 'issues', :action => 'bulk_update', :ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back},
:method => :post,
:selected => (@issue && @issue.custom_field_value(field) == value)
end
end
+22 -60
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -36,82 +36,58 @@ module CustomFieldsHelper
def custom_field_tag(name, custom_value) def custom_field_tag(name, custom_value)
custom_field = custom_value.custom_field custom_field = custom_value.custom_field
field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
field_id = "#{name}_custom_field_values_#{custom_field.id}" field_id = "#{name}_custom_field_values_#{custom_field.id}"
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.try(:edit_as)
when "date" when "date"
text_field_tag(field_name, custom_value.value, tag_options.merge(:size => 10)) + text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
calendar_for(field_id) calendar_for(field_id)
when "text" when "text"
text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3)) text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
when "bool" when "bool"
hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options) hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, :id => field_id)
when "list" when "list"
blank_option = ''.html_safe blank_option = custom_field.is_required? ?
unless custom_field.multiple? (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
if custom_field.is_required? '<option></option>'
unless custom_field.default_value.present? select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id)
blank_option = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '')
end
else else
blank_option = content_tag('option') text_field_tag(field_name, custom_value.value, :id => field_id)
end
end
s = select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value),
tag_options.merge(:multiple => custom_field.multiple?))
if custom_field.multiple?
s << hidden_field_tag(field_name, '')
end
s
else
text_field_tag(field_name, custom_value.value, tag_options)
end end
end end
# Return custom field label tag # Return custom field label tag
def custom_field_label_tag(name, custom_value, options={}) def custom_field_label_tag(name, custom_value)
required = options[:required] || custom_value.custom_field.is_required?
content_tag "label", h(custom_value.custom_field.name) + content_tag "label", h(custom_value.custom_field.name) +
(required ? " <span class=\"required\">*</span>".html_safe : ""), (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>".html_safe : ""),
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}" :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
:class => (custom_value.errors.empty? ? nil : "error" )
end end
# Return custom field tag with its label tag # Return custom field tag with its label tag
def custom_field_tag_with_label(name, custom_value, options={}) def custom_field_tag_with_label(name, custom_value)
custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value) custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
end end
def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil) def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil)
field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
field_id = "#{name}_custom_field_values_#{custom_field.id}" field_id = "#{name}_custom_field_values_#{custom_field.id}"
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.try(:edit_as)
when "date" when "date"
text_field_tag(field_name, '', tag_options.merge(:size => 10)) + text_field_tag(field_name, '', :id => field_id, :size => 10) +
calendar_for(field_id) calendar_for(field_id)
when "text" when "text"
text_area_tag(field_name, '', tag_options.merge(:rows => 3)) text_area_tag(field_name, '', :id => field_id, :rows => 3, :style => 'width:90%')
when "bool" when "bool"
select_tag(field_name, options_for_select([[l(:label_no_change_option), ''], select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
[l(:general_text_yes), '1'], [l(:general_text_yes), '1'],
[l(:general_text_no), '0']]), tag_options) [l(:general_text_no), '0']]), :id => field_id)
when "list" when "list"
options = [] select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values_options(projects)), :id => field_id)
options << [l(:label_no_change_option), ''] unless custom_field.multiple?
options << [l(:label_none), '__none__'] unless custom_field.is_required?
options += custom_field.possible_values_options(projects)
select_tag(field_name, options_for_select(options), tag_options.merge(:multiple => custom_field.multiple?))
else else
text_field_tag(field_name, '', tag_options) text_field_tag(field_name, '', :id => field_id)
end end
end end
@@ -123,11 +99,7 @@ module CustomFieldsHelper
# Return a string used to display a custom value # Return a string used to display a custom value
def format_value(value, field_format) def format_value(value, field_format)
if value.is_a?(Array) Redmine::CustomFieldFormat.format_value(value, field_format) # Proxy
value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ')
else
Redmine::CustomFieldFormat.format_value(value, field_format)
end
end end
# Return an array of custom field formats which can be used in select_tag # Return an array of custom field formats which can be used in select_tag
@@ -139,20 +111,10 @@ module CustomFieldsHelper
def render_api_custom_values(custom_values, api) def render_api_custom_values(custom_values, api)
api.array :custom_fields do api.array :custom_fields do
custom_values.each do |custom_value| custom_values.each do |custom_value|
attrs = {:id => custom_value.custom_field_id, :name => custom_value.custom_field.name} api.custom_field :id => custom_value.custom_field_id, :name => custom_value.custom_field.name do
attrs.merge!(:multiple => true) if custom_value.custom_field.multiple?
api.custom_field attrs do
if custom_value.value.is_a?(Array)
api.array :value do
custom_value.value.each do |value|
api.value value unless value.blank?
end
end
else
api.value custom_value.value api.value custom_value.value
end end
end end
end
end unless custom_values.empty? end unless custom_values.empty?
end end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+4
View File
@@ -0,0 +1,4 @@
# encoding: utf-8
#
module IssueMovesHelper
end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+41 -133
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -48,13 +48,13 @@ module IssuesHelper
@cached_label_priority ||= l(:field_priority) @cached_label_priority ||= l(:field_priority)
@cached_label_project ||= l(:field_project) @cached_label_project ||= l(:field_project)
link_to_issue(issue) + "<br /><br />".html_safe + (link_to_issue(issue) + "<br /><br />" +
"<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe + "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />" +
"<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe + "<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />" +
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe + "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe + "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
"<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe + "<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />" +
"<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe "<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}").html_safe
end end
def issue_heading(issue) def issue_heading(issue)
@@ -88,52 +88,10 @@ module IssuesHelper
content_tag('td', progress_bar(child.done_ratio, :width => '80px')), content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
:class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}") :class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
end end
s << '</table></form>' s << '</form></table>'
s.html_safe s.html_safe
end end
class IssueFieldsRows
include ActionView::Helpers::TagHelper
def initialize
@left = []
@right = []
end
def left(*args)
args.any? ? @left << cells(*args) : @left
end
def right(*args)
args.any? ? @right << cells(*args) : @right
end
def size
@left.size > @right.size ? @left.size : @right.size
end
def to_html
html = ''.html_safe
blank = content_tag('th', '') + content_tag('td', '')
size.times do |i|
left = @left[i] || blank
right = @right[i] || blank
html << content_tag('tr', left + right)
end
html
end
def cells(label, text, options={})
content_tag('th', "#{label}:", options) + content_tag('td', text, options)
end
end
def issue_fields_rows
r = IssueFieldsRows.new
yield r
r.to_html
end
def render_custom_fields_rows(issue) def render_custom_fields_rows(issue)
return if issue.custom_field_values.empty? return if issue.custom_field_values.empty?
ordered_values = [] ordered_values = []
@@ -173,11 +131,14 @@ module IssuesHelper
def sidebar_queries def sidebar_queries
unless @sidebar_queries unless @sidebar_queries
@sidebar_queries = Query.visible.all( # User can see public queries and his own queries
:order => "#{Query.table_name}.name ASC", visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
# Project specific queries and global queries # Project specific queries and global queries
:conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]) visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
) @sidebar_queries = Query.find(:all,
:select => 'id, name, is_public',
:order => "name ASC",
:conditions => visible.conditions)
end end
@sidebar_queries @sidebar_queries
end end
@@ -188,14 +149,12 @@ module IssuesHelper
content_tag('h3', h(title)) + content_tag('h3', h(title)) +
queries.collect {|query| queries.collect {|query|
css = 'query' link_to(h(query.name), url_params.merge(:query_id => query))
css << ' selected' if query == @query }.join('<br />')
link_to(h(query.name), url_params.merge(:query_id => query), :class => css)
}.join('<br />').html_safe
end end
def render_sidebar_queries def render_sidebar_queries
out = ''.html_safe out = ''
queries = sidebar_queries.select {|q| !q.is_public?} queries = sidebar_queries.select {|q| !q.is_public?}
out << query_links(l(:label_my_queries), queries) if queries.any? out << query_links(l(:label_my_queries), queries) if queries.any?
queries = sidebar_queries.select {|q| q.is_public?} queries = sidebar_queries.select {|q| q.is_public?}
@@ -203,76 +162,36 @@ module IssuesHelper
out out
end end
# Returns the textual representation of a journal details def show_detail(detail, no_html=false)
# as an array of strings
def details_to_strings(details, no_html=false, options={})
options[:only_path] = (options[:only_path] == false ? false : true)
strings = []
values_by_field = {}
details.each do |detail|
if detail.property == 'cf'
field_id = detail.prop_key
field = CustomField.find_by_id(field_id)
if field && field.multiple?
values_by_field[field_id] ||= {:added => [], :deleted => []}
if detail.old_value
values_by_field[field_id][:deleted] << detail.old_value
end
if detail.value
values_by_field[field_id][:added] << detail.value
end
next
end
end
strings << show_detail(detail, no_html, options)
end
values_by_field.each do |field_id, changes|
detail = JournalDetail.new(:property => 'cf', :prop_key => field_id)
if changes[:added].any?
detail.value = changes[:added]
strings << show_detail(detail, no_html, options)
elsif changes[:deleted].any?
detail.old_value = changes[:deleted]
strings << show_detail(detail, no_html, options)
end
end
strings
end
# Returns the textual representation of a single journal detail
def show_detail(detail, no_html=false, options={})
multiple = false
case detail.property case detail.property
when 'attr' when 'attr'
field = detail.prop_key.to_s.gsub(/\_id$/, "") field = detail.prop_key.to_s.gsub(/\_id$/, "")
label = l(("field_" + field).to_sym) label = l(("field_" + field).to_sym)
case detail.prop_key case
when 'due_date', 'start_date' when ['due_date', 'start_date'].include?(detail.prop_key)
value = format_date(detail.value.to_date) if detail.value value = format_date(detail.value.to_date) if detail.value
old_value = format_date(detail.old_value.to_date) if detail.old_value old_value = format_date(detail.old_value.to_date) if detail.old_value
when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id', when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
'priority_id', 'category_id', 'fixed_version_id'
value = find_name_by_reflection(field, detail.value) value = find_name_by_reflection(field, detail.value)
old_value = find_name_by_reflection(field, detail.old_value) old_value = find_name_by_reflection(field, detail.old_value)
when 'estimated_hours' when detail.prop_key == 'estimated_hours'
value = "%0.02f" % detail.value.to_f unless detail.value.blank? value = "%0.02f" % detail.value.to_f unless detail.value.blank?
old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank? old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
when 'parent_id' when detail.prop_key == 'parent_id'
label = l(:field_parent_issue) label = l(:field_parent_issue)
value = "##{detail.value}" unless detail.value.blank? value = "##{detail.value}" unless detail.value.blank?
old_value = "##{detail.old_value}" unless detail.old_value.blank? old_value = "##{detail.old_value}" unless detail.old_value.blank?
when 'is_private' when detail.prop_key == 'is_private'
value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank? value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank? old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
end end
when 'cf' when 'cf'
custom_field = CustomField.find_by_id(detail.prop_key) custom_field = CustomField.find_by_id(detail.prop_key)
if custom_field if custom_field
multiple = custom_field.multiple?
label = custom_field.name label = custom_field.name
value = format_value(detail.value, custom_field.field_format) if detail.value value = format_value(detail.value, custom_field.field_format) if detail.value
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
@@ -280,8 +199,7 @@ module IssuesHelper
when 'attachment' when 'attachment'
label = l(:label_attachment) label = l(:label_attachment)
end end
call_hook(:helper_issues_show_detail_after_setting, call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
{:detail => detail, :label => label, :value => value, :old_value => old_value })
label ||= detail.prop_key label ||= detail.prop_key
value ||= detail.value value ||= detail.value
@@ -290,17 +208,10 @@ module IssuesHelper
unless no_html unless no_html
label = content_tag('strong', label) label = content_tag('strong', label)
old_value = content_tag("i", h(old_value)) if detail.old_value old_value = content_tag("i", h(old_value)) if detail.old_value
old_value = content_tag("del", old_value) if detail.old_value and detail.value.blank? old_value = content_tag("strike", old_value) if detail.old_value and detail.value.blank?
if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key) if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
# Link to the attachment if it has not been removed # Link to the attachment if it has not been removed
value = link_to_attachment(atta, :download => true, :only_path => options[:only_path]) value = link_to_attachment(a)
if options[:only_path] != false && atta.is_text?
value += link_to(
image_tag('magnifier.png'),
:controller => 'attachments', :action => 'show',
:id => atta, :filename => atta.filename
)
end
else else
value = content_tag("i", h(value)) if value value = content_tag("i", h(value)) if value
end end
@@ -310,27 +221,24 @@ module IssuesHelper
s = l(:text_journal_changed_no_detail, :label => label) s = l(:text_journal_changed_no_detail, :label => label)
unless no_html unless no_html
diff_link = link_to 'diff', diff_link = link_to 'diff',
{:controller => 'journals', :action => 'diff', :id => detail.journal_id, {:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id},
:detail_id => detail.id, :only_path => options[:only_path]},
:title => l(:label_view_diff) :title => l(:label_view_diff)
s << " (#{ diff_link })" s << " (#{ diff_link })"
end end
s.html_safe s
elsif detail.value.present? elsif !detail.value.blank?
case detail.property case detail.property
when 'attr', 'cf' when 'attr', 'cf'
if detail.old_value.present? if !detail.old_value.blank?
l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe l(:text_journal_changed, :label => label, :old => old_value, :new => value)
elsif multiple
l(:text_journal_added, :label => label, :value => value).html_safe
else else
l(:text_journal_set_to, :label => label, :value => value).html_safe l(:text_journal_set_to, :label => label, :value => value)
end end
when 'attachment' when 'attachment'
l(:text_journal_added, :label => label, :value => value).html_safe l(:text_journal_added, :label => label, :value => value)
end end
else else
l(:text_journal_deleted, :label => label, :old => old_value).html_safe l(:text_journal_deleted, :label => label, :old => old_value)
end end
end end
@@ -371,16 +279,16 @@ module IssuesHelper
issues.each do |issue| issues.each do |issue|
col_values = columns.collect do |column| col_values = columns.collect do |column|
s = if column.is_a?(QueryCustomFieldColumn) s = if column.is_a?(QueryCustomFieldColumn)
cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
show_value(cv) show_value(cv)
else else
value = column.value(issue) value = issue.send(column.name)
if value.is_a?(Date) if value.is_a?(Date)
format_date(value) format_date(value)
elsif value.is_a?(Time) elsif value.is_a?(Time)
format_time(value) format_time(value)
elsif value.is_a?(Float) elsif value.is_a?(Float)
("%.2f" % value).gsub('.', decimal_separator) value.to_s.gsub('.', decimal_separator)
else else
value value
end end
+7 -9
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -23,24 +23,22 @@ module JournalsHelper
editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project))) editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project)))
links = [] links = []
if !journal.notes.blank? if !journal.notes.blank?
links << link_to(image_tag('comment.png'), links << link_to_remote(image_tag('comment.png'),
{:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal}, { :url => {:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal} },
:remote => true,
:method => 'post',
:title => l(:button_quote)) if options[:reply_links] :title => l(:button_quote)) if options[:reply_links]
links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes", links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes",
{ :controller => 'journals', :action => 'edit', :id => journal, :format => 'js' }, { :controller => 'journals', :action => 'edit', :id => journal },
:title => l(:button_edit)) if editable :title => l(:button_edit)) if editable
end end
content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty? content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty?
content << textilizable(journal, :notes) content << textilizable(journal, :notes)
css_classes = "wiki" css_classes = "wiki"
css_classes << " editable" if editable css_classes << " editable" if editable
content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes) content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => css_classes)
end end
def link_to_in_place_notes_editor(text, field_id, url, options={}) def link_to_in_place_notes_editor(text, field_id, url, options={})
onclick = "$.ajax({url: '#{url_for(url)}', type: 'get'}); return false;" onclick = "new Ajax.Request('#{url_for(url)}', {asynchronous:true, evalScripts:true, method:'get'}); return false;"
link_to text, '#', options.merge(:onclick => onclick) link_to text, '#', options.merge(:onclick => onclick)
end end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+35 -8
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -30,7 +30,7 @@ module ProjectsHelper
{:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural}, {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
{:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural}, {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
{:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki}, {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
{:name => 'repositories', :action => :manage_repository, :partial => 'projects/settings/repositories', :label => :label_repository_plural}, {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
{:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}, {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural},
{:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities} {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
] ]
@@ -51,15 +51,38 @@ module ProjectsHelper
content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id') content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id')
end end
# Renders the projects index # Renders a tree of projects as a nested set of unordered lists
# The given collection may be a subset of the whole project tree
# (eg. some intermediate nodes are private and can not be seen)
def render_project_hierarchy(projects) def render_project_hierarchy(projects)
render_project_nested_lists(projects) do |project| s = ''
s = link_to_project(project, {}, :class => "#{project.css_classes} #{User.current.member_of?(project) ? 'my-project' : nil}") if projects.any?
if project.description.present? ancestors = []
s << content_tag('div', textilizable(project.short_description, :project => project), :class => 'wiki description') original_project = @project
projects.each do |project|
# set the project environment to please macros.
@project = project
if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
else
ancestors.pop
s << "</li>"
while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
ancestors.pop
s << "</ul></li>\n"
end end
s
end end
classes = (ancestors.empty? ? 'root' : 'child')
s << "<li class='#{classes}'><div class='#{classes}'>" +
link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
s << "</div>\n"
ancestors << project
end
s << ("</li></ul>\n" * ancestors.size)
@project = original_project
end
s.html_safe
end end
# Returns a set of options for a select field, grouped by project. # Returns a set of options for a select field, grouped by project.
@@ -68,6 +91,10 @@ module ProjectsHelper
versions.each do |version| versions.each do |version|
grouped[version.project.name] << [version.name, version.id] grouped[version.project.name] << [version.name, version.id]
end end
# Add in the selected
if selected && !versions.include?(selected)
grouped[selected.project.name] << [selected.name, selected.id]
end
if grouped.keys.size > 1 if grouped.keys.size > 1
grouped_options_for_select(grouped, selected && selected.id) grouped_options_for_select(grouped, selected && selected.id)
+4 -33
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,12 +18,9 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module QueriesHelper module QueriesHelper
def filters_options_for_select(query)
options = [[]] def operators_for_select(filter_type)
options += query.available_filters.sort {|a,b| a[1][:order] <=> b[1][:order]}.map do |field, field_options| Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
[field_options[:name], field]
end
options_for_select(options)
end end
def column_header(column) def column_header(column)
@@ -34,14 +31,7 @@ module QueriesHelper
def column_content(column, issue) def column_content(column, issue)
value = column.value(issue) value = column.value(issue)
if value.is_a?(Array)
value.collect {|v| column_value(column, issue, v)}.compact.sort.join(', ').html_safe
else
column_value(column, issue, value)
end
end
def column_value(column, issue, value)
case value.class.name case value.class.name
when 'String' when 'String'
if column.name == :subject if column.name == :subject
@@ -56,8 +46,6 @@ module QueriesHelper
when 'Fixnum', 'Float' when 'Fixnum', 'Float'
if column.name == :done_ratio if column.name == :done_ratio
progress_bar(value, :width => '80px') progress_bar(value, :width => '80px')
elsif column.name == :spent_hours
sprintf "%.2f", value
else else
h(value.to_s) h(value.to_s)
end end
@@ -102,23 +90,6 @@ module QueriesHelper
end end
end end
def retrieve_query_from_session
if session[:query]
if session[:query][:id]
@query = Query.find_by_id(session[:query][:id])
return unless @query
else
@query = Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
end
if session[:query].has_key?(:project_id)
@query.project_id = session[:query][:project_id]
else
@query.project = @project
end
@query
end
end
def build_query_from_params def build_query_from_params
if params[:fields] || params[:f] if params[:fields] || params[:f]
@query.filters = {} @query.filters = {}
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+63 -63
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -46,17 +46,17 @@ module RepositoriesHelper
end end
def render_changeset_changes def render_changeset_changes
changes = @changeset.filechanges.find(:all, :limit => 1000, :order => 'path').collect do |change| changes = @changeset.changes.find(:all, :limit => 1000, :order => 'path').collect do |change|
case change.action case change.action
when 'A' when 'A'
# Detects moved/copied files # Detects moved/copied files
if !change.from_path.blank? if !change.from_path.blank?
change.action = change.action =
@changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C' @changeset.changes.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
end end
change change
when 'D' when 'D'
@changeset.filechanges.detect {|c| c.from_path == change.path} ? nil : change @changeset.changes.detect {|c| c.from_path == change.path} ? nil : change
else else
change change
end end
@@ -92,26 +92,22 @@ module RepositoriesHelper
text = link_to(h(text), :controller => 'repositories', text = link_to(h(text), :controller => 'repositories',
:action => 'show', :action => 'show',
:id => @project, :id => @project,
:repository_id => @repository.identifier_param,
:path => path_param, :path => path_param,
:rev => @changeset.identifier) :rev => @changeset.identifier)
output << "<li class='#{style}'>#{text}" output << "<li class='#{style}'>#{text}</li>"
output << render_changes_tree(s) output << render_changes_tree(s)
output << "</li>"
elsif c = tree[file][:c] elsif c = tree[file][:c]
style << " change-#{c.action}" style << " change-#{c.action}"
path_param = to_path_param(@repository.relative_path(c.path)) path_param = to_path_param(@repository.relative_path(c.path))
text = link_to(h(text), :controller => 'repositories', text = link_to(h(text), :controller => 'repositories',
:action => 'entry', :action => 'entry',
:id => @project, :id => @project,
:repository_id => @repository.identifier_param,
:path => path_param, :path => path_param,
:rev => @changeset.identifier) unless c.action == 'D' :rev => @changeset.identifier) unless c.action == 'D'
text << " - #{h(c.revision)}" unless c.revision.blank? text << " - #{h(c.revision)}" unless c.revision.blank?
text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories', text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories',
:action => 'diff', :action => 'diff',
:id => @project, :id => @project,
:repository_id => @repository.identifier_param,
:path => path_param, :path => path_param,
:rev => @changeset.identifier) + ') '.html_safe if c.action == 'M' :rev => @changeset.identifier) + ') '.html_safe if c.action == 'M'
text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank? text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank?
@@ -141,7 +137,15 @@ module RepositoriesHelper
select_tag('repository_scm', select_tag('repository_scm',
options_for_select(scm_options, repository.class.name.demodulize), options_for_select(scm_options, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?), :disabled => (repository && !repository.new_record?),
:data => {:remote => true, :method => 'get'}) :onchange => remote_function(
:url => {
:controller => 'repositories',
:action => 'edit',
:id => @project
},
:method => :get,
:with => "Form.serialize(this.form)")
)
end end
def with_leading_slash(path) def with_leading_slash(path)
@@ -154,7 +158,7 @@ module RepositoriesHelper
def subversion_field_tags(form, repository) def subversion_field_tags(form, repository)
content_tag('p', form.text_field(:url, :size => 60, :required => true, content_tag('p', form.text_field(:url, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')) + :disabled => (repository && !repository.root_url.blank?)) +
'<br />'.html_safe + '<br />'.html_safe +
'(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') + '(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
content_tag('p', form.text_field(:login, :size => 30)) + content_tag('p', form.text_field(:login, :size => 30)) +
@@ -169,7 +173,7 @@ module RepositoriesHelper
content_tag('p', form.text_field( content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository), :url, :label => l(:field_path_to_repository),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url'))) + :disabled => (repository && !repository.new_record?))) +
content_tag('p', form.select( content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS, :log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true)) :label => l(:field_commit_logs_encoding), :required => true))
@@ -179,7 +183,7 @@ module RepositoriesHelper
content_tag('p', form.text_field( content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository), :url, :label => l(:field_path_to_repository),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url') :disabled => (repository && !repository.root_url.blank?)
) + ) +
'<br />'.html_safe + l(:text_mercurial_repository_note)) + '<br />'.html_safe + l(:text_mercurial_repository_note)) +
content_tag('p', form.select( content_tag('p', form.select(
@@ -193,7 +197,7 @@ module RepositoriesHelper
content_tag('p', form.text_field( content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository), :url, :label => l(:field_path_to_repository),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url') :disabled => (repository && !repository.root_url.blank?)
) + ) +
'<br />'.html_safe + '<br />'.html_safe +
l(:text_git_repository_note)) + l(:text_git_repository_note)) +
@@ -213,12 +217,12 @@ module RepositoriesHelper
:root_url, :root_url,
:label => l(:field_cvsroot), :label => l(:field_cvsroot),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('root_url'))) + :disabled => !repository.new_record?)) +
content_tag('p', form.text_field( content_tag('p', form.text_field(
:url, :url,
:label => l(:field_cvs_module), :label => l(:field_cvs_module),
:size => 30, :required => true, :size => 30, :required => true,
:disabled => !repository.safe_attribute?('url'))) + :disabled => !repository.new_record?)) +
content_tag('p', form.select( content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS, :log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true)) + :label => l(:field_commit_logs_encoding), :required => true)) +
@@ -233,7 +237,7 @@ module RepositoriesHelper
content_tag('p', form.text_field( content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository), :url, :label => l(:field_path_to_repository),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url'))) + :disabled => (repository && !repository.new_record?))) +
content_tag('p', form.select( content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS, :log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true)) :label => l(:field_commit_logs_encoding), :required => true))
@@ -243,7 +247,7 @@ module RepositoriesHelper
content_tag('p', form.text_field( content_tag('p', form.text_field(
:url, :label => l(:field_root_directory), :url, :label => l(:field_root_directory),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url'))) + :disabled => (repository && !repository.root_url.blank?))) +
content_tag('p', form.select( content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS, :path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding) :label => l(:field_scm_path_encoding)
@@ -251,63 +255,59 @@ module RepositoriesHelper
'<br />'.html_safe + l(:text_scm_path_encoding_note)) '<br />'.html_safe + l(:text_scm_path_encoding_note))
end end
def index_commits(commits, heads) def index_commits(commits, heads, href_proc = nil)
return nil if commits.nil? or commits.first.parents.nil? return nil if commits.nil? or commits.first.parents.nil?
map = {}
commit_hashes = []
refs_map = {} refs_map = {}
heads.each do |head| href_proc ||= Proc.new {|x|x}
refs_map[head.scmid] ||= [] heads.each{|r| refs_map[r.scmid] ||= []; refs_map[r.scmid] << r}
refs_map[head.scmid] << head commits.reverse.each_with_index do |c, i|
h = {}
h[:parents] = c.parents.collect do |p|
[p.scmid, 0, 0]
end end
h[:rdmid] = i
commits_by_scmid = {} h[:space] = 0
commits.reverse.each_with_index do |commit, commit_index| h[:refs] = refs_map[c.scmid].join(" ") if refs_map.include? c.scmid
h[:scmid] = c.scmid
commits_by_scmid[commit.scmid] = { h[:href] = href_proc.call(c.scmid)
:parent_scmids => commit.parents.collect { |parent| parent.scmid }, commit_hashes << h
:rdmid => commit_index, map[c.scmid] = h
:refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil,
:scmid => commit.scmid,
:href => block_given? ? yield(commit.scmid) : commit.scmid
}
end end
heads.sort! do |a,b|
heads.sort! { |head1, head2| head1.to_s <=> head2.to_s } a.to_s <=> b.to_s
end
space = nil j = 0
heads.each do |head| heads.each do |h|
if commits_by_scmid.include? head.scmid if map.include? h.scmid then
space = index_head((space || -1) + 1, head, commits_by_scmid) j = mark_chain(j += 1, map[h.scmid], map)
end end
end end
# when no head matched anything use first commit # when no head matched anything use first commit
space ||= index_head(0, commits.first, commits_by_scmid) if j == 0 then
mark_chain(j += 1, map.values.first, map)
return commits_by_scmid, space end
map
end end
def index_head(space, commit, commits_by_scmid) def mark_chain(mark, commit, map)
stack = [[mark, commit]]
stack = [[space, commits_by_scmid[commit.scmid]]] markmax = mark
max_space = space
until stack.empty? until stack.empty?
space, commit = stack.pop current = stack.pop
commit[:space] = space if commit[:space].nil? m, commit = current
commit[:space] = m if commit[:space] == 0
space -= 1 m1 = m - 1
commit[:parent_scmids].each_with_index do |parent_scmid, parent_index| commit[:parents].each_with_index do |p, i|
psha = p[0]
parent_commit = commits_by_scmid[parent_scmid] if map.include? psha and map[psha][:space] == 0 then
stack << [m1 += 1, map[psha]] if i == 0
if parent_commit and parent_commit[:space].nil? stack = [[m1 += 1, map[psha]]] + stack if i > 0
stack.unshift [space += 1, parent_commit]
end end
end end
max_space = space if max_space < space markmax = m1 if markmax < m1
end end
max_space markmax
end end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+3 -5
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -37,7 +37,7 @@ module SearchHelper
result << content_tag('span', h(words), :class => "highlight token-#{t}") result << content_tag('span', h(words), :class => "highlight token-#{t}")
end end
end end
result.html_safe result
end end
def type_label(t) def type_label(t)
@@ -63,8 +63,6 @@ module SearchHelper
links << link_to(h(text), :q => params[:q], :titles_only => params[:titles_only], links << link_to(h(text), :q => params[:q], :titles_only => params[:titles_only],
:all_words => params[:all_words], :scope => params[:scope], t => 1) :all_words => params[:all_words], :scope => params[:scope], t => 1)
end end
('<ul>'.html_safe + ('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
links.map {|link| content_tag('li', link)}.join(' ').html_safe +
'</ul>'.html_safe) unless links.empty?
end end
end end
+6 -7
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -44,7 +44,7 @@ module SettingsHelper
setting_values = Setting.send(setting) setting_values = Setting.send(setting)
setting_values = [] unless setting_values.is_a?(Array) setting_values = [] unless setting_values.is_a?(Array)
content_tag("label", l(options[:label] || "setting_#{setting}")) + setting_label(setting, options).html_safe +
hidden_field_tag("settings[#{setting}][]", '').html_safe + hidden_field_tag("settings[#{setting}][]", '').html_safe +
choices.collect do |choice| choices.collect do |choice|
text, value = (choice.is_a?(Array) ? choice : [choice, choice]) text, value = (choice.is_a?(Array) ? choice : [choice, choice])
@@ -53,8 +53,7 @@ module SettingsHelper
check_box_tag( check_box_tag(
"settings[#{setting}][]", "settings[#{setting}][]",
value, value,
Setting.send(setting).include?(value), Setting.send(setting).include?(value)
:id => nil
) + text.to_s, ) + text.to_s,
:class => 'block' :class => 'block'
) )
@@ -73,13 +72,13 @@ module SettingsHelper
def setting_check_box(setting, options={}) def setting_check_box(setting, options={})
setting_label(setting, options).html_safe + setting_label(setting, options).html_safe +
hidden_field_tag("settings[#{setting}]", 0, :id => nil).html_safe + hidden_field_tag("settings[#{setting}]", 0).html_safe +
check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options).html_safe check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options).html_safe
end end
def setting_label(setting, options={}) def setting_label(setting, options={})
label = options.delete(:label) label = options.delete(:label)
label != false ? label_tag("settings_#{setting}", l(label || "setting_#{setting}")).html_safe : '' label != false ? content_tag("label", l(label || "setting_#{setting}")).html_safe : ''
end end
# Renders a notification field for a Redmine::Notifiable option # Renders a notification field for a Redmine::Notifiable option
@@ -87,7 +86,7 @@ module SettingsHelper
return content_tag(:label, return content_tag(:label,
check_box_tag('settings[notified_events][]', check_box_tag('settings[notified_events][]',
notifiable.name, notifiable.name,
Setting.notified_events.include?(notifiable.name), :id => nil).html_safe + Setting.notified_events.include?(notifiable.name)).html_safe +
l_or_humanize(notifiable.name, :prefix => 'label_').html_safe, l_or_humanize(notifiable.name, :prefix => 'label_').html_safe,
:class => notifiable.parent.present? ? "parent" : '').html_safe :class => notifiable.parent.present? ? "parent" : '').html_safe
end end
+1 -2
View File
@@ -160,8 +160,7 @@ module SortHelper
# sort_clause. # sort_clause.
# - criteria can be either an array or a hash of allowed keys # - criteria can be either an array or a hash of allowed keys
# #
def sort_update(criteria, sort_name=nil) def sort_update(criteria)
sort_name ||= self.sort_name
@sort_criteria = SortCriteria.new @sort_criteria = SortCriteria.new
@sort_criteria.available_criteria = criteria @sort_criteria.available_criteria = criteria
@sort_criteria.from_param(params[:sort] || session[sort_name]) @sort_criteria.from_param(params[:sort] || session[sort_name])
+21 -21
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -118,7 +118,7 @@ module TimelogHelper
entry.hours.to_s.gsub('.', decimal_separator), entry.hours.to_s.gsub('.', decimal_separator),
entry.comments entry.comments
] ]
fields += custom_fields.collect {|f| show_value(entry.custom_field_values.detect {|v| v.custom_field_id == f.id}) } fields += custom_fields.collect {|f| show_value(entry.custom_value_for(f)) }
csv << fields.collect {|c| Redmine::CodesetUtil.from_utf8( csv << fields.collect {|c| Redmine::CodesetUtil.from_utf8(
c.to_s, c.to_s,
@@ -128,10 +128,10 @@ module TimelogHelper
export export
end end
def format_criteria_value(criteria_options, value) def format_criteria_value(criteria, value)
if value.blank? if value.blank?
"[#{l(:label_none)}]" l(:label_none)
elsif k = criteria_options[:klass] elsif k = @available_criterias[criteria][:klass]
obj = k.find_by_id(value.to_i) obj = k.find_by_id(value.to_i)
if obj.is_a?(Issue) if obj.is_a?(Issue)
obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}" obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}"
@@ -139,28 +139,28 @@ module TimelogHelper
obj obj
end end
else else
format_value(value, criteria_options[:format]) format_value(value, @available_criterias[criteria][:format])
end end
end end
def report_to_csv(report) def report_to_csv(criterias, periods, hours)
decimal_separator = l(:general_csv_decimal_separator) decimal_separator = l(:general_csv_decimal_separator)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# Column headers # Column headers
headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) } headers = criterias.collect {|criteria| l(@available_criterias[criteria][:label]) }
headers += report.periods headers += periods
headers << l(:label_total) headers << l(:label_total)
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8( csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8(
c.to_s, c.to_s,
l(:general_csv_encoding) ) } l(:general_csv_encoding) ) }
# Content # Content
report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours) report_criteria_to_csv(csv, criterias, periods, hours)
# Total row # Total row
str_total = Redmine::CodesetUtil.from_utf8(l(:label_total), l(:general_csv_encoding)) str_total = Redmine::CodesetUtil.from_utf8(l(:label_total), l(:general_csv_encoding))
row = [ str_total ] + [''] * (report.criteria.size - 1) row = [ str_total ] + [''] * (criterias.size - 1)
total = 0 total = 0
report.periods.each do |period| periods.each do |period|
sum = sum_hours(select_hours(report.hours, report.columns, period.to_s)) sum = sum_hours(select_hours(hours, @columns, period.to_s))
total += sum total += sum
row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '') row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '')
end end
@@ -170,26 +170,26 @@ module TimelogHelper
export export
end end
def report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours, level=0) def report_criteria_to_csv(csv, criterias, periods, hours, level=0)
decimal_separator = l(:general_csv_decimal_separator) decimal_separator = l(:general_csv_decimal_separator)
hours.collect {|h| h[criteria[level]].to_s}.uniq.each do |value| hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value|
hours_for_value = select_hours(hours, criteria[level], value) hours_for_value = select_hours(hours, criterias[level], value)
next if hours_for_value.empty? next if hours_for_value.empty?
row = [''] * level row = [''] * level
row << Redmine::CodesetUtil.from_utf8( row << Redmine::CodesetUtil.from_utf8(
format_criteria_value(available_criteria[criteria[level]], value).to_s, format_criteria_value(criterias[level], value).to_s,
l(:general_csv_encoding) ) l(:general_csv_encoding) )
row += [''] * (criteria.length - level - 1) row += [''] * (criterias.length - level - 1)
total = 0 total = 0
periods.each do |period| periods.each do |period|
sum = sum_hours(select_hours(hours_for_value, columns, period.to_s)) sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s))
total += sum total += sum
row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '') row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '')
end end
row << ("%.2f" % total).gsub('.',decimal_separator) row << ("%.2f" % total).gsub('.',decimal_separator)
csv << row csv << row
if criteria.length > level + 1 if criterias.length > level + 1
report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours_for_value, level + 1) report_criteria_to_csv(csv, criterias, periods, hours_for_value, level + 1)
end end
end end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+4 -4
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -21,9 +21,9 @@ module UsersHelper
def users_status_options_for_select(selected) def users_status_options_for_select(selected)
user_count_by_status = User.count(:group => 'status').to_hash user_count_by_status = User.count(:group => 'status').to_hash
options_for_select([[l(:label_all), ''], options_for_select([[l(:label_all), ''],
["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'], ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", 1],
["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'], ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", 2],
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s) ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", 3]], selected)
end end
# Options for the new membership projects combo-box # Options for the new membership projects combo-box
+3 -11
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,18 +19,10 @@
module VersionsHelper module VersionsHelper
def version_anchor(version) STATUS_BY_CRITERIAS = %w(category tracker status priority author assigned_to)
if @project == version.project
anchor version.name
else
anchor "#{version.project.try(:identifier)}-#{version.name}"
end
end
STATUS_BY_CRITERIAS = %w(tracker status priority author assigned_to category)
def render_issue_status_by(version, criteria) def render_issue_status_by(version, criteria)
criteria = 'tracker' unless STATUS_BY_CRITERIAS.include?(criteria) criteria = 'category' unless STATUS_BY_CRITERIAS.include?(criteria)
h = Hash.new {|k,v| k[v] = [0, 0]} h = Hash.new {|k,v| k[v] = [0, 0]}
begin begin
+13 -22
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -30,8 +30,10 @@ module WatchersHelper
:action => (watched ? 'unwatch' : 'watch'), :action => (watched ? 'unwatch' : 'watch'),
:object_type => object.class.to_s.underscore, :object_type => object.class.to_s.underscore,
:object_id => object.id} :object_id => object.id}
link_to((watched ? l(:button_unwatch) : l(:button_watch)), url, link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
:remote => true, :method => 'post', :class => (watched ? 'icon icon-fav' : 'icon icon-fav-off')) {:url => url},
:href => url_for(url),
:class => (watched ? 'icon icon-fav' : 'icon icon-fav-off'))
end end
@@ -43,33 +45,22 @@ module WatchersHelper
# Returns a comma separated list of users watching the given object # Returns a comma separated list of users watching the given object
def watchers_list(object) def watchers_list(object)
remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project) remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
content = ''.html_safe
lis = object.watcher_users.collect do |user| lis = object.watcher_users.collect do |user|
s = ''.html_safe s = avatar(user, :size => "16").to_s + link_to_user(user, :class => 'user').to_s
s << avatar(user, :size => "16").to_s
s << link_to_user(user, :class => 'user')
if remove_allowed if remove_allowed
url = {:controller => 'watchers', url = {:controller => 'watchers',
:action => 'destroy', :action => 'destroy',
:object_type => object.class.to_s.underscore, :object_type => object.class.to_s.underscore,
:object_id => object.id, :object_id => object.id,
:user_id => user} :user_id => user}
s << ' ' s += ' ' + link_to_remote(image_tag('delete.png'),
s << link_to(image_tag('delete.png'), url, {:url => url},
:remote => true, :method => 'post', :style => "vertical-align: middle", :class => "delete") :href => url_for(url),
:style => "vertical-align: middle",
:class => "delete")
end end
content << content_tag('li', s) "<li>#{ s }</li>"
end end
content.present? ? content_tag('ul', content) : content lis.empty? ? "" : "<ul>#{ lis.join("\n") }</ul>"
end
def watchers_checkboxes(object, users, checked=nil)
users.map do |user|
c = checked.nil? ? object.watched_by?(user) : checked
tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil
content_tag 'label', "#{tag} #{h(user)}".html_safe,
:id => "issue_watcher_user_ids_#{user.id}",
:class => "floating"
end.join.html_safe
end end
end end
+1 -1
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+4 -4
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -21,14 +21,14 @@ module WikiHelper
def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0) def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
pages = pages.group_by(&:parent) unless pages.is_a?(Hash) pages = pages.group_by(&:parent) unless pages.is_a?(Hash)
s = ''.html_safe s = ''
if pages.has_key?(parent) if pages.has_key?(parent)
pages[parent].each do |page| pages[parent].each do |page|
attrs = "value='#{page.id}'" attrs = "value='#{page.id}'"
attrs << " selected='selected'" if selected == page attrs << " selected='selected'" if selected == page
indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : '' indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : nil
s << content_tag('option', (indent + h(page.pretty_title)).html_safe, :value => page.id.to_s, :selected => selected == page) + s << "<option #{attrs}>#{indent}#{h page.pretty_title}</option>\n" +
wiki_page_options_for_select(pages, selected, page, level + 1) wiki_page_options_for_select(pages, selected, page, level + 1)
end end
end end
+1 -12
View File
@@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,15 +18,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module WorkflowsHelper module WorkflowsHelper
def field_required?(field)
field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
end
def field_permission_tag(permissions, status, field)
name = field.is_a?(CustomField) ? field.id.to_s : field
options = [["", ""], [l(:label_readonly), "readonly"]]
options << [l(:label_required), "required"] unless field_required?(field)
select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, permissions[status.id][name]))
end
end end
+35 -121
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -21,10 +21,9 @@ class Attachment < ActiveRecord::Base
belongs_to :container, :polymorphic => true belongs_to :container, :polymorphic => true
belongs_to :author, :class_name => "User", :foreign_key => "author_id" belongs_to :author, :class_name => "User", :foreign_key => "author_id"
validates_presence_of :filename, :author validates_presence_of :container, :filename, :author
validates_length_of :filename, :maximum => 255 validates_length_of :filename, :maximum => 255
validates_length_of :disk_filename, :maximum => 255 validates_length_of :disk_filename, :maximum => 255
validates_length_of :description, :maximum => 255
validate :validate_max_file_size validate :validate_max_file_size
acts_as_event :title => :filename, acts_as_event :title => :filename,
@@ -45,25 +44,14 @@ class Attachment < ActiveRecord::Base
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"} "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
cattr_accessor :storage_path cattr_accessor :storage_path
@@storage_path = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, "files") @@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{Rails.root}/files"
cattr_accessor :thumbnails_storage_path
@@thumbnails_storage_path = File.join(Rails.root, "tmp", "thumbnails")
before_save :files_to_final_location before_save :files_to_final_location
after_destroy :delete_from_disk after_destroy :delete_from_disk
# Returns an unsaved copy of the attachment
def copy(attributes=nil)
copy = self.class.new
copy.attributes = self.attributes.dup.except("id", "downloads")
copy.attributes = attributes if attributes
copy
end
def validate_max_file_size def validate_max_file_size
if @temp_file && self.filesize > Setting.attachment_max_size.to_i.kilobytes if self.filesize > Setting.attachment_max_size.to_i.kilobytes
errors.add(:base, l(:error_attachment_too_big, :max_size => Setting.attachment_max_size.to_i.kilobytes)) errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
end end
end end
@@ -71,14 +59,10 @@ class Attachment < ActiveRecord::Base
unless incoming_file.nil? unless incoming_file.nil?
@temp_file = incoming_file @temp_file = incoming_file
if @temp_file.size > 0 if @temp_file.size > 0
if @temp_file.respond_to?(:original_filename) self.filename = sanitize_filename(@temp_file.original_filename)
self.filename = @temp_file.original_filename self.disk_filename = Attachment.disk_filename(filename)
self.filename.force_encoding("UTF-8") if filename.respond_to?(:force_encoding)
end
if @temp_file.respond_to?(:content_type)
self.content_type = @temp_file.content_type.to_s.chomp self.content_type = @temp_file.content_type.to_s.chomp
end if content_type.blank?
if content_type.blank? && filename.present?
self.content_type = Redmine::MimeType.of(filename) self.content_type = Redmine::MimeType.of(filename)
end end
self.filesize = @temp_file.size self.filesize = @temp_file.size
@@ -90,14 +74,6 @@ class Attachment < ActiveRecord::Base
nil nil
end end
def filename=(arg)
write_attribute :filename, sanitize_filename(arg.to_s)
if new_record? && disk_filename.blank?
self.disk_filename = Attachment.disk_filename(filename)
end
filename
end
# Copies the temporary file to its final location # Copies the temporary file to its final location
# and computes its MD5 hash # and computes its MD5 hash
def files_to_final_location def files_to_final_location
@@ -105,16 +81,11 @@ class Attachment < ActiveRecord::Base
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
md5 = Digest::MD5.new md5 = Digest::MD5.new
File.open(diskfile, "wb") do |f| File.open(diskfile, "wb") do |f|
if @temp_file.respond_to?(:read)
buffer = "" buffer = ""
while (buffer = @temp_file.read(8192)) while (buffer = @temp_file.read(8192))
f.write(buffer) f.write(buffer)
md5.update(buffer) md5.update(buffer)
end end
else
f.write(@temp_file)
md5.update(@temp_file)
end
end end
self.digest = md5.hexdigest self.digest = md5.hexdigest
end end
@@ -125,24 +96,14 @@ class Attachment < ActiveRecord::Base
end end
end end
# Deletes the file from the file system if it's not referenced by other attachments # Deletes file on the disk
def delete_from_disk def delete_from_disk
if Attachment.where("disk_filename = ? AND id <> ?", disk_filename, id).empty? File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
delete_from_disk!
end
end end
# Returns file's location on disk # Returns file's location on disk
def diskfile def diskfile
File.join(self.class.storage_path, disk_filename.to_s) "#{@@storage_path}/#{self.disk_filename}"
end
def title
title = filename.to_s
if description.present?
title << " (#{description})"
end
title
end end
def increment_download def increment_download
@@ -150,55 +111,19 @@ class Attachment < ActiveRecord::Base
end end
def project def project
container.try(:project) container.project
end end
def visible?(user=User.current) def visible?(user=User.current)
container && container.attachments_visible?(user) container.attachments_visible?(user)
end end
def deletable?(user=User.current) def deletable?(user=User.current)
container && container.attachments_deletable?(user) container.attachments_deletable?(user)
end end
def image? def image?
!!(self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i) self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i
end
def thumbnailable?
image?
end
# Returns the full path the attachment thumbnail, or nil
# if the thumbnail cannot be generated.
def thumbnail(options={})
if thumbnailable? && readable?
size = options[:size].to_i
if size > 0
# Limit the number of thumbnails per image
size = (size / 50) * 50
# Maximum thumbnail size
size = 800 if size > 800
else
size = Setting.thumbnails_size.to_i
end
size = 100 unless size > 0
target = File.join(self.class.thumbnails_storage_path, "#{id}_#{digest}_#{size}.thumb")
begin
Redmine::Thumbnail.generate(self.diskfile, target, size)
rescue => e
logger.error "An error occured while generating thumbnail for #{disk_filename} to #{target}\nException was: #{e.message}" if logger
return nil
end
end
end
# Deletes all thumbnails
def self.clear_thumbnails
Dir.glob(File.join(thumbnails_storage_path, "*.thumb")).each do |file|
File.delete file
end
end end
def is_text? def is_text?
@@ -214,31 +139,32 @@ class Attachment < ActiveRecord::Base
File.readable?(diskfile) File.readable?(diskfile)
end end
# Returns the attachment token
def token
"#{id}.#{digest}"
end
# Finds an attachment that matches the given token and that has no container
def self.find_by_token(token)
if token.to_s =~ /^(\d+)\.([0-9a-f]+)$/
attachment_id, attachment_digest = $1, $2
attachment = Attachment.where(:id => attachment_id, :digest => attachment_digest).first
if attachment && attachment.container.nil?
attachment
end
end
end
# Bulk attaches a set of files to an object # Bulk attaches a set of files to an object
# #
# Returns a Hash of the results: # Returns a Hash of the results:
# :files => array of the attached files # :files => array of the attached files
# :unsaved => array of the files that could not be attached # :unsaved => array of the files that could not be attached
def self.attach_files(obj, attachments) def self.attach_files(obj, attachments)
result = obj.save_attachments(attachments, User.current) attached = []
obj.attach_saved_attachments if attachments && attachments.is_a?(Hash)
result attachments.each_value do |attachment|
file = attachment['file']
next unless file && file.size > 0
a = Attachment.create(:container => obj,
:file => file,
:description => attachment['description'].to_s.strip,
:author => User.current)
obj.attachments << a
if a.new_record?
obj.unsaved_attachments ||= []
obj.unsaved_attachments << a
else
attached << a
end
end
end
{:files => attached, :unsaved => obj.unsaved_attachments}
end end
def self.latest_attach(attachments, filename) def self.latest_attach(attachments, filename)
@@ -247,19 +173,7 @@ class Attachment < ActiveRecord::Base
} }
end end
def self.prune(age=1.day)
Attachment.where("created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age).destroy_all
end
private private
# Physically deletes the file from the file system
def delete_from_disk!
if disk_filename.present? && File.exist?(diskfile)
File.delete(diskfile)
end
end
def sanitize_filename(value) def sanitize_filename(value)
# get only the filename, not the whole path # get only the filename, not the whole path
just_filename = value.gsub(/^.*(\\|\/)/, '') just_filename = value.gsub(/^.*(\\|\/)/, '')
+2 -8
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -15,13 +15,7 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Generic exception for when the AuthSource can not be reached
# (eg. can not connect to the LDAP)
class AuthSourceException < Exception; end
class AuthSourceTimeoutException < AuthSourceException; end
class AuthSource < ActiveRecord::Base class AuthSource < ActiveRecord::Base
include Redmine::SubclassFactory
include Redmine::Ciphering include Redmine::Ciphering
has_many :users has_many :users
@@ -59,7 +53,7 @@ class AuthSource < ActiveRecord::Base
# Try to authenticate a user not yet registered against available sources # Try to authenticate a user not yet registered against available sources
def self.authenticate(login, password) def self.authenticate(login, password)
AuthSource.where(:onthefly_register => true).all.each do |source| AuthSource.find(:all, :conditions => ["onthefly_register=?", true]).each do |source|
begin begin
logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug? logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
attrs = source.authenticate(login, password) attrs = source.authenticate(login, password)
+11 -54
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -15,49 +15,40 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv'
require 'net/ldap' require 'net/ldap'
require 'net/ldap/dn' require 'iconv'
require 'timeout'
class AuthSourceLdap < AuthSource class AuthSourceLdap < AuthSource
validates_presence_of :host, :port, :attr_login validates_presence_of :host, :port, :attr_login
validates_length_of :name, :host, :maximum => 60, :allow_nil => true validates_length_of :name, :host, :maximum => 60, :allow_nil => true
validates_length_of :account, :account_password, :base_dn, :filter, :maximum => 255, :allow_blank => 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_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
validates_numericality_of :port, :only_integer => true validates_numericality_of :port, :only_integer => true
validates_numericality_of :timeout, :only_integer => true, :allow_blank => true
validate :validate_filter
before_validation :strip_ldap_attributes before_validation :strip_ldap_attributes
def initialize(attributes=nil, *args) def after_initialize
super
self.port = 389 if self.port == 0 self.port = 389 if self.port == 0
end end
def authenticate(login, password) def authenticate(login, password)
return nil if login.blank? || password.blank? return nil if login.blank? || password.blank?
attrs = get_user_dn(login)
with_timeout do
attrs = get_user_dn(login, password)
if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password) if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
return attrs.except(:dn) return attrs.except(:dn)
end end
end rescue Net::LDAP::LdapError => text
rescue Net::LDAP::LdapError => e raise "LdapError: " + text
raise AuthSourceException.new(e.message)
end end
# test the connection to the LDAP # test the connection to the LDAP
def test_connection def test_connection
with_timeout do
ldap_con = initialize_ldap_con(self.account, self.account_password) ldap_con = initialize_ldap_con(self.account, self.account_password)
ldap_con.open { } ldap_con.open { }
end rescue Net::LDAP::LdapError => text
rescue Net::LDAP::LdapError => e raise "LdapError: " + text
raise AuthSourceException.new(e.message)
end end
def auth_method_name def auth_method_name
@@ -66,30 +57,6 @@ class AuthSourceLdap < AuthSource
private private
def with_timeout(&block)
timeout = self.timeout
timeout = 20 unless timeout && timeout > 0
Timeout.timeout(timeout) do
return yield
end
rescue Timeout::Error => e
raise AuthSourceTimeoutException.new(e.message)
end
def ldap_filter
if filter.present?
Net::LDAP::Filter.construct(filter)
end
rescue Net::LDAP::LdapError
nil
end
def validate_filter
if filter.present? && ldap_filter.nil?
errors.add(:filter, :invalid)
end
end
def strip_ldap_attributes def strip_ldap_attributes
[:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr| [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr|
write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil? write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil?
@@ -133,24 +100,14 @@ class AuthSourceLdap < AuthSource
end end
# Get the user's dn and any attributes for them, given their login # Get the user's dn and any attributes for them, given their login
def get_user_dn(login, password) def get_user_dn(login)
ldap_con = nil
if self.account && self.account.include?("$login")
ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password)
else
ldap_con = initialize_ldap_con(self.account, self.account_password) ldap_con = initialize_ldap_con(self.account, self.account_password)
end
login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
attrs = {} attrs = {}
search_filter = object_filter & login_filter
if f = ldap_filter
search_filter = search_filter & f
end
ldap_con.search( :base => self.base_dn, ldap_con.search( :base => self.base_dn,
:filter => search_filter, :filter => object_filter & login_filter,
:attributes=> search_attributes) do |entry| :attributes=> search_attributes) do |entry|
if onthefly_register? if onthefly_register?
+4 -37
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -21,37 +21,26 @@ class Board < ActiveRecord::Base
has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC" 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 => :destroy, :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 belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
acts_as_tree :dependent => :nullify acts_as_list :scope => :project_id
acts_as_list :scope => '(project_id = #{project_id} AND parent_id #{parent_id ? "= #{parent_id}" : "IS NULL"})'
acts_as_watchable acts_as_watchable
validates_presence_of :name, :description validates_presence_of :name, :description
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_length_of :description, :maximum => 255 validates_length_of :description, :maximum => 255
validate :validate_board
scope :visible, lambda {|*args| { :include => :project, named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } } :conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
safe_attributes 'name', 'description', 'parent_id', 'move_to' safe_attributes 'name', 'description', 'move_to'
def visible?(user=User.current) def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_messages, project) !user.nil? && user.allowed_to?(:view_messages, project)
end end
def reload(*args)
@valid_parents = nil
super
end
def to_s def to_s
name name
end end
def valid_parents
@valid_parents ||= project.boards - self_and_descendants
end
def reset_counters! def reset_counters!
self.class.reset_counters!(id) self.class.reset_counters!(id)
end end
@@ -64,26 +53,4 @@ class Board < ActiveRecord::Base
" last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})", " last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})",
["id = ?", board_id]) ["id = ?", board_id])
end end
def self.board_tree(boards, parent_id=nil, level=0)
tree = []
boards.select {|board| board.parent_id == parent_id}.sort_by(&:position).each do |board|
tree << [board, level]
tree += board_tree(boards, board.id, level+1)
end
if block_given?
tree.each do |board, level|
yield board, level
end
end
tree
end
protected
def validate_board
if parent_id && parent_id_changed?
errors.add(:parent_id, :invalid) unless valid_parents.include?(parent)
end
end
end end
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+17 -24
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,7 +20,7 @@ require 'iconv'
class Changeset < ActiveRecord::Base class Changeset < ActiveRecord::Base
belongs_to :repository belongs_to :repository
belongs_to :user belongs_to :user
has_many :filechanges, :class_name => 'Change', :dependent => :delete_all has_many :changes, :dependent => :delete_all
has_and_belongs_to_many :issues has_and_belongs_to_many :issues
has_and_belongs_to_many :parents, has_and_belongs_to_many :parents,
:class_name => "Changeset", :class_name => "Changeset",
@@ -31,10 +31,10 @@ class Changeset < ActiveRecord::Base
:join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}", :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
:association_foreign_key => 'changeset_id', :foreign_key => 'parent_id' :association_foreign_key => 'changeset_id', :foreign_key => 'parent_id'
acts_as_event :title => Proc.new {|o| o.title}, acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
:description => :long_comments, :description => :long_comments,
:datetime => :committed_on, :datetime => :committed_on,
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :repository_id => o.repository.identifier_param, :rev => o.identifier}} :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
acts_as_searchable :columns => 'comments', acts_as_searchable :columns => 'comments',
:include => {:repository => :project}, :include => {:repository => :project},
@@ -49,8 +49,7 @@ class Changeset < ActiveRecord::Base
validates_uniqueness_of :revision, :scope => :repository_id validates_uniqueness_of :revision, :scope => :repository_id
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
scope :visible, named_scope :visible, lambda {|*args| { :include => {:repository => :project},
lambda {|*args| { :include => {:repository => :project},
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } } :conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } }
after_create :scan_for_issues after_create :scan_for_issues
@@ -158,30 +157,26 @@ class Changeset < ActiveRecord::Base
else else
"r#{revision}" "r#{revision}"
end end
if repository && repository.identifier.present?
tag = "#{repository.identifier}|#{tag}"
end
if ref_project && project && ref_project != project if ref_project && project && ref_project != project
tag = "#{project.identifier}:#{tag}" tag = "#{project.identifier}:#{tag}"
end end
tag tag
end end
# Returns the title used for the changeset in the activity/search results
def title
repo = (repository && repository.identifier.present?) ? " (#{repository.identifier})" : ''
comm = short_comments.blank? ? '' : (': ' + short_comments)
"#{l(:label_revision)} #{format_identifier}#{repo}#{comm}"
end
# Returns the previous changeset # Returns the previous changeset
def previous def previous
@previous ||= Changeset.where(["id < ? AND repository_id = ?", id, repository_id]).order('id DESC').first @previous ||= Changeset.find(:first,
:conditions => ['id < ? AND repository_id = ?',
self.id, self.repository_id],
:order => 'id DESC')
end end
# Returns the next changeset # Returns the next changeset
def next def next
@next ||= Changeset.where(["id > ? AND repository_id = ?", id, repository_id]).order('id ASC').first @next ||= Changeset.find(:first,
:conditions => ['id > ? AND repository_id = ?',
self.id, self.repository_id],
:order => 'id ASC')
end end
# Creates a new Change from it's common parameters # Creates a new Change from it's common parameters
@@ -193,14 +188,14 @@ class Changeset < ActiveRecord::Base
:from_revision => change[:from_revision]) :from_revision => change[:from_revision])
end end
private
# Finds an issue that can be referenced by the commit message # 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) def find_referenced_issue_by_id(id)
return nil if id.blank? return nil if id.blank?
issue = Issue.find_by_id(id.to_i, :include => :project) issue = Issue.find_by_id(id.to_i, :include => :project)
if Setting.commit_cross_project_ref? if issue
# all issues can be referenced/fixed
elsif issue
# issue that belong to the repository project, a subproject or a parent project only
unless issue.project && unless issue.project &&
(project == issue.project || project.is_ancestor_of?(issue.project) || (project == issue.project || project.is_ancestor_of?(issue.project) ||
project.is_descendant_of?(issue.project)) project.is_descendant_of?(issue.project))
@@ -210,8 +205,6 @@ class Changeset < ActiveRecord::Base
issue issue
end end
private
def fix_issue(issue) def fix_issue(issue)
status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i) status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
if status.nil? if status.nil?
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
Loaded 100 of 1554 files, more files were not shown because too many files have changed in this diff. Show more