Compare commits

..

157 Commits

Author SHA1 Message Date
Jean-Philippe Lang
d63dcd28a3 tagged version 1.4.6
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/1.4.6@11156 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-09 16:21:19 +00:00
Jean-Philippe Lang
284ee1080b Removed Rails patch.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@11154 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-09 15:18:37 +00:00
Jean-Philippe Lang
190fc8d9db Changes for 1.4.6 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@11149 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-09 13:43:38 +00:00
Jean-Philippe Lang
cf5e29a83b Upgrade to Rails 2.3.15.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@11148 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-01-09 13:42:30 +00:00
Toshi MARUYAMA
2acfd0847c Merged r11051 from trunk to 1.4-stable (#12630)
Russian "x_hours" translation updated by Mikhail Velkin.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@11055 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-19 00:05:11 +00:00
Toshi MARUYAMA
2f2f360311 Merged r11035 from trunk to 1.4-stable (#12615)
Russian translation updated by Kirill Bezrukov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@11039 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-17 23:54:47 +00:00
Toshi MARUYAMA
29b64fce8e Merged r11022 from trunk to 1.4-stable (#12614)
Dutch translation updated.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@11025 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-17 12:45:09 +00:00
Toshi MARUYAMA
759a3321f4 Merged r11014 from trunk to 1.4-stable (#12605)
Norwegian translation for 1.4-stable updated by Ketil Mehl.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@11020 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-12-16 07:42:04 +00:00
Jean-Philippe Lang
b5c0a3313b Reverts r10896 (#12472).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10899 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-11-30 10:26:02 +00:00
Jean-Philippe Lang
ca73f99056 Reverts r10896 (#12472).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10898 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-11-30 10:25:48 +00:00
Jean-Philippe Lang
8312fec63f Merged r10893 into 1.4-stable (#12472).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10896 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-11-30 08:59:57 +00:00
Toshi MARUYAMA
68c2588f85 Merged r10862 from trunk to 1.4-stable (#12409)
scm: git: change extra_report_last_commit type at unit test to string.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10864 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-11-22 01:18:23 +00:00
Toshi MARUYAMA
31e714f2db Merged r10856 and r10857 from trunk to 1.4-stable (#12409)
scm: git: fix changesets aren't read after clear_changesets call.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10859 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-11-21 09:59:22 +00:00
Toshi MARUYAMA
886d6f006b Merged r10840 from trunk to 1.4-stable
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10849 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-11-18 22:47:32 +00:00
Jean-Philippe Lang
628e63eefe Updates for 1.4.5 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10834 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-11-17 13:24:56 +00:00
Toshi MARUYAMA
83b356484a Merged r10709 from trunk to 1.4-stable (#12196)
fix "Page not found" on OK button in SCM "View all revisions" page.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10711 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-10-24 10:33:15 +00:00
Toshi MARUYAMA
5210b58226 Merged r10702 from trunk to 1.4-stable (#12189)
add tmp/pdf directory.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10704 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-10-23 11:01:49 +00:00
Toshi MARUYAMA
a0932173a5 Merged r10689 from trunk to 1.4-stable
Partial backout r10263.

Backout replacing shoulda context of test/unit/helpers/issues_helper_test.rb.
Test methods in shoulda context do not run on Ruby 1.8.7-p370.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10693 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-10-22 03:29:14 +00:00
Toshi MARUYAMA
e889fe9c8a Merged r10688 from trunk to 1.4-stable
Backout r10253.

Backout replacing shoulda context of test/unit/helpers/issues_helper_test.rb.
Test methods in shoulda context do not run on Ruby 1.8.7-p370.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10692 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-10-22 03:28:44 +00:00
Toshi MARUYAMA
16b1fcb079 Merged r10596 from trunk to 1.4-stable (#9732)
German translation updated by Raphael Kallensee.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10601 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-10-11 03:55:38 +00:00
Toshi MARUYAMA
3b7793561a Merged r10572 from trunk
add missing fixtures to test/unit/issue_relation_test.rb

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10579 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-10-09 11:58:27 +00:00
Toshi MARUYAMA
2c3c1d2a02 Merged r10571 from trunk
add missing fixture to test/integration/api_test/issues_test.rb

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10578 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-10-09 11:58:13 +00:00
Toshi MARUYAMA
49cb3aaca6 Merged r10503 from trunk to 1.4-stable (#11966)
add functional test of sub repository diff path.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10508 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-28 04:48:01 +00:00
Toshi MARUYAMA
8a8df72937 Merged r10502 from trunk to 1.4-stable (#11966)
add functional test of main repository diff path.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10507 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-28 04:47:46 +00:00
Toshi MARUYAMA
ad238abd0c Merged r10490 from trunk to 1.4-stable (#11966)
explicitly define route at scm diff view.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10506 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-28 04:47:26 +00:00
Toshi MARUYAMA
d69cae859f Merged r10501 from trunk
set user preference diff type nil at functional tests.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10504 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-28 03:51:33 +00:00
Toshi MARUYAMA
59d4d210a7 Merged r10498 from trunk
scm: git: use with_settings instead of assigned setting at functional test

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10500 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-28 00:19:09 +00:00
Toshi MARUYAMA
633b86236c Merged r10477 from trunk
set default_language en at test_commit_closing_a_subproject_issue of unit changeset test.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10483 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-26 12:05:51 +00:00
Toshi MARUYAMA
91514dd682 add tests that i18n currency options are nil to 1.4-stable Rails2 (#11922)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10475 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-26 08:12:04 +00:00
Jean-Philippe Lang
917127f179 Updated 1.4 CHANGELOG.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10300 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-06 17:25:29 +00:00
Jean-Philippe Lang
49295481de Add namespace to Time (#11298).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10299 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-06 17:23:57 +00:00
Jean-Philippe Lang
8cc56cce66 Merged r10294 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10297 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-06 17:21:09 +00:00
Jean-Philippe Lang
8d5fadc773 Updated 1.4 CHANGELOG.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10296 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-06 17:19:27 +00:00
Jean-Philippe Lang
eb3e79b70d Merged r10148 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10295 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-06 17:07:49 +00:00
Toshi MARUYAMA
5d3bdb66cd Merged r10253, r10257, r10263 and r10264 from trunk to 1.4-stable
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10266 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-01 07:22:52 +00:00
Toshi MARUYAMA
1974123562 Merged r10255 from trunk to 1.4-stable
add missing fixture to test/unit/lib/redmine/safe_attributes_test.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10262 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-01 05:09:12 +00:00
Toshi MARUYAMA
bcef197b41 Merged r10256 from trunk to 1.4-stable
add missing fixtures to test/unit/lib/redmine/hook_test.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10261 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-01 05:08:57 +00:00
Toshi MARUYAMA
231145160f Merged r10249 from trunk to 1.4-stable
set language en to test_label_for at test/unit/query_test.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10260 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-01 05:08:42 +00:00
Toshi MARUYAMA
0e1ae572c7 Merged r10252 from trunk to 1.4-stable (#11752)
scm: git: use with_settings at test_diff_truncated of functional test.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10259 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-01 05:08:22 +00:00
Toshi MARUYAMA
b3a86771ce Merged r10251 from trunk to 1.4-stable (#11752)
scm: git: use diff_max_lines_displayed setting at test_diff_with_rev_and_path of functional test.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10258 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-01 05:08:06 +00:00
Toshi MARUYAMA
2516d4f408 Merged r9949 from trunk to 1.4-stable
Tests should not change settings.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10250 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-01 02:16:07 +00:00
Toshi MARUYAMA
03d6c4668e Merged r10245 from trunk to 1.4-stable (#11752)
scm: git: add functional test of diff with revision and path.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10248 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-31 23:55:56 +00:00
Toshi MARUYAMA
a97d6baddd Merged r10230 from trunk (#11665)
add check default document category is not defined on database in tests.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10233 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-22 22:44:30 +00:00
Toshi MARUYAMA
f3a8494cfb Merged r10219 from trunk to 1.4-stable (#11665)
add functional test to create non default document category.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10227 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-21 17:16:28 +00:00
Toshi MARUYAMA
14dcefaa97 set default category_id instead of the object (#11665)
Rails 2.3 still has issues with synchronizing the association_id
and association attributes of an object. That means, if you set the
association with an object first and then just set the id afterwards,
the object wins and the setting of the id gets lost.

This is not an issue in Rails >= 3.1 anymore.

Contributed by Holger Just.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10226 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-21 17:15:59 +00:00
Toshi MARUYAMA
f4a7d6ca8c Merged r10221 from trunk to 1.4-stable (#11665)
force set default document category if it is not set on database.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10225 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-21 17:15:38 +00:00
Toshi MARUYAMA
aedd5c5e35 Merged r10220 from trunk to 1.4-stable (#11665)
add unit test to get default document category defined on database.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10224 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-21 17:15:16 +00:00
Toshi MARUYAMA
554d6bcba2 Merged r10222 from trunk to 1.4-stable (#11665)
add position to DocumentCategory in enumerations fixture.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10223 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-21 17:14:53 +00:00
Toshi MARUYAMA
4fcec69a40 Merged r10216 from trunk
add missing fixtures to test/functional/documents_controller_test.rb

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10218 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-21 04:53:10 +00:00
Toshi MARUYAMA
7e6cc2f583 Merged r10213 from trunk
add missing fixtures to test/unit/document_test.rb

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10215 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-21 02:25:58 +00:00
Toshi MARUYAMA
16383ed386 Merged r10169 from trunk to 1.4-stable (#11600)
fix plural form of the abbreviation for hours in Brazilian Portuguese.

Contributed by Mauricio Piacentini.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10173 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-08 01:39:09 +00:00
Toshi MARUYAMA
27127e1a3b Merged r10167 from trunk to 1.4-stable
Gemfile: mocha version up 0.12.3.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10172 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-08 01:38:54 +00:00
Toshi MARUYAMA
a92ef237d3 Merged r10155 from trunk to 1.4-stable
Gemfile: pin mocha version 0.12.1.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10168 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-08-07 23:21:45 +00:00
Toshi MARUYAMA
a82a16378f Merged r10137 from trunk to 1.4-stable (#10320, #10818)
Gemfile: prevent "rake db:migrate RAILS_ENV=test" causes exception on Ruby 1.9.3.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10139 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-07-31 08:47:21 +00:00
Toshi MARUYAMA
886daba641 Merged r10055 from trunk to 1.4-stable (#11448)
Russian translation for 1.4-stable and 2.0-stable updated by Александр Закревский.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10057 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-07-20 10:12:18 +00:00
Toshi MARUYAMA
ab8dd5aa6e Backported r10005 from trunk to 1.4-stable (#11406, #11404)
German translation for configurable session lifetime and timeout updated by Hannes Meier.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10008 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-07-16 14:17:35 +00:00
Jean-Philippe Lang
dbc02e66dc Backported r9982 and r9984 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@10000 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-07-15 16:00:58 +00:00
Jean-Philippe Lang
75e350f4e6 Merged r9924 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9960 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-07-08 12:38:57 +00:00
Jean-Philippe Lang
81553d396e Merged r9908 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9955 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-07-08 12:32:38 +00:00
Jean-Philippe Lang
77e03b508b Merged r9875 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9951 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-07-08 12:26:49 +00:00
Toshi MARUYAMA
1b0e717902 Merged r9911 from trunk to 1.4-stable (#11328)
Fix Japanese mistranslation for 'label_language_based'.

Contributed by Go MAEDA.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9918 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-07-05 13:10:27 +00:00
Toshi MARUYAMA
53b8de5e39 Backport r9890 from trunk to 1.4-stable
Simplified Chinese translation updated by Steven Wong (#11261).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9893 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-26 11:44:38 +00:00
Toshi MARUYAMA
34df573ec2 Merge r9870 from trunk
scm: git: fix unable to run unit lib test if git binary is not available on Windows.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9874 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-19 02:12:59 +00:00
Jean-Philippe Lang
e2f00792a8 Updates for 1.4.4 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9866 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-18 18:46:53 +00:00
Jean-Philippe Lang
9ed998af9b Merged r9861 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9863 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-18 18:35:50 +00:00
Jean-Philippe Lang
1881706df4 Backported r9858 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9860 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-18 18:29:15 +00:00
Toshi MARUYAMA
a3bf12ab9f Backport r9856 from trunk (#11186)
Traditional Chinese translation updated by ChunChang Lo.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9857 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-18 03:49:29 +00:00
Toshi MARUYAMA
668981b8cd Merged r9853 from trunk (#10688)
fix PDF export tables problems.

Contributed by Jun NAITOH.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9855 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-17 13:23:57 +00:00
Toshi MARUYAMA
b303dddbe3 Merge r9750 from trunk
scm: git: skip Latin-1 path tests on Git for Windows above 1.7.10

Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10.
http://code.google.com/p/msysgit/issues/detail?id=80

So, Latin-1 path tests fail on Japanese Windows.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9852 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-17 10:53:30 +00:00
Jean-Philippe Lang
933e96116e Fix for Rails vulnerabilities CVE-2012-2694 and CVE-2012-2695.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9849 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-17 09:21:16 +00:00
Jean-Philippe Lang
10711fda6d Merged r9837 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9847 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-17 09:01:17 +00:00
Jean-Philippe Lang
b3b829c025 Merged r9831 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9844 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-17 08:57:15 +00:00
Jean-Philippe Lang
8cd0baf773 Merged r9822 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9842 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-17 08:54:12 +00:00
Jean-Philippe Lang
033aa68427 CHANGELOG updated.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9841 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-17 08:52:59 +00:00
Etienne Massip
1eb20d42bd Merged r9832 fro trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9833 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-14 21:09:00 +00:00
Toshi MARUYAMA
a9dbecd5a5 Backported r9817 from trunk (#11130)
Simplified Chinese translation updated.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9821 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-11 07:30:25 +00:00
Toshi MARUYAMA
a13218e9f4 Backported r9816 from trunk (#11129)
Bulgarian translation updated by Ivan Cenov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9820 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-11 07:30:00 +00:00
Toshi MARUYAMA
57b0faf128 Backported r9815 from trunk (#11128)
Japanese translation updated by Go MAEDA.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9819 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-11 07:29:31 +00:00
Jean-Philippe Lang
ba3828b49f Backported r9797 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9810 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-10 20:30:58 +00:00
Jean-Philippe Lang
fe1a152e02 Merged r9798 to r9801 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9803 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-10 13:40:02 +00:00
Jean-Philippe Lang
69709a2513 CHANGELOG update.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9791 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-09 13:54:33 +00:00
Jean-Philippe Lang
7b7bca0b59 Merged r9783 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9790 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-09 13:53:37 +00:00
Toshi MARUYAMA
64b0cb336d Merged r9777 from trunk (#11113)
fix German "field_multiple" translation glitch by Andreas Deininger.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9779 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-08 07:50:40 +00:00
Toshi MARUYAMA
79b7b32980 1.4-stable: scm: git: backport creating and updating test from trunk r9616 (#10830)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9775 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-06 23:08:29 +00:00
Jean-Philippe Lang
1e1517e6ab Updates for 1.4.3 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9772 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-05 17:21:42 +00:00
Jean-Philippe Lang
70b0d5722b Rails CVE-2012-2660.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9767 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-03 20:08:23 +00:00
Jean-Philippe Lang
3883d5e2db Merged r9755 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9757 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-03 08:50:53 +00:00
Jean-Philippe Lang
bca6c447cb Merged r9740 and r9741 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9754 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-03 08:13:58 +00:00
Jean-Philippe Lang
b230429a61 Merged r9742 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9752 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-06-03 08:07:37 +00:00
Jean-Philippe Lang
a06462548c Updates for 1.4.2 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9692 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-13 19:09:35 +00:00
Jean-Philippe Lang
e62a40a719 Merged r9689 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9690 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-13 11:27:09 +00:00
Jean-Philippe Lang
5a1be1d8df Backported r9687 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9688 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-13 10:47:10 +00:00
Jean-Philippe Lang
81c207b193 Restored rescue statement removed in r9684.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9685 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-13 09:46:36 +00:00
Jean-Philippe Lang
60cdcd5522 Redirect to referer when deleting a user (#10865).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9684 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-13 09:17:56 +00:00
Jean-Philippe Lang
da293fdfd8 Merged #10837.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9676 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-12 12:59:23 +00:00
Jean-Philippe Lang
9cfb7e1c87 Merged r9671 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9672 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-12 12:42:30 +00:00
Toshi MARUYAMA
78c185abde set svn:eol-style native to Albanian translation files (#10875)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9669 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-11 05:33:07 +00:00
Toshi MARUYAMA
3266265cad Merged r9666 from trunk (#10875)
Albanian translation added by Dimitri Toslluku.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9668 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-11 05:30:30 +00:00
Jean-Philippe Lang
d9304e062a Fixed ActionController::TestUploadedFile#respond_to? for failing tests.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9656 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-08 11:18:31 +00:00
Jean-Philippe Lang
40af0a2cbf Merged r9654 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9655 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-08 11:05:37 +00:00
Jean-Philippe Lang
eb53d600c9 Fixed that REST Uploads fail with fastcgi in 1.4-stable (#10832).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9653 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-08 10:39:42 +00:00
Jean-Philippe Lang
fa4fdf91a4 Do not trigger model validations when rebuilding the nested set (#10829).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9631 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-05 09:37:12 +00:00
Jean-Philippe Lang
e9ac98b249 Merged r9619 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9620 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-04 16:58:08 +00:00
Jean-Philippe Lang
9db20cd02c Merged r9551 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9607 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-01 16:14:07 +00:00
Jean-Philippe Lang
b0951bff54 Merged r9582 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9606 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-05-01 16:13:00 +00:00
Jean-Philippe Lang
0a09984954 Merged r9553 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9593 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-30 18:50:05 +00:00
Toshi MARUYAMA
9b7105465e Merged r9591 from trunk (#10800)
Simplified Chinese translation updated.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9592 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-30 13:15:07 +00:00
Toshi MARUYAMA
45683d9c2a route: add revision requirement to repository raw action lost in 1.4-stable
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9545 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-27 15:41:07 +00:00
Toshi MARUYAMA
dcba6f0400 Merged r9543 from trunk (#10785)
Bulgarian translation (jstoolbar) updated by Ivan Cenov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9544 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-27 11:49:39 +00:00
Toshi MARUYAMA
f11e9eb2d6 Merged r9523 from trunk (#10750)
Swedish translation updated by Nicklas Holm.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9524 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-24 09:49:07 +00:00
Toshi MARUYAMA
095d99cf9e Merged r9520 from trunk (#10745)
Japanese translation updated by Go MAEDA.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9521 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-24 07:33:17 +00:00
Toshi MARUYAMA
163ac957ba Merged r9509 from trunk (#10733)
Traditional Chinese translation updated by ChunChang Lo.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9512 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-23 08:38:28 +00:00
Jean-Philippe Lang
04a8a72491 Updates for 1.4.1 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9460 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-20 10:00:24 +00:00
Toshi MARUYAMA
a1a6dcffca Merged r9444 from trunk (#10664)
Updated ru and uk locales.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9458 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-20 08:33:46 +00:00
Jean-Philippe Lang
d31402734b Merged r9424, r9426, r9433 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9457 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-20 08:11:01 +00:00
Jean-Philippe Lang
5180ca0cdd Merged r9421 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9456 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-20 08:09:20 +00:00
Jean-Philippe Lang
e5802895ce Merged r9417 from trunk (#10664).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9455 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-20 07:45:33 +00:00
Jean-Philippe Lang
da1a3449ce Merged r9449 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9454 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-20 06:14:32 +00:00
Jean-Philippe Lang
2b2f721ef4 Merged r9452 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9453 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-20 06:13:37 +00:00
Jean-Philippe Lang
62114336cf Merged r9450 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9451 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-19 09:23:27 +00:00
Jean-Philippe Lang
4f48d1b4dd Merged r9442 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9443 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-19 07:19:30 +00:00
Jean-Philippe Lang
d6d064f875 Merged r9420 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9436 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-18 17:42:50 +00:00
Jean-Philippe Lang
6d8649b9d3 Merged r9419 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9435 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-18 17:42:09 +00:00
Jean-Philippe Lang
39deb03855 Merged r9430 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9434 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-18 17:41:28 +00:00
Jean-Philippe Lang
70036a7ad0 Merged r9431 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9432 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-18 12:05:35 +00:00
Toshi MARUYAMA
68d421b978 Merged r9427 from trunk
German translation changed by Hannes Meier (#10693)

* mail_body_account_information_external
* label_overall_activity
* label_send_information

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9429 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-18 00:16:11 +00:00
Toshi MARUYAMA
268a9db47e Merged r9425 from trunk
German translation for 1.4.0 updated by Hannes Meier (#10693)

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9428 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-18 00:15:33 +00:00
Jean-Philippe Lang
a35b81b9fa Merged r9412 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9413 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 08:08:22 +00:00
Jean-Philippe Lang
fd450fd2da Merged r9404, r9405 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9411 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 06:54:02 +00:00
Jean-Philippe Lang
2c0ba78f70 Merged r9409 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9410 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 06:53:10 +00:00
Jean-Philippe Lang
f0f01d370e Merged r9406 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9408 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 06:32:18 +00:00
Jean-Philippe Lang
4c330a1241 Merged r9391 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9403 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:58:46 +00:00
Jean-Philippe Lang
baa4ebd05f Merged r9389 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9402 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:57:13 +00:00
Jean-Philippe Lang
59f14478ed Merged r9387 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9401 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:53:47 +00:00
Jean-Philippe Lang
8fefb7c05b Merged r9390 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9400 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:52:49 +00:00
Jean-Philippe Lang
1feb373c89 Merged r9378 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9399 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:51:49 +00:00
Jean-Philippe Lang
32fd503cbb Merged r9381 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9398 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:50:58 +00:00
Jean-Philippe Lang
cf31aeaf81 Merged r9380 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9397 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:45:44 +00:00
Jean-Philippe Lang
83ea66fd2c Merged r9379 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9395 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:43:41 +00:00
Jean-Philippe Lang
ef2c5cab2d Merged r9392 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9393 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-13 19:41:05 +00:00
Etienne Massip
dee6f6b138 Merged r9374 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9388 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-11 18:31:32 +00:00
Jean-Philippe Lang
a4c0c18e3d Merged r9384, r9385 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9386 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-11 17:27:44 +00:00
Jean-Philippe Lang
4c82fbb6f8 Merged r9382 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9383 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-11 17:21:35 +00:00
Jean-Philippe Lang
68ded50edc Merged r9372 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9377 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-09 06:28:04 +00:00
Jean-Philippe Lang
72ecb80dc7 Merged r9358 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9376 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-09 06:27:07 +00:00
Jean-Philippe Lang
86ee285eb4 Merged r9367 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9375 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-09 06:22:31 +00:00
Jean-Philippe Lang
c229ea6386 Merged r9371 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9373 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-08 21:01:20 +00:00
Jean-Philippe Lang
687fca170e Merged r9359 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9360 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 13:51:55 +00:00
Toshi MARUYAMA
26564b06f7 Merged r9355 from trunk
remove 1.3-stable merged issues from CHANGELOG 1.4.0 list

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9356 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 13:01:12 +00:00
Jean-Philippe Lang
34016c38bd Merged r9349 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9354 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 12:39:51 +00:00
Jean-Philippe Lang
15ff361894 Merged r9350 and r9351 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9352 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 12:23:07 +00:00
Jean-Philippe Lang
b45b5f4322 Merged r9346 and r9347 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9348 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 11:17:31 +00:00
Toshi MARUYAMA
8addbc537a Merged r9343 from trunk
Simplified Chinese translation updated by fangzheng (#10611)

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9344 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 10:45:24 +00:00
Jean-Philippe Lang
87eeacba80 Merged r9341 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9342 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 10:27:22 +00:00
Jean-Philippe Lang
a7250c41e2 Merged r9339 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9340 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 10:25:03 +00:00
Jean-Philippe Lang
7c45396d92 Set version to stable.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9338 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 09:45:43 +00:00
Jean-Philippe Lang
c1f98c835c Adds 1.4-stable branch.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9337 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 09:41:15 +00:00
1521 changed files with 42198 additions and 40953 deletions

2
.gitignore vendored
View File

@@ -5,7 +5,6 @@
/config/database.yml
/config/email.yml
/config/initializers/session_store.rb
/config/initializers/secret_token.rb
/coverage
/db/*.db
/db/*.sqlite3
@@ -23,7 +22,6 @@
/tmp/sessions/*
/tmp/sockets/*
/tmp/test/*
/tmp/thumbnails/*
/vendor/cache
/vendor/rails
*.rbc

View File

@@ -7,7 +7,6 @@ config/configuration.yml
config/database.yml
config/email.yml
config/initializers/session_store.rb
config/initializers/secret_token.rb
coverage
db/*.db
db/*.sqlite3
@@ -25,7 +24,6 @@ tmp/pdf/*
tmp/sessions/*
tmp/sockets/*
tmp/test/*
tmp/thumbnails/*
vendor/cache
vendor/rails
*.rbc

99
Gemfile
View File

@@ -1,11 +1,10 @@
source 'https://rubygems.org'
source :rubygems
gem "rails", "3.2.13"
gem "jquery-rails", "~> 2.0.2"
gem "i18n", "~> 0.6.0"
gem "coderay", "~> 1.0.9"
gem "rails", "2.3.15"
gem "i18n", "~> 0.4.2"
gem "coderay", "~> 1.0.6"
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
gem "builder", "3.0.0"
gem "tzinfo", "~> 0.3.31"
# Optional gem for LDAP authentication
group :ldap do
@@ -14,8 +13,7 @@ end
# Optional gem for OpenID authentication
group :openid do
gem "ruby-openid", "~> 2.3.0", :require => "openid"
gem "rack-openid"
gem "ruby-openid", "~> 2.1.4", :require => "openid"
end
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
@@ -28,60 +26,55 @@ platforms :mri, :mingw do
end
end
platforms :jruby do
# jruby-openssl is bundled with JRuby 1.7.0
gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0'
gem "activerecord-jdbc-adapter", "~> 1.2.6"
# Database gems
platforms :mri, :mingw do
group :postgresql do
gem "pg", ">= 0.11.0"
end
group :sqlite do
gem "sqlite3"
end
end
# Include database gems for the adapters found in the database
# configuration file
require 'erb'
require 'yaml'
database_file = File.join(File.dirname(__FILE__), "config/database.yml")
if File.exist?(database_file)
database_config = YAML::load(ERB.new(IO.read(database_file)).result)
adapters = database_config.values.map {|c| c['adapter']}.compact.uniq
if adapters.any?
adapters.each do |adapter|
case adapter
when 'mysql2'
gem "mysql2", "~> 0.3.11", :platforms => [:mri, :mingw]
gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
when 'mysql'
gem "mysql", "~> 2.8.1", :platforms => [:mri, :mingw]
gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
when /postgresql/
gem "pg", ">= 0.11.0", :platforms => [:mri, :mingw]
gem "activerecord-jdbcpostgresql-adapter", :platforms => :jruby
when /sqlite3/
gem "sqlite3", :platforms => [:mri, :mingw]
gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
when /sqlserver/
gem "tiny_tds", "~> 0.5.1", :platforms => [:mri, :mingw]
gem "activerecord-sqlserver-adapter", :platforms => [:mri, :mingw]
else
warn("Unknown database adapter `#{adapter}` found in config/database.yml, use Gemfile.local to load your own database gems")
end
end
else
warn("No adapter found in config/database.yml, please configure it first")
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.2.7"
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
else
warn("Please configure your config/database.yml first")
end
group :development do
gem "rdoc", ">= 2.4.2"
gem "yard"
end
group :test do
gem "shoulda", "~> 3.3.2"
gem "mocha", "~> 0.13.3"
gem 'capybara', '~> 2.0.0'
gem 'nokogiri', '< 1.6.0'
gem 'selenium-webdriver', '2.35.1'
gem "shoulda", "~> 2.10.3"
# Shoulda does not work nice on Ruby 1.9.3 and seems to need test-unit explicitely.
gem "test-unit", :platforms => [:mri_19]
gem "edavis10-object_daddy", :require => "object_daddy"
gem "mocha", "0.12.3"
end
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
@@ -91,7 +84,7 @@ if File.exists?(local_gemfile)
end
# Load plugins' Gemfiles
Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file|
Dir.glob File.expand_path("../vendor/plugins/*/Gemfile", __FILE__) do |file|
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
instance_eval File.read(file)
end

View File

@@ -1,7 +1,15 @@
#!/usr/bin/env 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'

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -25,9 +25,7 @@ class AccountController < ApplicationController
# Login request and validation
def login
if request.get?
if User.current.logged?
redirect_to home_url
end
logout_user
else
authenticate_user
end
@@ -38,35 +36,23 @@ class AccountController < ApplicationController
# Log out current user and redirect to welcome page
def logout
if User.current.anonymous?
redirect_to home_url
elsif request.post?
logout_user
redirect_to home_url
end
# display the logout form
logout_user
redirect_to home_url
end
# Lets user choose a new password
# Enable user to choose a new 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]
@token = Token.find_token("recovery", params[:token].to_s)
if @token.nil? || @token.expired?
redirect_to home_url
return
end
@token = Token.find_by_action_and_value("recovery", params[:token])
redirect_to(home_url) && return unless @token and !@token.expired?
@user = @token.user
unless @user && @user.active?
redirect_to home_url
return
end
if request.post?
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
if @user.save
@token.destroy
flash[:notice] = l(:notice_account_password_updated)
redirect_to signin_path
redirect_to :action => 'login'
return
end
end
@@ -74,23 +60,17 @@ class AccountController < ApplicationController
return
else
if request.post?
user = User.find_by_mail(params[:mail].to_s)
# user not found or not active
unless user && user.active?
flash.now[:error] = l(:notice_account_unknown_email)
return
end
# user cannot change its password
unless user.change_password_allowed?
flash.now[:error] = l(:notice_can_t_change_password)
return
end
user = User.find_by_mail(params[:mail])
# user not found in db
(flash.now[:error] = l(:notice_account_unknown_email); return) unless user
# user uses an external authentification
(flash.now[:error] = l(:notice_can_t_change_password); return) if user.auth_source_id
# create a new token for password recovery
token = Token.new(:user => user, :action => "recovery")
if token.save
Mailer.lost_password(token).deliver
Mailer.deliver_lost_password(token)
flash[:notice] = l(:notice_account_lost_email_sent)
redirect_to signin_path
redirect_to :action => 'login'
return
end
end
@@ -99,14 +79,13 @@ class AccountController < ApplicationController
# User self-registration
def register
(redirect_to(home_url); return) unless Setting.self_registration? || session[:auth_source_registration]
redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
if request.get?
session[:auth_source_registration] = nil
@user = User.new(:language => current_language.to_s)
@user = User.new(:language => Setting.default_language)
else
user_params = params[:user] || {}
@user = User.new
@user.safe_attributes = user_params
@user.safe_attributes = params[:user]
@user.admin = false
@user.register
if session[:auth_source_registration]
@@ -117,13 +96,11 @@ class AccountController < ApplicationController
session[:auth_source_registration] = nil
self.logged_user = @user
flash[:notice] = l(:notice_account_activated)
redirect_to my_account_path
redirect_to :controller => 'my', :action => 'account'
end
else
@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 = user_params[:password], user_params[:password_confirmation]
end
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
case Setting.self_registration
when '1'
@@ -139,17 +116,17 @@ class AccountController < ApplicationController
# Token based account activation
def activate
(redirect_to(home_url); return) unless Setting.self_registration? && params[:token].present?
token = Token.find_token('register', params[:token].to_s)
(redirect_to(home_url); return) unless token and !token.expired?
redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
token = Token.find_by_action_and_value('register', params[:token])
redirect_to(home_url) && return unless token and !token.expired?
user = token.user
(redirect_to(home_url); return) unless user.registered?
redirect_to(home_url) && return unless user.registered?
user.activate
if user.save
token.destroy
flash[:notice] = l(:notice_account_activated)
end
redirect_to signin_path
redirect_to :action => 'login'
end
private
@@ -176,14 +153,12 @@ class AccountController < ApplicationController
end
def open_id_authenticate(openid_url)
back_url = signin_url(:autologin => params[:autologin])
authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => back_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?
user = User.find_or_initialize_by_identity_url(identity_url)
if user.new_record?
# Self-registration off
(redirect_to(home_url); return) unless Setting.self_registration?
redirect_to(home_url) && return unless Setting.self_registration?
# Create on the fly
user.login = registration['nickname'] unless registration['nickname'].nil?
@@ -219,7 +194,6 @@ class AccountController < ApplicationController
end
def successful_authentication(user)
logger.info "Successful authentication for '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}"
# Valid user
self.logged_user = user
# generate a key and set cookie if autologin
@@ -227,11 +201,12 @@ class AccountController < ApplicationController
set_autologin_cookie(user)
end
call_hook(:controller_account_success_authentication_after, {:user => user })
redirect_back_or_default my_page_path
redirect_back_or_default :controller => 'my', :action => 'page'
end
def set_autologin_cookie(user)
token = Token.create(:user => user, :action => 'autologin')
cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin'
cookie_options = {
:value => token.value,
:expires => 1.year.from_now,
@@ -239,7 +214,7 @@ class AccountController < ApplicationController
:secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
:httponly => true
}
cookies[autologin_cookie_name] = cookie_options
cookies[cookie_name] = cookie_options
end
# Onthefly creation failed, display the registration form to fill/fix attributes
@@ -260,9 +235,9 @@ class AccountController < ApplicationController
def register_by_email_activation(user, &block)
token = Token.new(:user => user, :action => "register")
if user.save and token.save
Mailer.register(token).deliver
Mailer.deliver_register(token)
flash[:notice] = l(:notice_account_register_done)
redirect_to signin_path
redirect_to :action => 'login'
else
yield if block_given?
end
@@ -278,7 +253,7 @@ class AccountController < ApplicationController
if user.save
self.logged_user = user
flash[:notice] = l(:notice_account_activated)
redirect_to my_account_path
redirect_to :controller => 'my', :action => 'account'
else
yield if block_given?
end
@@ -290,7 +265,7 @@ class AccountController < ApplicationController
def register_manually_by_administrator(user, &block)
if user.save
# Sends an email to the administrators
Mailer.account_activation_request(user).deliver
Mailer.deliver_account_activation_request(user)
account_pending
else
yield if block_given?
@@ -299,6 +274,6 @@ class AccountController < ApplicationController
def account_pending
flash[:notice] = l(:notice_account_pending)
redirect_to signin_path
redirect_to :action => 'login'
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -40,10 +40,10 @@ class ActivitiesController < ApplicationController
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|
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?
}
format.atom {

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -23,18 +23,19 @@ class AdminController < ApplicationController
before_filter :require_admin
helper :sort
include SortHelper
include SortHelper
def index
@no_configuration_data = Redmine::DefaultData::Loader::no_data?
end
def projects
@status = params[:status] || 1
scope = Project.status(@status).order('lft')
scope = Project.status(@status)
scope = scope.like(params[:name]) if params[:name].present?
@projects = scope.all
@projects = scope.all(:order => 'lft')
render :action => "projects", :layout => false if request.xhr?
end
@@ -54,7 +55,7 @@ class AdminController < ApplicationController
flash[:error] = l(:error_can_t_load_default_data, e.message)
end
end
redirect_to admin_path
redirect_to :action => 'index'
end
def test_email
@@ -62,13 +63,13 @@ class AdminController < ApplicationController
# Force ActionMailer to raise delivery errors so we can catch it
ActionMailer::Base.raise_delivery_errors = true
begin
@test = Mailer.test_email(User.current).deliver
@test = Mailer.deliver_test_email(User.current)
flash[:notice] = l(:notice_email_sent, User.current.mail)
rescue Exception => e
flash[:error] = l(:notice_email_error, e.message)
end
ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
redirect_to settings_path(:tab => 'notifications')
redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications'
end
def info

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -22,32 +22,54 @@ class Unauthorized < Exception; end
class ApplicationController < ActionController::Base
include Redmine::I18n
include Redmine::Pagination
include RoutesHelper
helper :routes
class_attribute :accept_api_auth_actions
class_attribute :accept_rss_auth_actions
class_attribute :model_object
layout 'base'
exempt_from_layout 'builder', 'rsb'
protect_from_forgery
def handle_unverified_request
super
cookies.delete(autologin_cookie_name)
cookies.delete(:autologin)
end
# FIXME: Remove this when all of Rack and Rails have learned how to
# properly use encodings
before_filter :params_filter
def params_filter
if RUBY_VERSION >= '1.9' && defined?(Rails) && Rails::VERSION::MAJOR < 3
self.utf8nize!(params)
end
end
def utf8nize!(obj)
if obj.frozen?
obj
elsif obj.is_a? String
obj.respond_to?(:force_encoding) ? obj.force_encoding("UTF-8") : obj
elsif obj.is_a? Hash
obj.each {|k, v| obj[k] = self.utf8nize!(v)}
elsif obj.is_a? Array
obj.each {|v| self.utf8nize!(v)}
else
obj
end
end
before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization
filter_parameter_logging :password
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
rescue_from ::Unauthorized, :with => :deny_access
rescue_from ::ActionView::MissingTemplate, :with => :missing_template
include Redmine::Search::Controller
include Redmine::MenuManager::MenuController
helper Redmine::MenuManager::MenuHelper
Redmine::Scm::Base.all.each do |scm|
require_dependency "repository/#{scm.underscore}"
end
def session_expiration
if session[:user_id]
if session_expired? && !try_to_autologin
@@ -85,56 +107,36 @@ class ApplicationController < ActionController::Base
Setting.check_cache
# Find the 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
# Returns the current user or nil if no user is logged in
# and starts a session if needed
def find_current_user
user = nil
unless api_request?
if session[:user_id]
# existing session
user = (User.active.find(session[:user_id]) rescue nil)
elsif autologin_user = try_to_autologin
user = autologin_user
elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
# RSS key authentication does not start a session
user = User.find_by_rss_key(params[:key])
end
end
if user.nil? && Setting.rest_api_enabled? && accept_api_auth?
if session[:user_id]
# existing session
(User.active.find(session[:user_id]) rescue nil)
elsif user = try_to_autologin
user
elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
# RSS key authentication does not start a session
User.find_by_rss_key(params[:key])
elsif Setting.rest_api_enabled? && accept_api_auth?
if (key = api_key_from_request)
# Use API key
user = User.find_by_api_key(key)
User.find_by_api_key(key)
else
# HTTP Basic, either username/password or API key/random
authenticate_with_http_basic do |username, password|
user = User.try_to_login(username, password) || User.find_by_api_key(username)
end
end
# Switch user if requested by an admin user
if user && user.admin? && (username = api_switch_user_from_request)
su = User.find_by_login(username)
if su && su.active?
logger.info(" User switched by: #{user.login} (id=#{user.id})") if logger
user = su
else
render_error :message => 'Invalid X-Redmine-Switch-User header', :status => 412
User.try_to_login(username, password) || User.find_by_api_key(username)
end
end
end
user
end
def autologin_cookie_name
Redmine::Configuration['autologin_cookie_name'].presence || 'autologin'
end
def try_to_autologin
if cookies[autologin_cookie_name] && Setting.autologin?
if cookies[:autologin] && Setting.autologin?
# auto-login feature starts a new session
user = User.try_to_autologin(cookies[autologin_cookie_name])
user = User.try_to_autologin(cookies[:autologin])
if user
reset_session
start_user_session(user)
@@ -157,7 +159,7 @@ class ApplicationController < ActionController::Base
# Logs out current user
def logout_user
if User.current.logged?
cookies.delete(autologin_cookie_name)
cookies.delete :autologin
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
self.logged_user = nil
end
@@ -270,7 +272,7 @@ class ApplicationController < ActionController::Base
end
def find_model_object
model = self.class.model_object
model = self.class.read_inheritable_attribute('model_object')
if model
@object = model.find(params[:id])
self.instance_variable_set('@' + controller_name.singularize, @object) if @object
@@ -280,47 +282,27 @@ class ApplicationController < ActionController::Base
end
def self.model_object(model)
self.model_object = model
write_inheritable_attribute('model_object', model)
end
# Find the issue whose id is the :id parameter
# Raises a Unauthorized exception if the issue is not visible
def find_issue
# Issue.visible.find(...) can not be used to redirect user to the login form
# if the issue actually exists but requires authentication
@issue = Issue.find(params[:id])
raise Unauthorized unless @issue.visible?
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
# Find issues with a single :id param or :ids array param
# Raises a Unauthorized exception if one of the issues is not visible
# Filter for bulk issue operations
def find_issues
@issues = Issue.find_all_by_id(params[:id] || params[:ids])
raise ActiveRecord::RecordNotFound if @issues.empty?
raise Unauthorized unless @issues.all?(&:visible?)
if @issues.detect {|issue| !issue.visible?}
deny_access
return
end
@projects = @issues.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1
rescue ActiveRecord::RecordNotFound
render_404
end
def find_attachments
if (attachments = params[:attachments]).present?
att = attachments.values.collect do |attachment|
Attachment.find_by_token( attachment[:token] ) if attachment[:token].present?
end
att.compact!
end
@attachments = att || []
end
# 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
def check_project_privacy
if @project && !@project.archived?
if @project && @project.active?
if @project.visible?
true
else
@@ -334,16 +316,12 @@ class ApplicationController < ActionController::Base
end
def back_url
url = params[:back_url]
if url.nil? && referer = request.env['HTTP_REFERER']
url = CGI.unescape(referer.to_s)
end
url
params[:back_url] || request.env['HTTP_REFERER']
end
def redirect_back_or_default(default)
back_url = params[:back_url].to_s
if back_url.present?
back_url = CGI.unescape(params[:back_url].to_s)
if !back_url.blank?
begin
uri = URI.parse(back_url)
# do not redirect user to another host or to the login or register page
@@ -352,7 +330,6 @@ class ApplicationController < ActionController::Base
return
end
rescue URI::InvalidURIError
logger.warn("Could not redirect to invalid URL #{back_url}")
# redirect to default
end
end
@@ -360,19 +337,6 @@ class ApplicationController < ActionController::Base
false
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={})
@project = nil
render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
@@ -396,17 +360,13 @@ class ApplicationController < ActionController::Base
format.html {
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
# 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
# but have no HTML representation for non admin users
def require_admin_or_api_request
@@ -439,15 +399,15 @@ class ApplicationController < ActionController::Base
@items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
@items = @items.slice(0, Setting.feeds_limit.to_i)
@title = options[:title] || Setting.app_title
render :template => "common/feed", :formats => [:atom], :layout => false,
render :template => "common/feed.atom", :layout => false,
:content_type => 'application/atom+xml'
end
def self.accept_rss_auth(*actions)
if actions.any?
self.accept_rss_auth_actions = actions
write_inheritable_attribute('accept_rss_auth_actions', actions)
else
self.accept_rss_auth_actions || []
read_inheritable_attribute('accept_rss_auth_actions') || []
end
end
@@ -457,9 +417,9 @@ class ApplicationController < ActionController::Base
def self.accept_api_auth(*actions)
if actions.any?
self.accept_api_auth_actions = actions
write_inheritable_attribute('accept_api_auth_actions', actions)
else
self.accept_api_auth_actions || []
read_inheritable_attribute('accept_api_auth_actions') || []
end
end
@@ -545,11 +505,6 @@ class ApplicationController < ActionController::Base
end
end
# Returns the API 'switch user' value if present
def api_switch_user_from_request
request.headers["X-Redmine-Switch-User"].to_s.presence
end
# Renders a warning flash if obj has unsaved attachments
def render_attachment_warning_if_needed(obj)
flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
@@ -578,17 +533,6 @@ 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."
end
# Renders a 200 response for successfull updates or deletions via the API
def render_api_ok
render_api_head :ok
end
# Renders a head API response
def render_api_head(status)
# #head would return a response body with one space
render :text => '', :status => status, :layout => nil
end
# Renders API response on validation failure
def render_validation_errors(objects)
if objects.is_a?(Array)
@@ -596,12 +540,26 @@ class ApplicationController < ActionController::Base
else
@error_messages = objects.errors.full_messages
end
render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil
render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => false
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
def _include_layout?(*args)
api_request? ? false : super
def pick_layout(*args)
api_request? ? nil : super
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -17,7 +17,7 @@
class AttachmentsController < ApplicationController
before_filter :find_project, :except => :upload
before_filter :file_readable, :read_authorize, :only => [:show, :download, :thumbnail]
before_filter :file_readable, :read_authorize, :only => [:show, :download]
before_filter :delete_authorize, :only => :destroy
before_filter :authorize_global, :only => :upload
@@ -52,26 +52,11 @@ class AttachmentsController < ApplicationController
@attachment.increment_download
end
if stale?(:etag => @attachment.digest)
# images are sent inline
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
:type => detect_content_type(@attachment),
:disposition => (@attachment.image? ? 'inline' : 'attachment')
end
end
# images are sent inline
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
:type => detect_content_type(@attachment),
:disposition => (@attachment.image? ? 'inline' : 'attachment')
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
@@ -84,18 +69,16 @@ class AttachmentsController < ApplicationController
@attachment = Attachment.new(:file => request.raw_post)
@attachment.author = User.current
@attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
saved = @attachment.save
@attachment.filename = Redmine::Utils.random_hex(16)
respond_to do |format|
format.js
format.api {
if saved
render :action => 'upload', :status => :created
else
render_validation_errors(@attachment)
end
}
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
@@ -103,17 +86,11 @@ class AttachmentsController < ApplicationController
if @attachment.container.respond_to?(:init_journal)
@attachment.container.init_journal(User.current)
end
if @attachment.container
# Make sure association callbacks are called
@attachment.container.attachments.delete(@attachment)
else
@attachment.destroy
end
respond_to do |format|
format.html { redirect_to_referer_or project_path(@project) }
format.js
end
# Make sure association callbacks are called
@attachment.container.attachments.delete(@attachment)
redirect_to :back
rescue ::ActionController::RedirectBackError
redirect_to :controller => 'projects', :action => 'show', :id => @project
end
private
@@ -128,12 +105,7 @@ private
# Checks that the file exists and is readable
def file_readable
if @attachment.readable?
true
else
logger.error "Cannot send attachment, #{@attachment.diskfile} does not exist or is unreadable."
render_404
end
@attachment.readable? ? true : render_404
end
def read_authorize

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,77 +20,57 @@ class AuthSourcesController < ApplicationController
menu_item :ldap_authentication
before_filter :require_admin
before_filter :find_auth_source, :only => [:edit, :update, :test_connection, :destroy]
def index
@auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 25
@auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 10
end
def new
klass_name = params[:type] || 'AuthSourceLdap'
@auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source])
render_404 unless @auth_source
end
def create
@auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source])
if @auth_source.save
flash[:notice] = l(:notice_successful_create)
redirect_to auth_sources_path
redirect_to :action => 'index'
else
render :action => 'new'
end
end
def edit
@auth_source = AuthSource.find(params[:id])
end
def update
@auth_source = AuthSource.find(params[:id])
if @auth_source.update_attributes(params[:auth_source])
flash[:notice] = l(:notice_successful_update)
redirect_to auth_sources_path
redirect_to :action => 'index'
else
render :action => 'edit'
end
end
def test_connection
@auth_source = AuthSource.find(params[:id])
begin
@auth_source.test_connection
flash[:notice] = l(:notice_successful_connection)
rescue Exception => e
flash[:error] = l(:error_unable_to_connect, e.message)
end
redirect_to auth_sources_path
redirect_to :action => 'index'
end
def destroy
unless @auth_source.users.exists?
@auth_source = AuthSource.find(params[:id])
unless @auth_source.users.find(:first)
@auth_source.destroy
flash[:notice] = l(:notice_successful_delete)
end
redirect_to auth_sources_path
end
def autocomplete_for_new_user
results = AuthSource.search(params[:term])
render :json => results.map {|result| {
'value' => result[:login],
'label' => "#{result[:login]} (#{result[:firstname]} #{result[:lastname]})",
'login' => result[:login].to_s,
'firstname' => result[:firstname].to_s,
'lastname' => result[:lastname].to_s,
'mail' => result[:mail].to_s,
'auth_source_id' => result[:auth_source_id].to_s
}}
end
private
def find_auth_source
@auth_source = AuthSource.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
redirect_to :action => 'index'
end
end

View File

@@ -1,44 +1,27 @@
# Redmine - project management software
# Copyright (C) 2006-2013 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
before_filter :find_project
def issues
@issues = []
q = (params[:q] || params[:term]).to_s.strip
if q.present?
scope = (params[:scope] == "all" || @project.nil? ? Issue : @project.issues).visible
if q.match(/\A#?(\d+)\z/)
@issues << scope.find_by_id($1.to_i)
end
@issues += scope.where("LOWER(#{Issue.table_name}.subject) LIKE LOWER(?)", "%#{q}%").order("#{Issue.table_name}.id DESC").limit(10).all
@issues.compact!
q = params[:q].to_s
query = (params[:scope] == "all" && Setting.cross_project_issue_relations?) ? Issue : @project.issues
if q.match(/^\d+$/)
@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
@issues.compact!
render :layout => false
end
private
def find_project
if params[:project_id].present?
@project = Project.find(params[:project_id])
end
project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
@project = Project.find(project_id)
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -25,7 +25,7 @@ class BoardsController < ApplicationController
helper :watchers
def index
@boards = @project.boards.includes(:last_message => :author).all
@boards = @project.boards
# show the board if there is only one
if @boards.size == 1
@board = @boards.first
@@ -37,29 +37,23 @@ class BoardsController < ApplicationController
respond_to do |format|
format.html {
sort_init 'updated_on', 'desc'
sort_update 'created_on' => "#{Message.table_name}.created_on",
sort_update 'created_on' => "#{Message.table_name}.created_on",
'replies' => "#{Message.table_name}.replies_count",
'updated_on' => "COALESCE(last_replies_messages.created_on, #{Message.table_name}.created_on)"
'updated_on' => "#{Message.table_name}.updated_on"
@topic_count = @board.topics.count
@topic_pages = Paginator.new @topic_count, per_page_option, params['page']
@topics = @board.topics.
reorder("#{Message.table_name}.sticky DESC").
includes(:last_reply).
limit(@topic_pages.per_page).
offset(@topic_pages.offset).
order(sort_clause).
preload(:author, {:last_reply => :author}).
all
@topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
@topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '),
:include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset
@message = Message.new(:board => @board)
render :action => 'show', :layout => !request.xhr?
}
format.atom {
@messages = @board.messages.
reorder('created_on DESC').
includes(:author, :board).
limit(Setting.feeds_limit.to_i).
all
@messages = @board.messages.find :all, :order => 'created_on DESC',
:include => [:author, :board],
:limit => Setting.feeds_limit.to_i
render_feed(@messages, :title => "#{@project}: #{@board}")
}
end
@@ -100,7 +94,7 @@ class BoardsController < ApplicationController
private
def redirect_to_settings_in_projects
redirect_to settings_project_path(@project, :tab => 'boards')
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end
def find_board_if_available

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# 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
@@ -32,12 +32,12 @@ class CommentsController < ApplicationController
flash[:notice] = l(:label_comment_added)
end
redirect_to news_path(@news)
redirect_to :controller => 'news', :action => 'show', :id => @news
end
def destroy
@news.comments.find(params[:comment_id]).destroy
redirect_to news_path(@news)
redirect_to :controller => 'news', :action => 'show', :id => @news
end
private

View File

@@ -1,31 +1,12 @@
# Redmine - project management software
# Copyright (C) 2006-2013 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
helper :watchers
helper :issues
def issues
@issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project)
(render_404; return) unless @issues.present?
if (@issues.size == 1)
@issue = @issues.first
end
@issue_ids = @issues.map(&:id).sort
@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
@projects = @issues.collect(&:project).compact.uniq
@@ -50,7 +31,6 @@ class ContextMenusController < ApplicationController
@assignables = @projects.map(&:assignable_users).reduce(:&)
@trackers = @projects.map(&:trackers).reduce(:&)
end
@versions = @projects.map {|p| p.shared_versions.open}.reduce(:&)
@priorities = IssuePriority.active.reverse
@back = back_url
@@ -68,15 +48,12 @@ class ContextMenusController < ApplicationController
end
end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
render :layout => false
end
def time_entries
@time_entries = TimeEntry.all(
:conditions => {:id => params[:ids]}, :include => :project)
(render_404; return) unless @time_entries.present?
@projects = @time_entries.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1
@activities = TimeEntryActivity.shared.active

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -23,7 +23,7 @@ class CustomFieldsController < ApplicationController
before_filter :find_custom_field, :only => [:edit, :update, :destroy]
def index
@custom_fields_by_type = CustomField.all.group_by {|f| f.class.name }
@custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
@tab = params[:tab] || 'IssueCustomField'
end
@@ -31,10 +31,10 @@ class CustomFieldsController < ApplicationController
end
def create
if @custom_field.save
if request.post? and @custom_field.save
flash[:notice] = l(:notice_successful_create)
call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
redirect_to custom_fields_path(:tab => @custom_field.class.name)
redirect_to :action => 'index', :tab => @custom_field.class.name
else
render :action => 'new'
end
@@ -44,22 +44,21 @@ class CustomFieldsController < ApplicationController
end
def update
if @custom_field.update_attributes(params[:custom_field])
if request.put? and @custom_field.update_attributes(params[:custom_field])
flash[:notice] = l(:notice_successful_update)
call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
redirect_to custom_fields_path(:tab => @custom_field.class.name)
redirect_to :action => 'index', :tab => @custom_field.class.name
else
render :action => 'edit'
end
end
def destroy
begin
@custom_field.destroy
rescue
flash[:error] = l(:error_can_not_delete_custom_field)
end
redirect_to custom_fields_path(:tab => @custom_field.class.name)
@custom_field.destroy
redirect_to :action => 'index', :tab => @custom_field.class.name
rescue
flash[:error] = l(:error_can_not_delete_custom_field)
redirect_to :action => 'index'
end
private
@@ -68,8 +67,6 @@ class CustomFieldsController < ApplicationController
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
if @custom_field.nil?
render_404
else
@custom_field.default_value = nil
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -27,7 +27,7 @@ class DocumentsController < ApplicationController
def index
@sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
documents = @project.documents.includes(:attachments, :category).all
documents = @project.documents.find :all, :include => [:attachments, :category]
case @sort_by
when 'date'
@grouped = documents.group_by {|d| d.updated_on.to_date }
@@ -43,7 +43,7 @@ class DocumentsController < ApplicationController
end
def show
@attachments = @document.attachments.all
@attachments = @document.attachments.find(:all, :order => "created_on DESC")
end
def new
@@ -58,7 +58,7 @@ class DocumentsController < ApplicationController
if @document.save
render_attachment_warning_if_needed(@document)
flash[:notice] = l(:notice_successful_create)
redirect_to project_documents_path(@project)
redirect_to :action => 'index', :project_id => @project
else
render :action => 'new'
end
@@ -71,7 +71,7 @@ class DocumentsController < ApplicationController
@document.safe_attributes = params[:document]
if request.put? and @document.save
flash[:notice] = l(:notice_successful_update)
redirect_to document_path(@document)
redirect_to :action => 'show', :id => @document
else
render :action => 'edit'
end
@@ -79,16 +79,14 @@ class DocumentsController < ApplicationController
def destroy
@document.destroy if request.delete?
redirect_to project_documents_path(@project)
redirect_to :controller => 'documents', :action => 'index', :project_id => @project
end
def add_attachment
attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document)
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
Mailer.attachments_added(attachments[:files]).deliver
end
redirect_to document_path(@document)
Mailer.deliver_attachments_added(attachments[:files]) if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
redirect_to :action => 'show', :id => @document
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,26 +18,13 @@
class EnumerationsController < ApplicationController
layout 'admin'
before_filter :require_admin, :except => :index
before_filter :require_admin_or_api_request, :only => :index
before_filter :require_admin
before_filter :build_new_enumeration, :only => [:new, :create]
before_filter :find_enumeration, :only => [:edit, :update, :destroy]
accept_api_auth :index
helper :custom_fields
def index
respond_to do |format|
format.html
format.api {
@klass = Enumeration.get_subclass(params[:type])
if @klass
@enumerations = @klass.shared.sorted.all
else
render_404
end
}
end
end
def new
@@ -46,7 +33,7 @@ class EnumerationsController < ApplicationController
def create
if request.post? && @enumeration.save
flash[:notice] = l(:notice_successful_create)
redirect_to enumerations_path
redirect_to :action => 'index', :type => @enumeration.type
else
render :action => 'new'
end
@@ -58,7 +45,7 @@ class EnumerationsController < ApplicationController
def update
if request.put? && @enumeration.update_attributes(params[:enumeration])
flash[:notice] = l(:notice_successful_update)
redirect_to enumerations_path
redirect_to :action => 'index', :type => @enumeration.type
else
render :action => 'edit'
end
@@ -68,14 +55,16 @@ class EnumerationsController < ApplicationController
if !@enumeration.in_use?
# No associated objects
@enumeration.destroy
redirect_to enumerations_path
return
elsif params[:reassign_to_id].present? && (reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id].to_i))
@enumeration.destroy(reassign_to)
redirect_to enumerations_path
redirect_to :action => 'index'
return
elsif params[:reassign_to_id]
if reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id])
@enumeration.destroy(reassign_to)
redirect_to :action => 'index'
return
end
end
@enumerations = @enumeration.class.system.all - [@enumeration]
@enumerations = @enumeration.class.all - [@enumeration]
end
private

View File

@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2013 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
menu_item :files
@@ -31,8 +14,8 @@ class FilesController < ApplicationController
'size' => "#{Attachment.table_name}.filesize",
'downloads' => "#{Attachment.table_name}.downloads"
@containers = [ Project.includes(:attachments).reorder(sort_clause).find(@project.id)]
@containers += @project.versions.includes(:attachments).reorder(sort_clause).all.sort.reverse
@containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
@containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
render :layout => !request.xhr?
end
@@ -46,7 +29,7 @@ class FilesController < ApplicationController
render_attachment_warning_if_needed(container)
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
redirect_to project_files_path(@project)
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -19,34 +19,51 @@ class GroupsController < ApplicationController
layout '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
# GET /groups
# GET /groups.xml
def index
@groups = Group.sorted.all
@groups = Group.find(:all, :order => 'lastname')
respond_to do |format|
format.html
format.api
format.html # index.html.erb
format.xml { render :xml => @groups }
end
end
# GET /groups/1
# GET /groups/1.xml
def show
@group = Group.find(params[:id])
respond_to do |format|
format.html
format.api
format.html # show.html.erb
format.xml { render :xml => @group }
end
end
# GET /groups/new
# GET /groups/new.xml
def new
@group = Group.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @group }
end
end
# GET /groups/1/edit
def edit
@group = Group.find(params[:id], :include => :projects)
end
# POST /groups
# POST /groups.xml
def create
@group = Group.new
@group.safe_attributes = params[:group]
@group = Group.new(params[:group])
respond_to do |format|
if @group.save
@@ -54,88 +71,102 @@ class GroupsController < ApplicationController
flash[:notice] = l(:notice_successful_create)
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
format.html { render :action => "new" }
format.api { render_validation_errors(@group) }
format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
end
end
end
def edit
end
# PUT /groups/1
# PUT /groups/1.xml
def update
@group.safe_attributes = params[:group]
@group = Group.find(params[:id])
respond_to do |format|
if @group.save
if @group.update_attributes(params[:group])
flash[:notice] = l(:notice_successful_update)
format.html { redirect_to(groups_path) }
format.api { render_api_ok }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.api { render_validation_errors(@group) }
format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /groups/1
# DELETE /groups/1.xml
def destroy
@group = Group.find(params[:id])
@group.destroy
respond_to do |format|
format.html { redirect_to(groups_path) }
format.api { render_api_ok }
format.html { redirect_to(groups_url) }
format.xml { head :ok }
end
end
def add_users
@users = User.find_all_by_id(params[:user_id] || params[:user_ids])
@group.users << @users if request.post?
@group = Group.find(params[:id])
users = User.find_all_by_id(params[:user_ids])
@group.users << users if request.post?
respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'users') }
format.js
format.api { render_api_ok }
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js {
render(:update) {|page|
page.replace_html "tab-content-users", :partial => 'groups/users'
users.each {|user| page.visual_effect(:highlight, "user-#{user.id}") }
}
}
end
end
def remove_user
@group = Group.find(params[:id])
@group.users.delete(User.find(params[:user_id])) if request.delete?
respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'users') }
format.js
format.api { render_api_ok }
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js { render(:update) {|page| page.replace_html "tab-content-users", :partial => 'groups/users'} }
end
end
def autocomplete_for_user
respond_to do |format|
format.js
end
@group = Group.find(params[:id])
@users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100)
render :layout => false
end
def edit_membership
@group = Group.find(params[:id])
@membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
@membership.save if request.post?
respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'memberships') }
format.js
if @membership.valid?
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
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
def destroy_membership
@group = Group.find(params[:id])
Member.find(params[:membership_id]).destroy if request.post?
respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'memberships') }
format.js
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'groups/memberships'} }
end
end
private
def find_group
@group = Group.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -23,17 +23,17 @@ class IssueCategoriesController < ApplicationController
before_filter :find_project_by_project_id, :only => [:index, :new, :create]
before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
def index
respond_to do |format|
format.html { redirect_to_settings_in_projects }
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project }
format.api { @categories = @project.issue_categories.all }
end
end
def show
respond_to do |format|
format.html { redirect_to_settings_in_projects }
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project }
format.api
end
end
@@ -41,11 +41,6 @@ class IssueCategoriesController < ApplicationController
def new
@category = @project.issue_categories.build
@category.safe_attributes = params[:issue_category]
respond_to do |format|
format.html
format.js
end
end
def create
@@ -55,15 +50,22 @@ class IssueCategoriesController < ApplicationController
respond_to do |format|
format.html do
flash[:notice] = l(:notice_successful_create)
redirect_to_settings_in_projects
redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
end
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', content_tag('option') + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
}
end
format.js
format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) }
end
else
respond_to do |format|
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) }
end
end
@@ -78,9 +80,9 @@ class IssueCategoriesController < ApplicationController
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to_settings_in_projects
redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
}
format.api { render_api_ok }
format.api { head :ok }
end
else
respond_to do |format|
@@ -92,27 +94,22 @@ class IssueCategoriesController < ApplicationController
def destroy
@issue_count = @category.issues.size
if @issue_count == 0 || params[:todo] || api_request?
if @issue_count == 0 || params[:todo] || api_request?
reassign_to = nil
if params[:reassign_to_id] && (params[:todo] == 'reassign' || params[:todo].blank?)
reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id])
end
@category.destroy(reassign_to)
respond_to do |format|
format.html { redirect_to_settings_in_projects }
format.api { render_api_ok }
format.html { redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' }
format.api { head :ok }
end
return
end
@categories = @project.issue_categories - [@category]
end
private
def redirect_to_settings_in_projects
redirect_to settings_project_path(@project, :tab => 'categories')
end
private
# Wrap ApplicationController's find_model_object method to set
# @category instead of just @issue_category
def find_model_object

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -48,10 +48,17 @@ class IssueRelationsController < ApplicationController
saved = @relation.save
respond_to do |format|
format.html { redirect_to issue_path(@issue) }
format.js {
@relations = @issue.reload.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
}
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js do
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
render :update do |page|
page.replace_html "relations", :partial => 'issues/relations'
if @relation.errors.empty?
page << "$('relation_delay').value = ''"
page << "$('relation_issue_to_id').value = ''"
end
end
end
format.api {
if saved
render :action => 'show', :status => :created, :location => relation_url(@relation)
@@ -67,9 +74,9 @@ class IssueRelationsController < ApplicationController
@relation.destroy
respond_to do |format|
format.html { redirect_to issue_path(@relation.issue_from) }
format.js
format.api { render_api_ok }
format.html { redirect_to issue_path } # TODO : does this really work since @issue is always nil? What is it useful to?
format.js { render(:update) {|page| page.remove "relation-#{@relation.id}"} }
format.api { head :ok }
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -25,7 +25,7 @@ class IssueStatusesController < ApplicationController
def index
respond_to do |format|
format.html {
@issue_status_pages, @issue_statuses = paginate IssueStatus.sorted, :per_page => 25
@issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
render :action => "index", :layout => false if request.xhr?
}
format.api {
@@ -42,7 +42,7 @@ class IssueStatusesController < ApplicationController
@issue_status = IssueStatus.new(params[:issue_status])
if request.post? && @issue_status.save
flash[:notice] = l(:notice_successful_create)
redirect_to issue_statuses_path
redirect_to :action => 'index'
else
render :action => 'new'
end
@@ -56,7 +56,7 @@ class IssueStatusesController < ApplicationController
@issue_status = IssueStatus.find(params[:id])
if request.put? && @issue_status.update_attributes(params[:issue_status])
flash[:notice] = l(:notice_successful_update)
redirect_to issue_statuses_path
redirect_to :action => 'index'
else
render :action => 'edit'
end
@@ -64,11 +64,11 @@ class IssueStatusesController < ApplicationController
def destroy
IssueStatus.find(params[:id]).destroy
redirect_to issue_statuses_path
redirect_to :action => 'index'
rescue
flash[:error] = l(:error_unable_delete_issue_status)
redirect_to issue_statuses_path
end
redirect_to :action => 'index'
end
def update_issue_done_ratio
if request.post? && IssueStatus.update_issue_done_ratios
@@ -76,6 +76,6 @@ class IssueStatusesController < ApplicationController
else
flash[:error] = l(:error_issue_done_ratios_not_updated)
end
redirect_to issue_statuses_path
redirect_to :action => 'index'
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -21,11 +21,11 @@ class IssuesController < ApplicationController
before_filter :find_issue, :only => [:show, :edit, :update]
before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
before_filter :find_project, :only => [:new, :create, :update_form]
before_filter :find_project, :only => [:new, :create]
before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => [:index]
before_filter :check_for_default_issue_status, :only => [:new, :create]
before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form]
before_filter :build_new_issue_from_params, :only => [:new, :create]
accept_rss_auth :index, :show
accept_api_auth :index, :show, :create, :update, :destroy
@@ -50,13 +50,13 @@ class IssuesController < ApplicationController
include SortHelper
include IssuesHelper
helper :timelog
helper :gantt
include Redmine::Export::PDF
def index
retrieve_query
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns)
@query.sort_criteria = sort_criteria.to_a
if @query.valid?
case params[:format]
@@ -71,8 +71,8 @@ class IssuesController < ApplicationController
end
@issue_count = @query.issue_count
@issue_pages = Paginator.new @issue_count, @limit, params['page']
@offset ||= @issue_pages.offset
@issue_pages = Paginator.new self, @issue_count, @limit, params['page']
@offset ||= @issue_pages.current.offset
@issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
:order => sort_clause,
:offset => @offset,
@@ -82,11 +82,11 @@ class IssuesController < ApplicationController
respond_to do |format|
format.html { render :template => 'issues/index', :layout => !request.xhr? }
format.api {
Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
Issue.load_relations(@issues) if include_in_api_response?('relations')
}
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') }
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'issues.pdf') }
format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
end
else
respond_to do |format|
@@ -100,9 +100,8 @@ class IssuesController < ApplicationController
end
def show
@journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
@journals.each_with_index {|j,i| j.indice = i+1}
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
@journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.all
@@ -120,10 +119,7 @@ class IssuesController < ApplicationController
}
format.api
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
format.pdf {
pdf = issue_to_pdf(@issue, :journals => @journals)
send_data(pdf, :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") }
end
end
@@ -132,6 +128,17 @@ class IssuesController < ApplicationController
def new
respond_to do |format|
format.html { render :action => 'new', :layout => !request.xhr? }
format.js {
render(:update) { |page|
if params[:project_change]
page.replace_html 'all_attributes', :partial => 'form'
else
page.replace_html 'attributes', :partial => 'attributes'
end
m = User.current.allowed_to?(:log_time, @issue.project) ? 'show' : 'hide'
page << "if ($('log_time')) {Element.#{m}('log_time');}"
}
}
end
end
@@ -143,13 +150,9 @@ class IssuesController < ApplicationController
respond_to do |format|
format.html {
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))
if params[:continue]
attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
redirect_to new_project_issue_path(@issue.project, :issue => attrs)
else
redirect_to issue_path(@issue)
end
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?} } :
{ :action => 'show', :id => @issue })
}
format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
end
@@ -180,8 +183,12 @@ class IssuesController < ApplicationController
rescue ActiveRecord::StaleObjectError
@conflict = true
if params[:last_journal_id]
@conflict_journals = @issue.journals_after(params[:last_journal_id]).all
@conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
if params[:last_journal_id].present?
last_journal_id = params[:last_journal_id].to_i
@conflict_journals = @issue.journals.all(:conditions => ["#{Journal.table_name}.id > ?", last_journal_id])
else
@conflict_journals = @issue.journals.all
end
end
end
@@ -190,8 +197,8 @@ class IssuesController < ApplicationController
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
respond_to do |format|
format.html { redirect_back_or_default issue_path(@issue) }
format.api { render_api_ok }
format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
format.api { head :ok }
end
else
respond_to do |format|
@@ -201,11 +208,6 @@ class IssuesController < ApplicationController
end
end
# Updates the issue form when changing the project, status or tracker
# on issue creation/update
def update_form
end
# Bulk edit/copy a set of issues
def bulk_edit
@issues.sort!
@@ -235,7 +237,6 @@ class IssuesController < ApplicationController
@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(:&)
@@ -249,20 +250,10 @@ class IssuesController < ApplicationController
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|
issue.reload
if @copy
issue = issue.copy({},
:attachments => params[:copy_attachments].present?,
:subtasks => params[:copy_subtasks].present?
)
issue = issue.copy({}, :attachments => params[:copy_attachments].present?)
end
journal = issue.init_journal(User.current, params[:notes])
issue.safe_attributes = attributes
@@ -278,12 +269,12 @@ class IssuesController < ApplicationController
if params[:follow]
if @issues.size == 1 && moved_issues.size == 1
redirect_to issue_path(moved_issues.first)
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
elsif moved_issues.map(&:project).uniq.size == 1
redirect_to project_issues_path(moved_issues.map(&:project).first)
redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first
end
else
redirect_back_or_default _project_issues_path(@project)
redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
end
end
@@ -316,12 +307,24 @@ class IssuesController < ApplicationController
end
end
respond_to do |format|
format.html { redirect_back_or_default _project_issues_path(@project) }
format.api { render_api_ok }
format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
format.api { head :ok }
end
end
private
private
def find_issue
# Issue.visible.find(...) can not be used to redirect user to the login form
# if the issue actually exists but requires authentication
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
unless @issue.visible?
deny_access
return
end
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
def find_project
project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
@@ -356,7 +359,8 @@ class IssuesController < ApplicationController
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@time_entry.attributes = params[:time_entry]
@issue.init_journal(User.current)
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
@issue.init_journal(User.current, @notes)
issue_attributes = params[:issue]
if issue_attributes && params[:conflict_resolution]
@@ -365,7 +369,7 @@ class IssuesController < ApplicationController
issue_attributes = issue_attributes.dup
issue_attributes.delete(:lock_version)
when 'add_notes'
issue_attributes = issue_attributes.slice(:notes)
issue_attributes = {}
when 'cancel'
redirect_to issue_path(@issue)
return false
@@ -386,8 +390,7 @@ class IssuesController < ApplicationController
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)
@issue.copy_from(@copy_from, :attachments => @copy_attachments)
rescue ActiveRecord::RecordNotFound
render_404
return
@@ -399,7 +402,7 @@ class IssuesController < ApplicationController
end
@issue.project = @project
@issue.author ||= User.current
@issue.author = User.current
# Tracker must be set before custom field values
@issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
if @issue.tracker.nil?

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -57,20 +57,26 @@ class JournalsController < ApplicationController
end
def new
@journal = Journal.visible.find(params[:journal_id]) if params[:journal_id]
if @journal
user = @journal.user
text = @journal.notes
journal = Journal.find(params[:journal_id]) if params[:journal_id]
if journal
user = journal.user
text = journal.notes
else
user = @issue.author
text = @issue.description
end
# Replaces pre blocks with [...]
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
@content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
@content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
rescue ActiveRecord::RecordNotFound
render_404
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\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
def edit
@@ -80,7 +86,7 @@ class JournalsController < ApplicationController
@journal.destroy if @journal.details.empty? && @journal.notes.blank?
call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
respond_to do |format|
format.html { redirect_to issue_path(@journal.journalized) }
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id }
format.js { render :action => 'update' }
end
else
@@ -97,9 +103,17 @@ class JournalsController < ApplicationController
private
def find_journal
@journal = Journal.visible.find(params[:id])
@journal = Journal.find(params[:id])
@project = @journal.journalized.project
rescue ActiveRecord::RecordNotFound
render_404
end
# TODO: duplicated in IssuesController
def find_issue
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -26,8 +26,8 @@ class MembersController < ApplicationController
def index
@offset, @limit = api_offset_and_limit
@member_count = @project.member_principals.count
@member_pages = Paginator.new @member_count, @limit, params['page']
@offset ||= @member_pages.offset
@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,
@@ -63,16 +63,31 @@ class MembersController < ApplicationController
end
respond_to do |format|
format.html { redirect_to_settings_in_projects }
format.js { @members = members }
format.api {
@member = members.first
if @member.valid?
if members.present? && members.all? {|m| m.valid? }
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js {
render(:update) {|page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
page << 'hideOnLoad()'
members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
}
}
format.api {
@member = members.first
render :action => 'show', :status => :created, :location => membership_url(@member)
else
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(', ')))
}
}
format.api { render_validation_errors(members.first) }
end
end
end
@@ -82,11 +97,17 @@ class MembersController < ApplicationController
end
saved = @member.save
respond_to do |format|
format.html { redirect_to_settings_in_projects }
format.js
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js {
render(:update) {|page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
page << 'hideOnLoad()'
page.visual_effect(:highlight, "member-#{@member.id}")
}
}
format.api {
if saved
render_api_ok
head :ok
else
render_validation_errors(@member)
end
@@ -99,11 +120,15 @@ class MembersController < ApplicationController
@member.destroy
end
respond_to do |format|
format.html { redirect_to_settings_in_projects }
format.js
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js { render(:update) {|page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
page << 'hideOnLoad()'
}
}
format.api {
if @member.destroyed?
render_api_ok
head :ok
else
head :unprocessable_entity
end
@@ -112,14 +137,8 @@ class MembersController < ApplicationController
end
def autocomplete
respond_to do |format|
format.js
end
@principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100)
render :layout => false
end
private
def redirect_to_settings_in_projects
redirect_to settings_project_path(@project, :tab => 'members')
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -19,11 +19,9 @@ class MessagesController < ApplicationController
menu_item :boards
default_search_scope :messages
before_filter :find_board, :only => [:new, :preview]
before_filter :find_attachments, :only => [:preview]
before_filter :find_message, :except => [:new, :preview]
before_filter :authorize, :except => [:preview, :edit, :destroy]
helper :boards
helper :watchers
helper :attachments
include AttachmentsHelper
@@ -40,13 +38,11 @@ class MessagesController < ApplicationController
end
@reply_count = @topic.children.count
@reply_pages = Paginator.new @reply_count, REPLIES_PER_PAGE, page
@replies = @topic.children.
includes(:author, :attachments, {:board => :project}).
reorder("#{Message.table_name}.created_on ASC").
limit(@reply_pages.per_page).
offset(@reply_pages.offset).
all
@reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page
@replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}],
:order => "#{Message.table_name}.created_on ASC",
:limit => @reply_pages.items_per_page,
:offset => @reply_pages.current.offset)
@reply = Message.new(:subject => "RE: #{@message.subject}")
render :action => "show", :layout => false if request.xhr?
@@ -63,7 +59,7 @@ class MessagesController < ApplicationController
if @message.save
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
render_attachment_warning_if_needed(@message)
redirect_to board_message_path(@board, @message)
redirect_to :action => 'show', :id => @message
end
end
end
@@ -80,7 +76,7 @@ class MessagesController < ApplicationController
attachments = Attachment.attach_files(@reply, params[:attachments])
render_attachment_warning_if_needed(@reply)
end
redirect_to board_message_path(@board, @topic, :r => @reply)
redirect_to :action => 'show', :id => @topic, :r => @reply
end
# Edit a message
@@ -92,40 +88,46 @@ class MessagesController < ApplicationController
render_attachment_warning_if_needed(@message)
flash[:notice] = l(:notice_successful_update)
@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
# Delete a messages
def destroy
(render_403; return false) unless @message.destroyable_by?(User.current)
r = @message.to_param
@message.destroy
if @message.parent
redirect_to board_message_path(@board, @message.parent, :r => r)
else
redirect_to project_board_path(@project, @board)
end
redirect_to @message.parent.nil? ?
{ :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
{ :action => 'show', :id => @message.parent, :r => @message }
end
def quote
@subject = @message.subject
@subject = "RE: #{@subject}" unless @subject.starts_with?('RE:')
@content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> "
@content << @message.content.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
user = @message.author
text = @message.content
subject = @message.subject.gsub('"', '\"')
subject = "RE: #{subject}" unless subject.starts_with?('RE:')
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
def preview
message = @board.messages.find_by_id(params[:id])
@attachements = message.attachments if message
@text = (params[:message] || params[:reply])[:content]
@previewed = message
render :partial => 'common/preview'
end
private
def find_message
return unless find_board
find_board
@message = @board.messages.find(params[:id], :include => :parent)
@topic = @message.root
rescue ActiveRecord::RecordNotFound
@@ -137,6 +139,5 @@ private
@project = @board.project
rescue ActiveRecord::RecordNotFound
render_404
nil
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -53,12 +53,13 @@ class MyController < ApplicationController
if request.post?
@user.safe_attributes = params[:user]
@user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
if @user.save
@user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
set_language_if_valid @user.language
flash[:notice] = l(:notice_account_updated)
redirect_to my_account_path
redirect_to :action => 'account'
return
end
end
@@ -68,7 +69,7 @@ class MyController < ApplicationController
def destroy
@user = User.current
unless @user.own_account_deletable?
redirect_to my_account_path
redirect_to :action => 'account'
return
end
@@ -87,7 +88,7 @@ class MyController < ApplicationController
@user = User.current
unless @user.change_password_allowed?
flash[:error] = l(:notice_can_t_change_password)
redirect_to my_account_path
redirect_to :action => 'account'
return
end
if request.post?
@@ -95,7 +96,7 @@ class MyController < ApplicationController
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
if @user.save
flash[:notice] = l(:notice_account_password_updated)
redirect_to my_account_path
redirect_to :action => 'account'
end
else
flash[:error] = l(:notice_account_wrong_password)
@@ -113,7 +114,7 @@ class MyController < ApplicationController
User.current.rss_key
flash[:notice] = l(:notice_feeds_access_key_reseted)
end
redirect_to my_account_path
redirect_to :action => 'account'
end
# Create a new API key
@@ -126,7 +127,7 @@ class MyController < ApplicationController
User.current.api_key
flash[:notice] = l(:notice_api_access_key_reseted)
end
redirect_to my_account_path
redirect_to :action => 'account'
end
# User's page layout configuration
@@ -134,11 +135,7 @@ class MyController < ApplicationController
@user = User.current
@blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
@block_options = []
BLOCKS.each do |k, v|
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
BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
end
# Add a block to user's page
@@ -146,17 +143,16 @@ class MyController < ApplicationController
# params[:block] : id of the block to add
def add_block
block = params[:block].to_s.underscore
if block.present? && BLOCKS.key?(block)
@user = User.current
layout = @user.pref[:my_page_layout] || {}
# remove if already present in a group
%w(top left right).each {|f| (layout[f] ||= []).delete block }
# add it on top
layout['top'].unshift block
@user.pref[:my_page_layout] = layout
@user.pref.save
end
redirect_to my_page_layout_path
(render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
@user = User.current
layout = @user.pref[:my_page_layout] || {}
# remove if already present in a group
%w(top left right).each {|f| (layout[f] ||= []).delete block }
# add it on top
layout['top'].unshift block
@user.pref[:my_page_layout] = layout
@user.pref.save
render :partial => "block", :locals => {:user => @user, :block_name => block}
end
# Remove a block to user's page
@@ -169,7 +165,7 @@ class MyController < ApplicationController
%w(top left right).each {|f| (layout[f] ||= []).delete block }
@user.pref[:my_page_layout] = layout
@user.pref.save
redirect_to my_page_layout_path
render :nothing => true
end
# Change blocks order on user's page
@@ -179,8 +175,7 @@ class MyController < ApplicationController
group = params[:group]
@user = User.current
if group.is_a?(String)
group_items = (params["blocks"] || []).collect(&:underscore)
group_items.each {|s| s.sub!(/^block_/, '')}
group_items = (params["list-#{group}"] || []).collect(&:underscore)
if group_items and group_items.is_a? Array
layout = @user.pref[:my_page_layout] || {}
# remove group blocks if they are presents in other groups

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -40,8 +40,8 @@ class NewsController < ApplicationController
scope = @project ? @project.news.visible : News.visible
@news_count = scope.count
@news_pages = Paginator.new @news_count, @limit, params['page']
@offset ||= @news_pages.offset
@news_pages = Paginator.new self, @news_count, @limit, params['page']
@offset ||= @news_pages.current.offset
@newss = scope.all(:include => [:author, :project],
:order => "#{News.table_name}.created_on DESC",
:offset => @offset,
@@ -73,7 +73,7 @@ class NewsController < ApplicationController
if @news.save
render_attachment_warning_if_needed(@news)
flash[:notice] = l(:notice_successful_create)
redirect_to project_news_index_path(@project)
redirect_to :controller => 'news', :action => 'index', :project_id => @project
else
render :action => 'new'
end
@@ -88,7 +88,7 @@ class NewsController < ApplicationController
if @news.save
render_attachment_warning_if_needed(@news)
flash[:notice] = l(:notice_successful_update)
redirect_to news_path(@news)
redirect_to :action => 'show', :id => @news
else
render :action => 'edit'
end
@@ -96,7 +96,7 @@ class NewsController < ApplicationController
def destroy
@news.destroy
redirect_to project_news_index_path(@project)
redirect_to :action => 'index', :project_id => @project
end
private

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -16,17 +16,17 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class PreviewsController < ApplicationController
before_filter :find_project, :find_attachments
before_filter :find_project
def issue
@issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
if @issue
@attachements = @issue.attachments
@description = params[:issue] && params[:issue][:description]
if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n")
@description = nil
end
# params[:notes] is useful for preview of notes in issue history
@notes = params[:notes] || (params[:issue] ? params[:issue][:notes] : nil)
@notes = params[:notes]
else
@description = (params[:issue] ? params[:issue][:description] : nil)
end
@@ -34,9 +34,6 @@ class PreviewsController < ApplicationController
end
def news
if params[:id].present? && news = News.visible.find_by_id(params[:id])
@previewed = news
end
@text = (params[:news] ? params[:news][:description] : nil)
render :partial => 'common/preview'
end

View File

@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2013 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
before_filter :find_project_by_project_id
before_filter :authorize
@@ -29,7 +12,7 @@ class ProjectEnumerationsController < ApplicationController
flash[:notice] = l(:notice_successful_update)
end
redirect_to settings_project_path(@project, :tab => 'activities')
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end
def destroy
@@ -37,6 +20,7 @@ class ProjectEnumerationsController < ApplicationController
time_entry_activity.destroy(time_entry_activity.parent)
end
flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project, :tab => 'activities')
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -43,40 +43,36 @@ class ProjectsController < ApplicationController
helper :repositories
include RepositoriesHelper
include ProjectsHelper
helper :members
# Lists visible projects
def index
respond_to do |format|
format.html {
scope = Project
unless params[:closed]
scope = scope.active
end
@projects = scope.visible.order('lft').all
@projects = Project.visible.find(:all, :order => 'lft')
}
format.api {
@offset, @limit = api_offset_and_limit
@project_count = Project.visible.count
@projects = Project.visible.offset(@offset).limit(@limit).order('lft').all
@projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => 'lft')
}
format.atom {
projects = Project.visible.order('created_on DESC').limit(Setting.feeds_limit.to_i).all
projects = Project.visible.find(:all, :order => 'created_on DESC',
:limit => Setting.feeds_limit.to_i)
render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
}
end
end
def new
@issue_custom_fields = IssueCustomField.sorted.all
@trackers = Tracker.sorted.all
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@project = Project.new
@project.safe_attributes = params[:project]
end
def create
@issue_custom_fields = IssueCustomField.sorted.all
@trackers = Tracker.sorted.all
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@project = Project.new
@project.safe_attributes = params[:project]
@@ -91,12 +87,10 @@ class ProjectsController < ApplicationController
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_create)
if params[:continue]
attrs = {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}
redirect_to new_project_path(attrs)
else
redirect_to settings_project_path(@project)
end
redirect_to(params[:continue] ?
{:controller => 'projects', :action => 'new', :project => {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}} :
{:controller => 'projects', :action => 'settings', :id => @project}
)
}
format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
end
@@ -106,15 +100,23 @@ class ProjectsController < ApplicationController
format.api { render_validation_errors(@project) }
end
end
end
def copy
@issue_custom_fields = IssueCustomField.sorted.all
@trackers = Tracker.sorted.all
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@root_projects = Project.find(:all,
:conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
:order => 'name')
@source_project = Project.find(params[:id])
if request.get?
@project = Project.copy_from(@source_project)
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
if @project
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
else
redirect_to :controller => 'admin', :action => 'projects'
end
else
Mailer.with_deliveries(params[:notifications] == '1') do
@project = Project.new
@@ -122,37 +124,40 @@ class ProjectsController < ApplicationController
if validate_parent_id && @project.copy(@source_project, :only => params[:only])
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_create)
redirect_to settings_project_path(@project)
redirect_to :controller => 'projects', :action => 'settings', :id => @project
elsif !@project.new_record?
# Project was created
# But some objects were not copied due to validation failures
# (eg. issues from disabled trackers)
# TODO: inform about that
redirect_to settings_project_path(@project)
redirect_to :controller => 'projects', :action => 'settings', :id => @project
end
end
end
rescue ActiveRecord::RecordNotFound
# source_project not found
render_404
redirect_to :controller => 'admin', :action => 'projects'
end
# Show @project
def show
# try to redirect to the requested menu item
if params[:jump] && redirect_to_project_menu_item(@project, params[:jump])
return
if params[:jump]
# try to redirect to the requested menu item
redirect_to_project_menu_item(@project, params[:jump]) && return
end
@users_by_role = @project.users_by_role
@subprojects = @project.children.visible.all
@news = @project.news.limit(5).includes(:author, :project).reorder("#{News.table_name}.created_on DESC").all
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@trackers = @project.rolled_up_trackers
cond = @project.project_condition(Setting.display_subprojects_issues?)
@open_issues_by_tracker = Issue.visible.open.where(cond).count(:group => :tracker)
@total_issues_by_tracker = Issue.visible.where(cond).count(:group => :tracker)
@open_issues_by_tracker = Issue.visible.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)
@total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
@@ -167,10 +172,10 @@ class ProjectsController < ApplicationController
end
def settings
@issue_custom_fields = IssueCustomField.sorted.all
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@issue_category ||= IssueCategory.new
@member ||= @project.members.new
@trackers = Tracker.sorted.all
@trackers = Tracker.all
@wiki ||= @project.wiki
end
@@ -184,9 +189,9 @@ class ProjectsController < ApplicationController
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project)
redirect_to :action => 'settings', :id => @project
}
format.api { render_api_ok }
format.api { head :ok }
end
else
respond_to do |format|
@@ -202,7 +207,7 @@ class ProjectsController < ApplicationController
def modules
@project.enabled_module_names = params[:enabled_module_names]
flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project, :tab => 'modules')
redirect_to :action => 'settings', :id => @project, :tab => 'modules'
end
def archive
@@ -211,22 +216,12 @@ class ProjectsController < ApplicationController
flash[:error] = l(:error_can_not_archive_project)
end
end
redirect_to admin_projects_path(:status => params[:status])
redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end
def unarchive
@project.unarchive if request.post? && !@project.active?
redirect_to admin_projects_path(:status => params[:status])
end
def close
@project.close
redirect_to project_path(@project)
end
def reopen
@project.reopen
redirect_to project_path(@project)
redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end
# Delete @project
@@ -235,8 +230,8 @@ class ProjectsController < ApplicationController
if api_request? || params[:confirm]
@project_to_destroy.destroy
respond_to do |format|
format.html { redirect_to admin_projects_path }
format.api { render_api_ok }
format.html { redirect_to :controller => 'admin', :action => 'projects' }
format.api { head :ok }
end
end
# hide project in layout

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -32,34 +32,35 @@ class QueriesController < ApplicationController
@limit = per_page_option
end
@query_count = IssueQuery.visible.count
@query_pages = Paginator.new @query_count, @limit, params['page']
@queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
@query_count = Query.visible.count
@query_pages = Paginator.new self, @query_count, @limit, params['page']
@queries = Query.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
respond_to do |format|
format.html { render :nothing => true }
format.api
end
end
def new
@query = IssueQuery.new
@query = Query.new
@query.user = User.current
@query.project = @project
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
build_query_from_params
end
def create
@query = IssueQuery.new(params[:query])
@query = Query.new(params[:query])
@query.user = User.current
@query.project = params[:query_is_for_all] ? nil : @project
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
build_query_from_params
@query.column_names = nil if params[:default_columns]
if @query.save
flash[:notice] = l(:notice_successful_create)
redirect_to _project_issues_path(@project, :query_id => @query)
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
else
render :action => 'new', :layout => !request.xhr?
end
@@ -72,12 +73,12 @@ class QueriesController < ApplicationController
@query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all]
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
build_query_from_params
@query.column_names = nil if params[:default_columns]
if @query.save
flash[:notice] = l(:notice_successful_update)
redirect_to _project_issues_path(@project, :query_id => @query)
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
else
render :action => 'edit'
end
@@ -85,12 +86,12 @@ class QueriesController < ApplicationController
def destroy
@query.destroy
redirect_to _project_issues_path(@project, :set_filter => 1)
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
end
private
def find_query
@query = IssueQuery.find(params[:id])
@query = Query.find(params[:id])
@project = @query.project
render_403 unless @query.editable_by?(User.current)
rescue ActiveRecord::RecordNotFound

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -22,7 +22,7 @@ class ReportsController < ApplicationController
def issue_report
@trackers = @project.trackers
@versions = @project.shared_versions.sort
@priorities = IssuePriority.all.reverse
@priorities = IssuePriority.all
@categories = @project.issue_categories
@assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort
@authors = @project.users.sort
@@ -53,7 +53,7 @@ class ReportsController < ApplicationController
@report_title = l(:field_version)
when "priority"
@field = "priority_id"
@rows = IssuePriority.all.reverse
@rows = IssuePriority.all
@data = Issue.by_priority(@project)
@report_title = l(:field_priority)
when "category"
@@ -90,6 +90,6 @@ class ReportsController < ApplicationController
private
def find_issue_statuses
@statuses = IssueStatus.sorted.all
@statuses = IssueStatus.find(:all, :order => 'position')
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# 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
@@ -18,7 +18,6 @@
require 'SVG/Graph/Bar'
require 'SVG/Graph/BarHorizontal'
require 'digest/sha1'
require 'redmine/scm/adapters/abstract_adapter'
class ChangesetNotFound < Exception; end
class InvalidRevisionParam < Exception; end
@@ -42,15 +41,11 @@ class RepositoriesController < ApplicationController
@repository = Repository.factory(scm)
@repository.is_default = @project.repository.nil?
@repository.project = @project
render :layout => !request.xhr?
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 = Repository.factory(params[:repository_scm], params[:repository])
@repository.project = @project
if request.post? && @repository.save
redirect_to settings_project_path(@project, :tab => 'repositories')
@@ -63,11 +58,7 @@ class RepositoriesController < ApplicationController
end
def update
attrs = pickup_extra_info
@repository.safe_attributes = attrs[:attrs]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end
@repository.attributes = params[:repository]
@repository.project = @project
if request.put? && @repository.save
redirect_to settings_project_path(@project, :tab => 'repositories')
@@ -76,20 +67,6 @@ class RepositoriesController < ApplicationController
end
end
def pickup_extra_info
p = {}
p_extra = {}
params[:repository].each do |k, v|
if k =~ /^extra_/
p_extra[k] = v
else
p[k] = v
end
end
{:attrs => p, :attrs_extra => p_extra}
end
private :pickup_extra_info
def committers
@committers = @repository.committers
@users = @project.users
@@ -138,14 +115,13 @@ class RepositoriesController < ApplicationController
def revisions
@changeset_count = @repository.changesets.count
@changeset_pages = Paginator.new @changeset_count,
@changeset_pages = Paginator.new self, @changeset_count,
per_page_option,
params['page']
@changesets = @repository.changesets.
limit(@changeset_pages.per_page).
offset(@changeset_pages.offset).
includes(:user, :repository, :parents).
all
@changesets = @repository.changesets.find(:all,
:limit => @changeset_pages.items_per_page,
:offset => @changeset_pages.current.offset,
:include => [:user, :repository, :parents])
respond_to do |format|
format.html { render :layout => false if request.xhr? }
@@ -153,15 +129,7 @@ class RepositoriesController < ApplicationController
end
end
def raw
entry_and_raw(true)
end
def entry
entry_and_raw(false)
end
def entry_and_raw(is_raw)
@entry = @repository.entry(@path, @rev)
(show_error_not_found; return) unless @entry
@@ -170,14 +138,13 @@ class RepositoriesController < ApplicationController
@content = @repository.cat(@path, @rev)
(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) ||
! is_entry_text_data?(@content, @path)
# Force the download
send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
send_type = Redmine::MimeType.of(@path)
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
else
# Prevent empty lines when displaying a file with Windows style eol
@@ -187,7 +154,6 @@ class RepositoriesController < ApplicationController
@changeset = @repository.find_changeset_by_name(@rev)
end
end
private :entry_and_raw
def is_entry_text_data?(ent, path)
# UTF-16 contains "\x00".
@@ -236,6 +202,22 @@ class RepositoriesController < ApplicationController
if @issue
@changeset.issues << @issue
respond_to do |format|
format.js {
render :update do |page|
page.replace_html "related-issues", :partial => "related_issues"
page.visual_effect :highlight, "related-issue-#{@issue.id}"
end
}
end
else
respond_to do |format|
format.js {
render :update do |page|
page.alert(l(:label_issue) + ' ' + l('activerecord.errors.messages.invalid'))
end
}
end
end
end
@@ -243,9 +225,17 @@ class RepositoriesController < ApplicationController
# 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
if @issue
@changeset.issues.delete(@issue)
end
respond_to do |format|
format.js {
render :update do |page|
page.remove "related-issue-#{@issue.id}"
end if @issue
}
end
end
def diff
@@ -317,7 +307,8 @@ class RepositoriesController < ApplicationController
@repository = @project.repository
end
(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_to = params[:rev_to]
@@ -352,17 +343,17 @@ class RepositoriesController < ApplicationController
@date_to = Date.today
@date_from = @date_to << 11
@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,
: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_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(
:all, :group => :commit_date, :include => :changeset,
:conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
changes_by_day = repository.changes.count(
:all, :group => :commit_date,
:conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
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 = []
12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
@@ -393,10 +384,10 @@ class RepositoriesController < ApplicationController
end
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}
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}
fields = commits_by_author.collect {|r| r.first}
@@ -432,3 +423,4 @@ class RepositoriesController < ApplicationController
graph.burn
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,15 +18,15 @@
class RolesController < ApplicationController
layout 'admin'
before_filter :require_admin, :except => [:index, :show]
before_filter :require_admin_or_api_request, :only => [:index, :show]
before_filter :find_role, :only => [:show, :edit, :update, :destroy]
accept_api_auth :index, :show
before_filter :require_admin, :except => :index
before_filter :require_admin_or_api_request, :only => :index
before_filter :find_role, :only => [:edit, :update, :destroy]
accept_api_auth :index
def index
respond_to do |format|
format.html {
@role_pages, @roles = paginate Role.sorted, :per_page => 25
@role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
render :action => "index", :layout => false if request.xhr?
}
format.api {
@@ -35,18 +35,9 @@ class RolesController < ApplicationController
end
end
def show
respond_to do |format|
format.api
end
end
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})
if params[:copy].present? && @copy_from = Role.find_by_id(params[:copy])
@role.copy_from(@copy_from)
end
@roles = Role.sorted.all
end
@@ -55,10 +46,10 @@ class RolesController < ApplicationController
if request.post? && @role.save
# workflow copy
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
flash[:notice] = l(:notice_successful_create)
redirect_to roles_path
redirect_to :action => 'index'
else
@roles = Role.sorted.all
render :action => 'new'
@@ -71,7 +62,7 @@ class RolesController < ApplicationController
def update
if request.put? and @role.update_attributes(params[:role])
flash[:notice] = l(:notice_successful_update)
redirect_to roles_path
redirect_to :action => 'index'
else
render :action => 'edit'
end
@@ -79,10 +70,10 @@ class RolesController < ApplicationController
def destroy
@role.destroy
redirect_to roles_path
redirect_to :action => 'index'
rescue
flash[:error] = l(:error_can_not_remove_role)
redirect_to roles_path
redirect_to :action => 'index'
end
def permissions
@@ -94,7 +85,7 @@ class RolesController < ApplicationController
role.save
end
flash[:notice] = l(:notice_successful_update)
redirect_to roles_path
redirect_to :action => 'index'
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,6 +18,9 @@
class SearchController < ApplicationController
before_filter :find_optional_project
helper :messages
include MessagesHelper
def index
@question = params[:q] || ""
@question.strip!
@@ -40,8 +43,8 @@ class SearchController < ApplicationController
begin; offset = params[:offset].to_time if params[:offset]; rescue; end
# quick jump to an issue
if (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i))
redirect_to issue_path(issue)
if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1.to_i)
redirect_to :controller => "issues", :action => "show", :id => $1
return
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -19,8 +19,6 @@ class SettingsController < ApplicationController
layout 'admin'
menu_item :plugins, :only => :plugin
helper :queries
before_filter :require_admin
def index
@@ -38,11 +36,10 @@ class SettingsController < ApplicationController
Setting[name] = value
end
flash[:notice] = l(:notice_successful_update)
redirect_to settings_path(:tab => params[:tab])
redirect_to :action => 'edit', :tab => params[:tab]
else
@options = {}
user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]}
@options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]}
@options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] }
@deliveries = ActionMailer::Base.perform_deliveries
@guessed_host_and_path = request.host_with_port.dup
@@ -54,15 +51,10 @@ class SettingsController < ApplicationController
def plugin
@plugin = Redmine::Plugin.find(params[:id])
unless @plugin.configurable?
render_404
return
end
if request.post?
Setting.send "plugin_#{@plugin.id}=", params[:settings]
flash[:notice] = l(:notice_successful_update)
redirect_to plugin_settings_path(@plugin)
redirect_to :action => 'plugin', :id => @plugin.id
else
@partial = @plugin.settings[:partial]
@settings = Setting.send "plugin_#{@plugin.id}"

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -30,33 +30,41 @@ class TimelogController < ApplicationController
accept_rss_auth :index
accept_api_auth :index, :show, :create, :update, :destroy
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
helper :sort
include SortHelper
helper :issues
include TimelogHelper
helper :custom_fields
include CustomFieldsHelper
helper :queries
include QueriesHelper
def index
@query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
sort_init 'spent_on', 'desc'
sort_update 'spent_on' => ['spent_on', "#{TimeEntry.table_name}.created_on"],
'user' => 'user_id',
'activity' => 'activity_id',
'project' => "#{Project.table_name}.name",
'issue' => 'issue_id',
'hours' => 'hours'
sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns)
scope = time_entry_scope(:order => sort_clause)
retrieve_date_range
scope = TimeEntry.visible.spent_between(@from, @to)
if @issue
scope = scope.on_issue(@issue)
elsif @project
scope = scope.on_project(@project, Setting.display_subprojects_issues?)
end
respond_to do |format|
format.html {
# Paginate results
@entry_count = scope.count
@entry_pages = Paginator.new @entry_count, per_page_option, params['page']
@entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
@entries = scope.all(
:include => [:project, :activity, :user, {:issue => :tracker}],
:limit => @entry_pages.per_page,
:offset => @entry_pages.offset
:order => sort_clause,
:limit => @entry_pages.items_per_page,
:offset => @entry_pages.current.offset
)
@total_hours = scope.sum(:hours).to_f
@@ -67,13 +75,15 @@ class TimelogController < ApplicationController
@offset, @limit = api_offset_and_limit
@entries = scope.all(
:include => [:project, :activity, :user, {:issue => :tracker}],
:order => sort_clause,
:limit => @limit,
:offset => @offset
)
}
format.atom {
entries = scope.reorder("#{TimeEntry.table_name}.created_on DESC").all(
entries = scope.all(
:include => [:project, :activity, :user, {:issue => :tracker}],
:order => "#{TimeEntry.table_name}.created_on DESC",
:limit => Setting.feeds_limit.to_i
)
render_feed(entries, :title => l(:label_spent_time))
@@ -81,18 +91,17 @@ class TimelogController < ApplicationController
format.csv {
# Export all entries
@entries = scope.all(
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}]
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:order => sort_clause
)
send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv')
send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
}
end
end
def report
@query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
scope = time_entry_scope
@report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
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? }
@@ -125,24 +134,16 @@ class TimelogController < ApplicationController
flash[:notice] = l(:notice_successful_create)
if params[:continue]
if params[:project_id]
options = {
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]
}
if @time_entry.issue
redirect_to new_project_issue_time_entry_path(@time_entry.project, @time_entry.issue, options)
else
redirect_to new_project_time_entry_path(@time_entry.project, options)
end
else
options = {
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]
}
redirect_to new_time_entry_path(options)
end
else
redirect_back_or_default project_time_entries_path(@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) }
@@ -168,9 +169,9 @@ class TimelogController < ApplicationController
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_back_or_default project_time_entries_path(@time_entry.project)
redirect_back_or_default :action => 'index', :project_id => @time_entry.project
}
format.api { render_api_ok }
format.api { head :ok }
end
else
respond_to do |format|
@@ -199,7 +200,7 @@ class TimelogController < ApplicationController
end
end
set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
redirect_back_or_default project_time_entries_path(@projects.first)
redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
end
def destroy
@@ -218,11 +219,11 @@ class TimelogController < ApplicationController
else
flash[:error] = l(:notice_unable_delete_time_entry)
end
redirect_back_or_default project_time_entries_path(@projects.first)
redirect_back_or_default(:action => 'index', :project_id => @projects.first)
}
format.api {
if destroyed
render_api_ok
head :ok
else
render_validation_errors(@time_entries)
end
@@ -290,13 +291,48 @@ private
end
end
# Returns the TimeEntry scope for index and report actions
def time_entry_scope(options={})
scope = @query.results_scope(options)
if @issue
scope = scope.on_issue(@issue)
# Retrieves the date range based on predefined ranges or specific from/to param dates
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
scope
@from, @to = @to, @from if @from && @to && @from > @to
end
def parse_params_for_bulk_time_entry_attributes(params)

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -25,30 +25,30 @@ class TrackersController < ApplicationController
def index
respond_to do |format|
format.html {
@tracker_pages, @trackers = paginate Tracker.sorted, :per_page => 25
@tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
render :action => "index", :layout => false if request.xhr?
}
format.api {
@trackers = Tracker.sorted.all
@trackers = Tracker.all
}
end
end
def new
@tracker ||= Tracker.new(params[:tracker])
@trackers = Tracker.sorted.all
@projects = Project.all
@trackers = Tracker.find :all, :order => 'position'
@projects = Project.find(:all)
end
def create
@tracker = Tracker.new(params[:tracker])
if @tracker.save
if request.post? and @tracker.save
# workflow copy
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
flash[:notice] = l(:notice_successful_create)
redirect_to trackers_path
redirect_to :action => 'index'
return
end
new
@@ -57,14 +57,14 @@ class TrackersController < ApplicationController
def edit
@tracker ||= Tracker.find(params[:id])
@projects = Project.all
@projects = Project.find(:all)
end
def update
@tracker = Tracker.find(params[:id])
if @tracker.update_attributes(params[:tracker])
if request.put? and @tracker.update_attributes(params[:tracker])
flash[:notice] = l(:notice_successful_update)
redirect_to trackers_path
redirect_to :action => 'index'
return
end
edit
@@ -78,24 +78,6 @@ class TrackersController < ApplicationController
else
@tracker.destroy
end
redirect_to trackers_path
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 fields_trackers_path
return
end
@trackers = Tracker.sorted.all
@custom_fields = IssueCustomField.all.sort
redirect_to :action => 'index'
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -45,9 +45,12 @@ class UsersController < ApplicationController
scope = scope.in_group(params[:group_id]) if params[:group_id].present?
@user_count = scope.count
@user_pages = Paginator.new @user_count, @limit, params['page']
@offset ||= @user_pages.offset
@users = scope.order(sort_clause).limit(@limit).offset(@offset).all
@user_pages = Paginator.new self, @user_count, @limit, params['page']
@offset ||= @user_pages.current.offset
@users = scope.find :all,
:order => sort_clause,
:limit => @limit,
:offset => @offset
respond_to do |format|
format.html {
@@ -55,7 +58,7 @@ class UsersController < ApplicationController
render :layout => !request.xhr?
}
format.api
end
end
end
def show
@@ -80,7 +83,7 @@ class UsersController < ApplicationController
def new
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
@auth_sources = AuthSource.all
@auth_sources = AuthSource.find(:all)
end
def create
@@ -92,24 +95,24 @@ class UsersController < ApplicationController
if @user.save
@user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
@user.pref.save
@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|
format.html {
flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
if params[:continue]
redirect_to new_user_path
else
redirect_to edit_user_path(@user)
end
flash[:notice] = l(:notice_successful_create)
redirect_to(params[:continue] ?
{:controller => 'users', :action => 'new'} :
{:controller => 'users', :action => 'edit', :id => @user}
)
}
format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
end
else
@auth_sources = AuthSource.all
@auth_sources = AuthSource.find(:all)
# Clear password input
@user.password = @user.password_confirmation = nil
@@ -121,7 +124,7 @@ class UsersController < ApplicationController
end
def edit
@auth_sources = AuthSource.all
@auth_sources = AuthSource.find(:all)
@membership ||= Member.new
end
@@ -136,26 +139,27 @@ class UsersController < ApplicationController
was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
# TODO: Similar to My#account
@user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
if @user.save
@user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
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?
Mailer.account_information(@user, params[:user][:password]).deliver
Mailer.deliver_account_information(@user, params[:user][:password])
end
respond_to do |format|
format.html {
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
else
@auth_sources = AuthSource.all
@auth_sources = AuthSource.find(:all)
@membership ||= Member.new
# Clear password input
@user.password = @user.password_confirmation = nil
@@ -165,22 +169,39 @@ class UsersController < ApplicationController
format.api { render_validation_errors(@user) }
end
end
rescue ::ActionController::RedirectBackError
redirect_to :controller => 'users', :action => 'edit', :id => @user
end
def destroy
@user.destroy
respond_to do |format|
format.html { redirect_back_or_default(users_path) }
format.api { render_api_ok }
format.html { redirect_to :back }
format.api { head :ok }
end
rescue ::ActionController::RedirectBackError
redirect_to(users_url)
end
def edit_membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save
respond_to do |format|
format.html { redirect_to edit_user_path(@user, :tab => 'memberships') }
format.js
if @membership.valid?
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
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
@@ -190,8 +211,8 @@ class UsersController < ApplicationController
@membership.destroy
end
respond_to do |format|
format.html { redirect_to edit_user_path(@user, :tab => 'memberships') }
format.js
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,7 +20,7 @@ class VersionsController < ApplicationController
model_object Version
before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
before_filter :find_project_by_project_id, :only => [:index, :new, :create, :close_completed]
before_filter :find_project, :only => [:index, :new, :create, :close_completed]
before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
@@ -31,7 +31,7 @@ class VersionsController < ApplicationController
def index
respond_to do |format|
format.html {
@trackers = @project.trackers.sorted.all
@trackers = @project.trackers.find(:all, :order => 'position')
retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
@@ -64,10 +64,9 @@ class VersionsController < ApplicationController
def show
respond_to do |format|
format.html {
@issues = @version.fixed_issues.visible.
includes(:status, :tracker, :priority).
reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
all
@issues = @version.fixed_issues.visible.find(:all,
:include => [:status, :tracker, :priority],
:order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
}
format.api
end
@@ -79,7 +78,13 @@ class VersionsController < ApplicationController
respond_to do |format|
format.html
format.js
format.js do
render :update do |page|
page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
page << "showModal('ajax-modal', '600px');"
page << "Form.Element.focus('version_name');"
end
end
end
end
@@ -96,9 +101,16 @@ class VersionsController < ApplicationController
respond_to do |format|
format.html do
flash[:notice] = l(:notice_successful_create)
redirect_back_or_default settings_project_path(@project, :tab => 'versions')
redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end
format.js do
render(:update) {|page|
page << 'hideModal();'
# IE doesn't support the replace_html rjs method for select box options
page.replace "issue_fixed_version_id",
content_tag('select', content_tag('option') + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
}
end
format.js
format.api do
render :action => 'show', :status => :created, :location => version_url(@version)
end
@@ -106,7 +118,12 @@ class VersionsController < ApplicationController
else
respond_to do |format|
format.html { render :action => 'new' }
format.js { render :action => 'new' }
format.js do
render :update do |page|
page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
page << "Form.Element.focus('version_name');"
end
end
format.api { render_validation_errors(@version) }
end
end
@@ -125,9 +142,9 @@ class VersionsController < ApplicationController
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_back_or_default settings_project_path(@project, :tab => 'versions')
redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
}
format.api { render_api_ok }
format.api { head :ok }
end
else
respond_to do |format|
@@ -142,21 +159,21 @@ class VersionsController < ApplicationController
if request.put?
@project.close_completed_versions
end
redirect_to settings_project_path(@project, :tab => 'versions')
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end
def destroy
if @version.fixed_issues.empty?
@version.destroy
respond_to do |format|
format.html { redirect_back_or_default settings_project_path(@project, :tab => 'versions') }
format.api { render_api_ok }
format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project }
format.api { head :ok }
end
else
respond_to do |format|
format.html {
flash[:error] = l(:notice_unable_delete_version)
redirect_to settings_project_path(@project, :tab => 'versions')
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
}
format.api { head :unprocessable_entity }
end
@@ -166,11 +183,16 @@ class VersionsController < ApplicationController
def status_by
respond_to do |format|
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
private
private
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
if ids = params[:tracker_ids]
@@ -179,4 +201,5 @@ class VersionsController < ApplicationController
@selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
end
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -16,65 +16,94 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class WatchersController < ApplicationController
before_filter :require_login, :find_watchables, :only => [:watch, :unwatch]
before_filter :find_project
before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
before_filter :authorize, :only => [:new, :destroy]
def watch
set_watcher(@watchables, User.current, true)
if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
render_403
else
set_watcher(User.current, true)
end
end
def unwatch
set_watcher(@watchables, User.current, false)
set_watcher(User.current, false)
end
before_filter :find_project, :authorize, :only => [:new, :create, :append, :destroy, :autocomplete_for_user]
accept_api_auth :create, :destroy
def new
respond_to do |format|
format.js do
render :update do |page|
page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
page << "showModal('ajax-modal', '400px');"
page << "$('ajax-modal').addClassName('new-watcher');"
end
end
end
end
def create
user_ids = []
if params[:watcher].is_a?(Hash)
user_ids << (params[:watcher][:user_ids] || params[:watcher][:user_id])
else
user_ids << params[:user_id]
end
user_ids.flatten.compact.uniq.each do |user_id|
Watcher.create(:watchable => @watched, :user_id => user_id)
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|
format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
format.js
format.api { render_api_ok }
format.html { redirect_to :back }
format.js do
render :update do |page|
page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
end
end
end
rescue ::ActionController::RedirectBackError
render :text => 'Watcher added.', :layout => true
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)
users = User.active.find_all_by_id(user_ids)
respond_to do |format|
format.js do
render :update do |page|
users.each do |user|
page.select("#issue_watcher_user_ids_#{user.id}").each do |item|
page.remove item
end
end
page.insert_html :bottom, 'watchers_inputs', :text => watchers_checkboxes(nil, users, true)
end
end
end
end
end
def destroy
@watched.set_watcher(User.find(params[:user_id]), false)
@watched.set_watcher(User.find(params[:user_id]), false) if request.post?
respond_to do |format|
format.html { redirect_to :back }
format.js
format.api { render_api_ok }
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.sorted.like(params[:q]).limit(100).all
@users = User.active.like(params[:q]).find(:all, :limit => 100)
if @watched
@users -= @watched.watcher_users
end
render :layout => false
end
private
private
def find_project
if params[:object_type] && params[:object_id]
klass = Object.const_get(params[:object_type].camelcase)
@@ -82,28 +111,26 @@ class WatchersController < ApplicationController
@watched = klass.find(params[:object_id])
@project = @watched.project
elsif params[:project_id]
@project = Project.visible.find_by_param(params[:project_id])
@project = Project.visible.find(params[:project_id])
end
rescue
render_404
end
def find_watchables
klass = Object.const_get(params[:object_type].camelcase) rescue nil
if klass && klass.respond_to?('watched_by')
@watchables = klass.find_all_by_id(Array.wrap(params[:object_id]))
raise Unauthorized if @watchables.any? {|w| w.respond_to?(:visible?) && !w.visible?}
end
render_404 unless @watchables.present?
end
def set_watcher(watchables, user, watching)
watchables.each do |watchable|
watchable.set_watcher(user, watching)
end
def set_watcher(user, watching)
@watched.set_watcher(user, watching)
respond_to do |format|
format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} }
format.html { redirect_to :back }
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
rescue ::ActionController::RedirectBackError
render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -35,9 +35,7 @@ class WikiController < ApplicationController
default_search_scope :wiki_pages
before_filter :find_wiki, :authorize
before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
accept_api_auth :index, :show, :update, :destroy
before_filter :find_attachments, :only => [:preview]
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
helper :attachments
include AttachmentsHelper
@@ -47,13 +45,7 @@ class WikiController < ApplicationController
# List of pages, sorted alphabetically and by parent (hierarchy)
def index
load_pages_for_index
respond_to do |format|
format.html {
@pages_by_parent_id = @pages.group_by(&:parent_id)
}
format.api
end
@pages_by_parent_id = @pages.group_by(&:parent_id)
end
# List of page, by last update
@@ -65,7 +57,7 @@ class WikiController < ApplicationController
# display a page (in editing mode if it doesn't exist)
def show
if @page.new_record?
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable?
edit
render :action => 'edit'
else
@@ -74,7 +66,8 @@ class WikiController < ApplicationController
return
end
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
deny_access
# Redirects user to the current version if he's not allowed to view previous versions
redirect_to :version => nil
return
end
@content = @page.content_for_version(params[:version])
@@ -96,10 +89,7 @@ class WikiController < ApplicationController
@content.current_version? &&
Redmine::WikiFormatting.supports_section_edit?
respond_to do |format|
format.html
format.api
end
render :action => 'show'
end
# edit an existing page or a new one
@@ -119,7 +109,7 @@ class WikiController < ApplicationController
# To prevent StaleObjectError exception when reverting to a previous version
@content.version = @page.content.version
@text = @content.text
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@section = params[:section].to_i
@@ -131,65 +121,48 @@ class WikiController < ApplicationController
# Creates a new page or updates an existing one
def update
return render_403 unless editable?
was_new_page = @page.new_record?
@page.content = WikiContent.new(:page => @page) if @page.new_record?
@page.safe_attributes = params[:wiki_page]
@content = @page.content
content_params = params[:content]
if content_params.nil? && params[:wiki_page].is_a?(Hash)
content_params = params[:wiki_page].slice(:text, :comments, :version)
end
content_params ||= {}
@content = @page.content_for_version(params[:version])
@content.text = initial_page_content(@page) if @content.text.blank?
# don't keep previous comment
@content.comments = nil
@content.comments = content_params[:comments]
@text = content_params[:text]
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 content if text wasn't changed
@page.save
redirect_to :action => 'show', :project_id => @project, :id => @page.title
return
end
@content.comments = params[:content][:comments]
@text = params[:content][:text]
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@section = params[:section].to_i
@section_hash = params[:section_hash]
@content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
else
@content.version = content_params[:version] if content_params[:version]
@content.version = params[:content][:version]
@content.text = @text
end
@content.author = User.current
if @page.save_with_content
@page.content = @content
if @page.save
attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page)
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
respond_to do |format|
format.html { redirect_to project_wiki_page_path(@project, @page.title) }
format.api {
if was_new_page
render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title)
else
render_api_ok
end
}
end
redirect_to :action => 'show', :project_id => @project, :id => @page.title
else
respond_to do |format|
format.html { render :action => 'edit' }
format.api { render_validation_errors(@content) }
end
render :action => 'edit'
end
rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError
# Optimistic locking exception
respond_to do |format|
format.html {
flash.now[:error] = l(:notice_locking_conflict)
render :action => 'edit'
}
format.api { render_api_head :conflict }
end
rescue ActiveRecord::RecordNotSaved
respond_to do |format|
format.html { render :action => 'edit' }
format.api { render_validation_errors(@content) }
end
flash.now[:error] = l(:notice_locking_conflict)
render :action => 'edit'
end
# rename a page
@@ -200,26 +173,25 @@ class WikiController < ApplicationController
@original_title = @page.pretty_title
if request.post? && @page.update_attributes(params[:wiki_page])
flash[:notice] = l(:notice_successful_update)
redirect_to project_wiki_page_path(@project, @page.title)
redirect_to :action => 'show', :project_id => @project, :id => @page.title
end
end
def protect
@page.update_attribute :protected, params[:protected]
redirect_to project_wiki_page_path(@project, @page.title)
redirect_to :action => 'show', :project_id => @project, :id => @page.title
end
# show page history
def history
@version_count = @page.content.versions.count
@version_pages = Paginator.new @version_count, per_page_option, params['page']
@version_pages = Paginator.new self, @version_count, per_page_option, params['p']
# don't load text
@versions = @page.content.versions.
select("id, author_id, comments, updated_on, version").
reorder('version DESC').
limit(@version_pages.per_page + 1).
offset(@version_pages.offset).
all
@versions = @page.content.versions.find :all,
:select => "id, author_id, comments, updated_on, version",
:order => 'version DESC',
:limit => @version_pages.items_per_page + 1,
:offset => @version_pages.current.offset
render :layout => false if request.xhr?
end
@@ -256,28 +228,16 @@ class WikiController < ApplicationController
end
else
@reassignable_to = @wiki.pages - @page.self_and_descendants
# display the destroy form if it's a user request
return unless api_request?
return
end
end
@page.destroy
respond_to do |format|
format.html { redirect_to project_wiki_index_path(@project) }
format.api { render_api_ok }
end
end
def destroy_version
return render_403 unless editable?
@content = @page.content_for_version(params[:version])
@content.destroy
redirect_to_referer_or history_project_wiki_page_path(@project, @page.title)
redirect_to :action => 'index', :project_id => @project
end
# Export wiki to a single pdf or html file
def export
@pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}])
@pages = @wiki.pages.all(:order => 'title', :include => [:content, :attachments], :limit => 75)
respond_to do |format|
format.html {
export = render_to_string :action => 'export_multiple', :layout => false
@@ -294,7 +254,7 @@ class WikiController < ApplicationController
# page is nil when previewing a new page
return render_403 unless page.nil? || editable?(page)
if page
@attachments += page.attachments
@attachements = page.attachments
@previewed = page.content
end
@text = params[:content][:text]
@@ -351,6 +311,6 @@ private
end
def load_pages_for_index
@pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
@pages = @wiki.pages.with_updated_on.all(:order => 'title', :include => {:wiki => :project})
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -24,13 +24,14 @@ class WikisController < ApplicationController
@wiki = @project.wiki || Wiki.new(:project => @project)
@wiki.safe_attributes = params[:wiki]
@wiki.save if request.post?
render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
end
# Delete a project's wiki
def destroy
if request.post? && params[:confirm] && @project.wiki
@project.wiki.destroy
redirect_to settings_project_path(@project, :tab => 'wiki')
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
end
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,27 +18,30 @@
class WorkflowsController < ApplicationController
layout 'admin'
before_filter :require_admin, :find_roles, :find_trackers
before_filter :require_admin
before_filter :find_roles
before_filter :find_trackers
def index
@workflow_counts = WorkflowTransition.count_by_tracker_and_role
@workflow_counts = Workflow.count_by_tracker_and_role
end
def edit
@role = Role.find_by_id(params[:role_id]) if params[:role_id]
@tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id]
@role = Role.find_by_id(params[:role_id])
@tracker = Tracker.find_by_id(params[:tracker_id])
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|
transitions.each { |new_status_id, options|
author = options.is_a?(Array) && options.include?('author') && !options.include?('always')
assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always')
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
redirect_to workflows_edit_path(: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
end
end
@@ -47,10 +50,10 @@ class WorkflowsController < ApplicationController
if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
@statuses = @tracker.issue_statuses
end
@statuses ||= IssueStatus.sorted.all
@statuses ||= IssueStatus.find(:all, :order => 'position')
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['always'] = workflows.select {|w| !w.author && !w.assignee}
@workflows['author'] = workflows.select {|w| w.author}
@@ -58,35 +61,6 @@ class WorkflowsController < ApplicationController
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 workflows_permissions_path(: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
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
@@ -106,12 +80,12 @@ class WorkflowsController < ApplicationController
if request.post?
if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?)
flash.now[:error] = l(:error_workflow_copy_source)
elsif @target_trackers.blank? || @target_roles.blank?
elsif @target_trackers.nil? || @target_roles.nil?
flash.now[:error] = l(:error_workflow_copy_target)
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)
redirect_to workflows_copy_path(: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
end
@@ -119,10 +93,10 @@ class WorkflowsController < ApplicationController
private
def find_roles
@roles = Role.sorted.all
@roles = Role.find(:all, :order => 'builtin, position')
end
def find_trackers
@trackers = Tracker.sorted.all
@trackers = Tracker.find(:all, :order => 'position')
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,33 +0,0 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 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 ActivitiesHelper
def sort_activity_events(events)
events_by_group = events.group_by(&:event_group)
sorted_events = []
events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event|
if group_events = events_by_group.delete(event.event_group)
group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i|
sorted_events << [e, i > 0]
end
end
end
sorted_events
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,8 +20,6 @@
module AdminHelper
def project_status_options_for_select(selected)
options_for_select([[l(:label_all), ''],
[l(:project_status_active), '1'],
[l(:project_status_closed), '5'],
[l(:project_status_archived), '9']], selected.to_s)
[l(:status_active), '1']], selected.to_s)
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -24,7 +24,6 @@ module ApplicationHelper
include Redmine::WikiFormatting::Macros::Definitions
include Redmine::I18n
include GravatarHelper::PublicMethods
include Redmine::Pagination::Helper
extend Forwardable
def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
@@ -44,12 +43,18 @@ module ApplicationHelper
link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
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
def link_to_user(user, options={})
if user.is_a?(User)
name = h(user.name(options[:format]))
if user.active? || (User.current.admin? && user.logged?)
link_to name, user_path(user), :class => user.css_classes
if user.active?
link_to name, :controller => 'users', :action => 'show', :id => user
else
name
end
@@ -65,12 +70,10 @@ module ApplicationHelper
# link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
# link_to_issue(issue, :subject => false) # => Defect #6
# link_to_issue(issue, :project => true) # => Foo - Defect #6
# link_to_issue(issue, :subject => false, :tracker => false) # => #6
#
def link_to_issue(issue, options={})
title = nil
subject = nil
text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
if options[:subject] == false
title = truncate(issue.subject, :length => 60)
else
@@ -79,7 +82,9 @@ module ApplicationHelper
subject = truncate(subject, :length => options[:truncate])
end
end
s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title
s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
:class => issue.css_classes,
:title => title
s << h(": #{subject}") if subject
s = h("#{issue.project} - ") + s if options[:project]
s
@@ -91,10 +96,14 @@ module ApplicationHelper
# * :download - Force download (default: false)
def link_to_attachment(attachment, options={})
text = options.delete(:text) || attachment.filename
route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
html_options = options.slice!(:only_path)
url = send(route_method, attachment, attachment.filename, options)
link_to text, url, html_options
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),
{:controller => 'attachments', :action => action,
:id => attachment, :filename => attachment.filename}.merge(opt_only_path),
options)
end
# Generates a link to a SCM revision
@@ -116,11 +125,13 @@ module ApplicationHelper
# Generates a link to a message
def link_to_message(message, options={}, html_options = nil)
link_to(
truncate(message.subject, :length => 60),
board_message_path(message.board_id, message.parent_id || message.id, {
h(truncate(message.subject, :length => 60)),
{ :controller => 'messages', :action => 'show',
:board_id => message.board_id,
:id => message.root,
:r => (message.parent_id && message.id),
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
}.merge(options)),
}.merge(options),
html_options
)
end
@@ -129,45 +140,22 @@ module ApplicationHelper
# Examples:
#
# link_to_project(project) # => link to the specified project overview
# link_to_project(project, :action=>'settings') # => link to project settings
# link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
# link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
#
def link_to_project(project, options={}, html_options = nil)
if project.archived?
h(project.name)
elsif options.key?(:action)
ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
link_to project.name, url, html_options
else
link_to project.name, project_path(project, options), html_options
end
end
# Generates a link to a project settings if active
def link_to_project_settings(project, options={}, html_options=nil)
if project.active?
link_to project.name, settings_project_path(project, options), html_options
elsif project.archived?
h(project.name)
url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
link_to(h(project), url, html_options)
else
link_to project.name, project_path(project, options), html_options
h(project)
end
end
def wiki_page_path(page, options={})
url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
end
def thumbnail_tag(attachment)
link_to image_tag(thumbnail_path(attachment)),
named_attachment_path(attachment, attachment.filename),
:title => attachment.filename
end
def toggle_link(name, id, options={})
onclick = "$('##{id}').toggle(); "
onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
onclick = "Element.toggle('#{id}'); "
onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
onclick << "return false;"
link_to(name, "#", :onclick => onclick)
end
@@ -180,12 +168,17 @@ module ApplicationHelper
}))
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)
h(truncate_single_line(text, :length => 100))
end
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
def format_activity_description(text)
@@ -195,7 +188,7 @@ module ApplicationHelper
def format_version_name(version)
if version.project == @project
h(version)
h(version)
else
h("#{version.project} - #{version}")
end
@@ -207,46 +200,13 @@ module ApplicationHelper
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={})
content = ''
if pages[node]
content << "<ul class=\"pages-hierarchy\">\n"
pages[node].each do |page|
content << "<li>"
content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
:title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
content << "</li>\n"
@@ -260,7 +220,7 @@ module ApplicationHelper
def render_flash_messages
s = ''
flash.each do |k,v|
s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
s << (content_tag('div', v.html_safe, :class => "flash #{k}"))
end
s.html_safe
end
@@ -277,24 +237,23 @@ module ApplicationHelper
# Renders the project quick-jump box
def render_project_jump_box
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?
options =
("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
'<option value="" disabled="disabled">---</option>').html_safe
options << project_tree_options_for_select(projects, :selected => @project) do |p|
{ :value => project_path(:id => p, :jump => current_menu_item) }
s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
"<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
'<option value="" disabled="disabled">---</option>'
s << project_tree_options_for_select(projects, :selected => @project) do |p|
{ :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
end
select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
s << '</select>'
s.html_safe
end
end
def project_tree_options_for_select(projects, options = {})
s = ''
project_tree(projects) do |project, level|
name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ').html_safe : '')
tag_options = {:value => project.id}
if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
tag_options[:selected] = 'selected'
@@ -314,10 +273,34 @@ module ApplicationHelper
Project.project_tree(projects, &block)
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)
s = ''
principals.each do |principal|
s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
principals.sort.each do |principal|
s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
end
s.html_safe
end
@@ -326,7 +309,7 @@ module ApplicationHelper
def principals_options_for_select(collection, selected=nil)
s = ''
if collection.include?(User.current)
s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
s << content_tag('option', "<< #{l(:label_me)} >>".html_safe, :value => User.current.id)
end
groups = ''
collection.sort.each do |element|
@@ -336,16 +319,7 @@ module ApplicationHelper
unless groups.empty?
s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
end
s.html_safe
end
# Options for the new membership projects combo-box
def options_for_membership_project_select(principal, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
options << project_tree_options_for_select(projects) do |p|
{:disabled => principal.projects.to_a.include?(p)}
end
options
s
end
# Truncates and returns the string as a single line
@@ -378,7 +352,7 @@ module ApplicationHelper
def time_tag(time)
text = distance_of_time_in_words(Time.now, time)
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
content_tag('acronym', text, :title => format_time(time))
end
@@ -395,8 +369,48 @@ module ApplicationHelper
end
def to_path_param(path)
str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
str.blank? ? nil : str
path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
end
def pagination_links_full(paginator, count=nil, options={})
page_param = options.delete(:page_param) || :page
per_page_links = options.delete(:per_page_links)
url_param = params.dup
html = ''
if paginator.current.previous
# \xc2\xab(utf-8) = &#171;
html << link_to_content_update(
"\xc2\xab " + l(:label_previous),
url_param.merge(page_param => paginator.current.previous)) + ' '
end
html << (pagination_links_each(paginator, options) do |n|
link_to_content_update(n.to_s, url_param.merge(page_param => n))
end || '')
if paginator.current.next
# \xc2\xbb(utf-8) = &#187;
html << ' ' + link_to_content_update(
(l(:label_next) + " \xc2\xbb"),
url_param.merge(page_param => paginator.current.next))
end
unless count.nil?
html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
if per_page_links != false && links = per_page_links(paginator.items_per_page)
html << " | #{links}"
end
end
html.html_safe
end
def per_page_links(selected=nil)
links = Setting.per_page_options_array.collect do |n|
n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
end
links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
end
def reorder_links(name, url, method = :post)
@@ -471,11 +485,7 @@ module ApplicationHelper
end
def accesskey(s)
@used_accesskeys ||= []
key = Redmine::AccessKeys.key_for(s)
return nil if @used_accesskeys.include?(key)
@used_accesskeys << key
key
Redmine::AccessKeys.key_for s
end
# Formats text according to system settings.
@@ -499,8 +509,6 @@ module ApplicationHelper
project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
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)
@parsed_headings = []
@@ -508,8 +516,8 @@ module ApplicationHelper
@current_section = 0 if options[:edit_section_links]
parse_sections(text, project, obj, attr, only_path, options)
text = parse_non_pre_blocks(text, obj, macros) do |text|
[:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
text = parse_non_pre_blocks(text) do |text|
[: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
end
end
@@ -522,7 +530,7 @@ module ApplicationHelper
text.html_safe
end
def parse_non_pre_blocks(text, obj, macros)
def parse_non_pre_blocks(text)
s = StringScanner.new(text)
tags = []
parsed = ''
@@ -531,9 +539,6 @@ module ApplicationHelper
text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
if tags.empty?
yield text
inject_macros(text, obj, macros) if macros.any?
else
inject_macros(text, obj, macros, false) if macros.any?
end
parsed << text
if tag
@@ -556,14 +561,14 @@ module ApplicationHelper
def parse_inline_attachments(text, project, obj, attr, only_path, options)
# when using an image link, try to use an attachment, if possible
attachments = options[:attachments] || []
attachments += obj.attachments if obj.respond_to?(:attachments)
if attachments.present?
if options[:attachments] || (obj && obj.respond_to?(:attachments))
attachments = options[:attachments] || obj.attachments
text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
filename, ext, alt, alttext = $1.downcase, $2, $3, $4
# search for the picture in attachments
if found = Attachment.latest_attach(attachments, filename)
image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
image_url = url_for :only_path => only_path, :controller => 'attachments',
:action => 'download', :id => found
desc = found.description.to_s.gsub('"', '')
if !desc.blank? && alttext.blank?
alt = " title=\"#{desc}\" alt=\"#{desc}\""
@@ -592,9 +597,9 @@ module ApplicationHelper
esc, all, page, title = $1, $2, $3, $5
if esc.nil?
if page =~ /^([^\:]+)\:(.*)$/
identifier, page = $1, $2
link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
title ||= identifier if page.blank?
link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
page = $2
title ||= $1 if page.blank?
end
if link_project && link_project.wiki
@@ -616,7 +621,7 @@ module ApplicationHelper
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, :version => nil, :anchor => anchor, :parent => parent)
:id => wiki_page_id, :anchor => anchor, :parent => parent)
end
end
link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
@@ -662,11 +667,10 @@ module ApplicationHelper
# identifier:document:"Some document"
# identifier:version:1.0.0
# identifier:source:some/file
def parse_redmine_links(text, default_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|
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|
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
link = nil
project = default_project
if project_identifier
project = Project.visible.find_by_identifier(project_identifier)
end
@@ -690,7 +694,7 @@ module ApplicationHelper
oid = identifier.to_i
case prefix
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, :anchor => anchor},
:class => issue.css_classes,
@@ -752,26 +756,27 @@ module ApplicationHelper
when 'commit', 'source', 'export'
if project
repository = nil
if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
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.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
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',
:title => truncate_single_line(changeset.comments, :length => 100)
:title => truncate_single_line(h(changeset.comments), :length => 100)
end
else
if repository && User.current.allowed_to?(:browse_repository, project)
name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
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}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
:path => to_path_param(path),
:rev => rev,
:anchor => anchor},
:anchor => anchor,
:format => (prefix == 'export' ? 'raw' : nil)},
:class => (prefix == 'export' ? 'source download' : 'source')
end
end
@@ -779,11 +784,12 @@ module ApplicationHelper
end
when 'attachment'
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
if attachments && attachment = Attachment.latest_attach(attachments, name)
link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
if attachments && attachment = attachments.detect {|a| a.filename == name }
link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
:class => 'attachment'
end
when 'project'
if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
link = link_to_project(p, {:only_path => only_path}, :class => 'project')
end
end
@@ -832,57 +838,31 @@ module ApplicationHelper
end
end
MACROS_RE = /(
MACROS_RE = /
(!)? # escaping
(
\{\{ # opening tag
([\w]+) # macro name
(\(([^\n\r]*?)\))? # optional arguments
([\n\r].*?[\n\r])? # optional block of text
(\(([^\}]*)\))? # optional arguments
\}\} # closing tag
)
)/mx unless const_defined?(:MACROS_RE)
/x unless const_defined?(:MACROS_RE)
MACRO_SUB_RE = /(
\{\{
macro\((\d+)\)
\}\}
)/x unless const_defined?(:MACRO_SUB_RE)
# Extracts macros from text
def catch_macros(text)
macros = {}
# Macros substitution
def parse_macros(text, project, obj, attr, only_path, options)
text.gsub!(MACROS_RE) do
all, macro = $1, $4.downcase
if macro_exists?(macro) || all =~ MACRO_SUB_RE
index = macros.size
macros[index] = all
"{{macro(#{index})}}"
esc, all, macro = $1, $2, $3.downcase
args = ($5 || '').split(',').each(&:strip)
if esc.nil?
begin
exec_macro(macro, obj, args)
rescue => e
"<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
end || all
else
all
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
TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
@@ -930,7 +910,8 @@ module ApplicationHelper
end
def lang_options_for_select(blank=true)
(blank ? [["(auto)", ""]] : []) + languages_options
(blank ? [["(auto)", ""]] : []) +
valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
end
def label_tag_for(name, option_tags = nil, options = {})
@@ -938,12 +919,19 @@ module ApplicationHelper
content_tag("label", label_text)
end
def labelled_tabular_form_for(*args, &proc)
ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
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 => Redmine::Views::LabelledFormBuilder})
form_for(*args, &proc)
end
def labelled_form_for(*args, &proc)
args << {} unless args.last.is_a?(Hash)
options = args.last
if args.first.is_a?(Symbol)
options.merge!(:as => args.shift)
end
options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
form_for(*args, &proc)
end
@@ -956,11 +944,10 @@ module ApplicationHelper
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)
options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
remote_form_for(*args, &proc)
end
def error_messages_for(*objects)
@@ -977,44 +964,10 @@ module ApplicationHelper
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
url = back_url
hidden_field_tag('back_url', url, :id => nil) unless url.blank?
back_url = params[:back_url] || request.env['HTTP_REFERER']
back_url = CGI.unescape(back_url.to_s)
hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
end
def check_all_links(form_name)
@@ -1036,7 +989,7 @@ module ApplicationHelper
(pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
(pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
), :class => 'progress', :style => "width: #{width};").html_safe +
content_tag('p', legend, :class => 'percent').html_safe
content_tag('p', legend, :class => 'pourcent').html_safe
end
def checked_image(checked=true)
@@ -1058,89 +1011,37 @@ module ApplicationHelper
end
@context_menu_included = true
end
javascript_tag "contextMenuInit('#{ url_for(url) }')"
javascript_tag "new ContextMenu('#{ url_for(url) }')"
end
def calendar_for(field_id)
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
def include_calendar_headers_tags
unless @calendar_headers_tags_included
@calendar_headers_tags_included = true
content_for :header_tags do
start_of_week = Setting.start_of_week
start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
# Redmine uses 1..7 (monday..sunday) in settings and locales
# JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
start_of_week = start_of_week.to_i % 7
tags = javascript_tag(
"var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
"showOn: 'button', buttonImageOnly: true, buttonImage: '" +
path_to_image('/images/calendar.png') +
"', showButtonPanel: true, showWeek: true, showOtherMonths: true, selectOtherMonths: 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
source
end
end
super sources, options
end
# Overrides Rails' image_tag with themes and plugins support.
# Examples:
# image_tag('image.png') # => picks image.png from the current theme or defaults
# image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
#
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}"
start_of_week = case Setting.start_of_week.to_i
when 1
'Calendar._FD = 1;' # Monday
when 7
'Calendar._FD = 0;' # Sunday
when 6
'Calendar._FD = 6;' # Saturday
else
source
'' # use language
end
javascript_include_tag('calendar/calendar') +
javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
javascript_tag(start_of_week) +
javascript_include_tag('calendar/calendar-setup') +
stylesheet_link_tag('calendar')
end
end
super sources, options
end
def content_for(name, content = nil, &block)
@@ -1153,14 +1054,6 @@ module ApplicationHelper
(@has_content && @has_content[name]) || false
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?
!!ActionMailer::Base.perform_deliveries
end
@@ -1169,7 +1062,7 @@ module ApplicationHelper
# +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 = { })
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
if user.respond_to?(:mail)
email = user.mail
@@ -1183,19 +1076,14 @@ module ApplicationHelper
end
def sanitize_anchor_name(anchor)
if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
else
# TODO: remove when ruby1.8 is no longer supported
anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
end
anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
end
# Returns the javascript tags that are included in the html layout head
def javascript_heads
tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
tags = javascript_include_tag(:defaults)
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
tags
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -21,14 +21,12 @@ module AttachmentsHelper
# Displays view/delete links to the attachments of the given object
# Options:
# :author -- author names are not displayed if set to false
# :thumbails -- display thumbnails if enabled in settings
def link_to_attachments(container, options = {})
options.assert_valid_keys(:author, :thumbnails)
options.assert_valid_keys(:author)
if container.attachments.any?
options = {:deletable => container.attachments_deletable?, :author => true}.merge(options)
render :partial => 'attachments/links',
:locals => {:attachments => container.attachments, :options => options, :thumbnails => (options[:thumbnails] && Setting.thumbnails_enabled?)}
render :partial => 'attachments/links', :locals => {:attachments => container.attachments, :options => options}
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,24 +18,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# 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
@@ -26,8 +26,8 @@ module ContextMenusHelper
end
if options.delete(:disabled)
options.delete(:method)
options.delete(:data)
options[:onclick] = 'return false;'
options.delete(:confirm)
options.delete(:onclick)
options[:class] << ' disabled'
url = '#'
end
@@ -36,7 +36,7 @@ module ContextMenusHelper
def bulk_update_custom_field_context_menu_link(field, text, value)
context_menu_link h(text),
bulk_update_issues_path(:ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back),
{:controller => 'issues', :action => 'bulk_update', :ids => @issues.collect(&:id), :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back},
:method => :post,
:selected => (@issue && @issue.custom_field_value(field) == value)
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# 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
@@ -20,89 +20,92 @@
module CustomFieldsHelper
def custom_fields_tabs
CustomField::CUSTOM_FIELDS_TABS
tabs = [{:name => 'IssueCustomField', :partial => 'custom_fields/index', :label => :label_issue_plural},
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', :label => :label_spent_time},
{:name => 'ProjectCustomField', :partial => 'custom_fields/index', :label => :label_project_plural},
{:name => 'VersionCustomField', :partial => 'custom_fields/index', :label => :label_version_plural},
{:name => 'UserCustomField', :partial => 'custom_fields/index', :label => :label_user_plural},
{:name => 'GroupCustomField', :partial => 'custom_fields/index', :label => :label_group_plural},
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', :label => TimeEntryActivity::OptionName},
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', :label => IssuePriority::OptionName},
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName}
]
end
# Return custom field html tag corresponding to its format
def custom_field_tag(name, custom_value)
def custom_field_tag(name, custom_value)
custom_field = custom_value.custom_field
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
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)
case field_format.try(:edit_as)
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)
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"
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"
blank_option = ''.html_safe
blank_option = ''
unless custom_field.multiple?
if custom_field.is_required?
unless custom_field.default_value.present?
blank_option = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '')
blank_option = "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>"
end
else
blank_option = content_tag('option')
blank_option = '<option></option>'
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?))
s = select_tag(field_name, blank_option.html_safe + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value),
:id => field_id, :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)
text_field_tag(field_name, custom_value.value, :id => field_id)
end
end
# Return custom field label tag
def custom_field_label_tag(name, custom_value, options={})
required = options[:required] || custom_value.custom_field.is_required?
def custom_field_label_tag(name, custom_value)
content_tag "label", h(custom_value.custom_field.name) +
(required ? " <span class=\"required\">*</span>".html_safe : ""),
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
(custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>".html_safe : ""),
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
end
# Return custom field tag with its label tag
def custom_field_tag_with_label(name, custom_value, options={})
custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value)
def custom_field_tag_with_label(name, custom_value)
custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
end
def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil)
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
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)
case field_format.try(:edit_as)
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)
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"
select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
[l(:general_text_yes), '1'],
[l(:general_text_no), '0']]), tag_options)
[l(:general_text_no), '0']]), :id => field_id)
when "list"
options = []
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?))
select_tag(field_name, options_for_select(options),
:id => field_id, :multiple => custom_field.multiple?)
else
text_field_tag(field_name, '', tag_options)
text_field_tag(field_name, '', :id => field_id)
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -24,19 +24,19 @@ module GanttHelper
when :in
if gantt.zoom < 4
link_to_content_update l(:text_zoom_in),
params.merge(gantt.params.merge(:zoom => (gantt.zoom + 1))),
params.merge(gantt.params.merge(:zoom => (gantt.zoom+1))),
:class => 'icon icon-zoom-in'
else
content_tag(:span, l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe
content_tag('span', l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe
end
when :out
if gantt.zoom > 1
link_to_content_update l(:text_zoom_out),
params.merge(gantt.params.merge(:zoom => (gantt.zoom - 1))),
params.merge(gantt.params.merge(:zoom => (gantt.zoom-1))),
:class => 'icon icon-zoom-out'
else
content_tag(:span, l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe
content_tag('span', l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe
end
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,25 +18,19 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module GroupsHelper
# Options for the new membership projects combo-box
def options_for_membership_project_select(user, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
options << project_tree_options_for_select(projects) do |p|
{:disabled => (user.projects.include?(p))}
end
options
end
def group_settings_tabs
tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general},
{:name => 'users', :partial => 'groups/users', :label => :label_user_plural},
{:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural}
]
end
def render_principals_for_new_group_users(group)
scope = User.active.sorted.not_in_group(group).like(params[:q])
principal_count = scope.count
principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page']
principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all
s = content_tag('div', principals_check_box_tags('user_ids[]', principals), :id => 'principals')
links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
link_to text, autocomplete_for_user_group_path(group, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
}
s + content_tag('p', links, :class => 'pagination')
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -65,7 +65,7 @@ module IssuesHelper
s = ''
ancestors = issue.root? ? [] : issue.ancestors.visible.all
ancestors.each do |ancestor|
s << '<div>' + content_tag('p', link_to_issue(ancestor, :project => (issue.project_id != ancestor.project_id)))
s << '<div>' + content_tag('p', link_to_issue(ancestor))
end
s << '<div>'
subject = h(issue.subject)
@@ -80,71 +80,18 @@ module IssuesHelper
def render_descendants_tree(issue)
s = '<form><table class="list issues">'
issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
css = "issue issue-#{child.id} hascontextmenu"
css << " idnt idnt-#{level}" if level > 0
s << content_tag('tr',
content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
content_tag('td', link_to_issue(child, :truncate => 60, :project => (issue.project_id != child.project_id)), :class => 'subject') +
content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') +
content_tag('td', h(child.status)) +
content_tag('td', link_to_user(child.assigned_to)) +
content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
:class => css)
:class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
end
s << '</table></form>'
s.html_safe
end
# Returns a link for adding a new subtask to the given issue
def link_to_new_subtask(issue)
attrs = {
:tracker_id => issue.tracker,
:parent_issue_id => issue
}
link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs))
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)
return if issue.custom_field_values.empty?
ordered_values = []
@@ -184,7 +131,7 @@ module IssuesHelper
def sidebar_queries
unless @sidebar_queries
@sidebar_queries = IssueQuery.visible.all(
@sidebar_queries = Query.visible.all(
:order => "#{Query.table_name}.name ASC",
# Project specific queries and global queries
:conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
@@ -214,28 +161,6 @@ module IssuesHelper
out
end
def email_issue_attributes(issue)
items = []
%w(author status priority assigned_to category fixed_version).each do |attribute|
unless issue.disabled_core_fields.include?(attribute+"_id")
items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
end
end
issue.custom_field_values.each do |value|
items << "#{value.custom_field.name}: #{show_value(value)}"
end
items
end
def render_email_issue_attributes(issue, html=false)
items = email_issue_attributes(issue)
if html
content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe)
else
items.map{|s| "* #{s}"}.join("\n")
end
end
# Returns the textual representation of a journal details
# as an array of strings
def details_to_strings(details, no_html=false, options={})
@@ -323,7 +248,7 @@ module IssuesHelper
unless no_html
label = content_tag('strong', label)
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)
# Link to the attachment if it has not been removed
value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
@@ -369,16 +294,10 @@ module IssuesHelper
# Find the name of an associated record stored in the field attribute
def find_name_by_reflection(field, id)
unless id.present?
return nil
end
association = Issue.reflect_on_association(field.to_sym)
if association
record = association.class_name.constantize.find_by_id(id)
if record
record.name.force_encoding('UTF-8') if record.name.respond_to?(:force_encoding)
return record.name
end
return record.name if record
end
end
@@ -395,4 +314,41 @@ module IssuesHelper
end
end
end
def issues_to_csv(issues, project, query, options={})
decimal_separator = l(:general_csv_decimal_separator)
encoding = l(:general_csv_encoding)
columns = (options[:columns] == 'all' ? query.available_columns : query.columns)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } +
(options[:description] ? [Redmine::CodesetUtil.from_utf8(l(:field_description), encoding)] : [])
# csv lines
issues.each do |issue|
col_values = columns.collect do |column|
s = if column.is_a?(QueryCustomFieldColumn)
cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
show_value(cv)
else
value = column.value(issue)
if value.is_a?(Date)
format_date(value)
elsif value.is_a?(Time)
format_time(value)
elsif value.is_a?(Float)
("%.2f" % value).gsub('.', decimal_separator)
else
value
end
end
s.to_s
end
csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) } +
(options[:description] ? [Redmine::CodesetUtil.from_utf8(issue.description, encoding)] : [])
end
end
export
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -23,13 +23,11 @@ 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)))
links = []
if !journal.notes.blank?
links << link_to(image_tag('comment.png'),
{:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal},
:remote => true,
:method => 'post',
:title => l(:button_quote)) if options[:reply_links]
links << link_to_remote(image_tag('comment.png'),
{ :url => {:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal} },
:title => l(:button_quote)) if options[:reply_links]
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
end
content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty?
@@ -40,7 +38,7 @@ module JournalsHelper
end
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)
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,18 +18,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MembersHelper
def render_principals_for_new_members(project)
scope = Principal.active.sorted.not_member_of(project).like(params[:q])
principal_count = scope.count
principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page']
principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all
s = content_tag('div', principals_check_box_tags('membership[user_ids][]', principals), :id => 'principals')
links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
}
s + content_tag('p', links, :class => 'pagination')
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,54 +18,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MyHelper
def calendar_items(startdt, enddt)
Issue.visible.
where(:project_id => User.current.projects.map(&:id)).
where("(start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)", startdt, enddt, startdt, enddt).
includes(:project, :tracker, :priority, :assigned_to).
all
end
def documents_items
Document.visible.order("#{Document.table_name}.created_on DESC").limit(10).all
end
def issuesassignedtome_items
Issue.visible.open.
where(:assigned_to_id => ([User.current.id] + User.current.group_ids)).
limit(10).
includes(:status, :project, :tracker, :priority).
order("#{IssuePriority.table_name}.position DESC, #{Issue.table_name}.updated_on DESC").
all
end
def issuesreportedbyme_items
Issue.visible.
where(:author_id => User.current.id).
limit(10).
includes(:status, :project, :tracker).
order("#{Issue.table_name}.updated_on DESC").
all
end
def issueswatched_items
Issue.visible.on_active_project.watched_by(User.current.id).recently_updated.limit(10).all
end
def news_items
News.visible.
where(:project_id => User.current.projects.map(&:id)).
limit(10).
includes(:project, :author).
order("#{News.table_name}.created_on DESC").
all
end
def timelog_items
TimeEntry.
where("#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", User.current.id, Date.today - 6, Date.today).
includes(:activity, :project, {:issue => [:tracker, :status]}).
order("#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC").
all
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -51,15 +51,38 @@ module ProjectsHelper
content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id')
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)
render_project_nested_lists(projects) do |project|
s = link_to_project(project, {}, :class => "#{project.css_classes} #{User.current.member_of?(project) ? 'my-project' : nil}")
if project.description.present?
s << content_tag('div', textilizable(project.short_description, :project => project), :class => 'wiki description')
s = ''
if projects.any?
ancestors = []
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
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
s << ("</li></ul>\n" * ancestors.size)
@project = original_project
end
s.html_safe
end
# Returns a set of options for a select field, grouped by project.
@@ -68,6 +91,10 @@ module ProjectsHelper
versions.each do |version|
grouped[version.project.name] << [version.name, version.id]
end
# Add in the selected
if selected && !versions.include?(selected)
grouped[selected.project.name] << [selected.name, selected.id]
end
if grouped.keys.size > 1
grouped_options_for_select(grouped, selected && selected.id)

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,60 +18,9 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module QueriesHelper
def filters_options_for_select(query)
options_for_select(filters_options(query))
end
def filters_options(query)
options = [[]]
options += query.available_filters.map do |field, field_options|
[field_options[:name], field]
end
end
def query_filters_hidden_tags(query)
tags = ''.html_safe
query.filters.each do |field, options|
tags << hidden_field_tag("f[]", field, :id => nil)
tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
options[:values].each do |value|
tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
end
end
tags
end
def query_columns_hidden_tags(query)
tags = ''.html_safe
query.columns.each do |column|
tags << hidden_field_tag("c[]", column.name, :id => nil)
end
tags
end
def query_hidden_tags(query)
query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
end
def available_block_columns_tags(query)
tags = ''.html_safe
query.available_block_columns.each do |column|
tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column)) + " #{column.caption}", :class => 'inline')
end
tags
end
def query_available_inline_columns_options(query)
(query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
end
def query_selected_inline_columns_options(query)
(query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
end
def render_query_columns_selection(query, options={})
tag_name = (options[:name] || 'c') + '[]'
render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
def operators_for_select(filter_type)
Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
end
def column_header(column)
@@ -83,7 +32,7 @@ module QueriesHelper
def column_content(column, issue)
value = column.value(issue)
if value.is_a?(Array)
value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
value.collect {|v| column_value(column, issue, v)}.compact.sort.join(', ').html_safe
else
column_value(column, issue, value)
end
@@ -94,8 +43,6 @@ module QueriesHelper
when 'String'
if column.name == :subject
link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
elsif column.name == :description
issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
else
h(value)
end
@@ -103,16 +50,14 @@ module QueriesHelper
format_time(value)
when 'Date'
format_date(value)
when 'Fixnum'
if column.name == :id
link_to value, issue_path(issue)
elsif column.name == :done_ratio
when 'Fixnum', 'Float'
if column.name == :done_ratio
progress_bar(value, :width => '80px')
elsif column.name == :spent_hours
sprintf "%.2f", value
else
value.to_s
h(value.to_s)
end
when 'Float'
sprintf "%.2f", value
when 'User'
link_to_user value
when 'Project'
@@ -124,82 +69,32 @@ module QueriesHelper
when 'FalseClass'
l(:general_text_No)
when 'Issue'
value.visible? ? link_to_issue(value) : "##{value.id}"
when 'IssueRelation'
other = value.other_issue(issue)
content_tag('span',
(l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe,
:class => value.css_classes_for(issue))
link_to_issue(value, :subject => false)
else
h(value)
end
end
def csv_content(column, issue)
value = column.value(issue)
if value.is_a?(Array)
value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
else
csv_value(column, issue, value)
end
end
def csv_value(column, issue, value)
case value.class.name
when 'Time'
format_time(value)
when 'Date'
format_date(value)
when 'Float'
sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
when 'IssueRelation'
other = value.other_issue(issue)
l(value.label_for(issue)) + " ##{other.id}"
else
value.to_s
end
end
def query_to_csv(items, query, options={})
encoding = l(:general_csv_encoding)
columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
query.available_block_columns.each do |column|
if options[column.name].present?
columns << column
end
end
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
# csv lines
items.each do |item|
csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) }
end
end
export
end
# Retrieve query from session or build a new query
def retrieve_query
if !params[:query_id].blank?
cond = "project_id IS NULL"
cond << " OR project_id = #{@project.id}" if @project
@query = IssueQuery.find(params[:query_id], :conditions => cond)
@query = Query.find(params[:query_id], :conditions => cond)
raise ::Unauthorized unless @query.visible?
@query.project = @project
session[:query] = {:id => @query.id, :project_id => @query.project_id}
sort_clear
elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
# Give it a name, required to be valid
@query = IssueQuery.new(:name => "_")
@query = Query.new(:name => "_")
@query.project = @project
@query.build_from_params(params)
build_query_from_params
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
else
# retrieve from session
@query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
@query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
@query ||= Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
@query.project = @project
end
end
@@ -207,10 +102,10 @@ module QueriesHelper
def retrieve_query_from_session
if session[:query]
if session[:query][:id]
@query = IssueQuery.find_by_id(session[:query][:id])
@query = Query.find_by_id(session[:query][:id])
return unless @query
else
@query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
@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]
@@ -220,4 +115,17 @@ module QueriesHelper
@query
end
end
def build_query_from_params
if params[:fields] || params[:f]
@query.filters = {}
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
else
@query.available_filters.keys.each do |field|
@query.add_short_filter(field, params[field]) if params[field]
end
end
@query.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
@query.column_names = params[:c] || (params[:query] && params[:query][:column_names])
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -24,7 +24,7 @@ module ReportsHelper
data.each { |row|
match = 1
criteria.each { |k, v|
match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && (v == 0 ? ['f', false] : ['t', true]).include?(row[k]))
match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && row[k] == (v == 0 ? "f" : "t"))
} unless criteria.nil?
a = a + row["total"].to_i if match == 1
} unless data.nil?
@@ -35,9 +35,4 @@ module ReportsHelper
a = aggregate data, criteria
a > 0 ? link_to(h(a), *args) : '-'
end
def aggregate_path(project, field, row, options={})
parameters = {:set_filter => 1, :subproject_id => '!*', field => row.id}.merge(options)
project_issues_path(row.is_a?(Project) ? row : project, parameters)
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -17,6 +17,9 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv'
require 'redmine/codeset_util'
module RepositoriesHelper
def format_revision(revision)
if revision.respond_to? :format_identifier
@@ -43,17 +46,17 @@ module RepositoriesHelper
end
def render_changeset_changes
changes = @changeset.filechanges.limit(1000).reorder('path').all.collect do |change|
changes = @changeset.changes.find(:all, :limit => 1000, :order => 'path').collect do |change|
case change.action
when 'A'
# Detects moved/copied files
if !change.from_path.blank?
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
change
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
change
end
@@ -138,7 +141,12 @@ module RepositoriesHelper
select_tag('repository_scm',
options_for_select(scm_options, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?),
:data => {:remote => true, :method => 'get'})
:onchange => remote_function(
:url => new_project_repository_path(@project),
:method => :get,
:update => 'content',
:with => "Form.serialize(this.form)")
)
end
def with_leading_slash(path)
@@ -151,7 +159,7 @@ module RepositoriesHelper
def subversion_field_tags(form, repository)
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 +
'(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
content_tag('p', form.text_field(:login, :size => 30)) +
@@ -166,7 +174,7 @@ module RepositoriesHelper
content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url'))) +
:disabled => (repository && !repository.new_record?))) +
content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true))
@@ -176,7 +184,7 @@ module RepositoriesHelper
content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')
:disabled => (repository && !repository.root_url.blank?)
) +
'<br />'.html_safe + l(:text_mercurial_repository_note)) +
content_tag('p', form.select(
@@ -190,7 +198,7 @@ module RepositoriesHelper
content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')
:disabled => (repository && !repository.root_url.blank?)
) +
'<br />'.html_safe +
l(:text_git_repository_note)) +
@@ -210,12 +218,12 @@ module RepositoriesHelper
:root_url,
:label => l(:field_cvsroot),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('root_url'))) +
:disabled => !repository.new_record?)) +
content_tag('p', form.text_field(
:url,
:label => l(:field_cvs_module),
:size => 30, :required => true,
:disabled => !repository.safe_attribute?('url'))) +
:disabled => !repository.new_record?)) +
content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true)) +
@@ -230,7 +238,7 @@ module RepositoriesHelper
content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url'))) +
:disabled => (repository && !repository.new_record?))) +
content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true))
@@ -240,7 +248,7 @@ module RepositoriesHelper
content_tag('p', form.text_field(
:url, :label => l(:field_root_directory),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url'))) +
:disabled => (repository && !repository.root_url.blank?))) +
content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
@@ -250,13 +258,16 @@ module RepositoriesHelper
def index_commits(commits, heads)
return nil if commits.nil? or commits.first.parents.nil?
refs_map = {}
heads.each do |head|
refs_map[head.scmid] ||= []
refs_map[head.scmid] << head
end
commits_by_scmid = {}
commits.reverse.each_with_index do |commit, commit_index|
commits_by_scmid[commit.scmid] = {
:parent_scmids => commit.parents.collect { |parent| parent.scmid },
:rdmid => commit_index,
@@ -265,28 +276,38 @@ module RepositoriesHelper
:href => block_given? ? yield(commit.scmid) : commit.scmid
}
end
heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
space = nil
heads.each do |head|
if commits_by_scmid.include? head.scmid
space = index_head((space || -1) + 1, head, commits_by_scmid)
end
end
# when no head matched anything use first commit
space ||= index_head(0, commits.first, commits_by_scmid)
return commits_by_scmid, space
end
def index_head(space, commit, commits_by_scmid)
stack = [[space, commits_by_scmid[commit.scmid]]]
max_space = space
until stack.empty?
space, commit = stack.pop
commit[:space] = space if commit[:space].nil?
space -= 1
commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
parent_commit = commits_by_scmid[parent_scmid]
if parent_commit and parent_commit[:space].nil?
stack.unshift [space += 1, parent_commit]
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -44,7 +44,7 @@ module SettingsHelper
setting_values = Setting.send(setting)
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 +
choices.collect do |choice|
text, value = (choice.is_a?(Array) ? choice : [choice, choice])
@@ -53,10 +53,9 @@ module SettingsHelper
check_box_tag(
"settings[#{setting}][]",
value,
setting_values.include?(value),
:id => nil
Setting.send(setting).include?(value)
) + text.to_s,
:class => (options[:inline] ? 'inline' : 'block')
:class => 'block'
)
end.join.html_safe
end
@@ -73,13 +72,13 @@ module SettingsHelper
def setting_check_box(setting, options={})
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
end
def setting_label(setting, options={})
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
# Renders a notification field for a Redmine::Notifiable option
@@ -87,20 +86,8 @@ module SettingsHelper
return content_tag(:label,
check_box_tag('settings[notified_events][]',
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,
:class => notifiable.parent.present? ? "parent" : '').html_safe
end
def cross_project_subtasks_options
options = [
[:label_disabled, ''],
[:label_cross_project_system, 'system'],
[:label_cross_project_tree, 'tree'],
[:label_cross_project_hierarchy, 'hierarchy'],
[:label_cross_project_descendants, 'descendants']
]
options.map {|label, value| [l(label), value.to_s]}
end
end

View File

@@ -80,20 +80,15 @@ module SortHelper
@criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',')
end
# Returns an array of SQL fragments used to sort the list
def to_sql
sql = @criteria.collect do |k,o|
if s = @available_criteria[k]
(o ? s.to_a : s.to_a.collect {|c| append_desc(c)})
(o ? s.to_a : s.to_a.collect {|c| append_desc(c)}).join(', ')
end
end.flatten.compact
end.compact.join(', ')
sql.blank? ? nil : sql
end
def to_a
@criteria.dup
end
def add!(key, asc)
@criteria.delete_if {|k,o| k == key}
@criteria = [[key, asc]] + @criteria
@@ -187,10 +182,6 @@ module SortHelper
@sort_criteria.to_sql
end
def sort_criteria
@sort_criteria
end
# Returns a link which sorts by the named column.
#
# - column is the name of an attribute in the sorted record collection.

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -56,10 +56,10 @@ module TimelogHelper
end
def select_hours(data, criteria, value)
if value.to_s.empty?
data.select {|row| row[criteria].blank? }
if value.to_s.empty?
data.select {|row| row[criteria].blank? }
else
data.select {|row| row[criteria].to_s == value.to_s}
data.select {|row| row[criteria].to_s == value.to_s}
end
end
@@ -77,7 +77,6 @@ module TimelogHelper
[l(:label_yesterday), 'yesterday'],
[l(:label_this_week), 'current_week'],
[l(:label_last_week), 'last_week'],
[l(:label_last_n_weeks, 2), 'last_2_weeks'],
[l(:label_last_n_days, 7), '7_days'],
[l(:label_this_month), 'current_month'],
[l(:label_last_month), 'last_month'],
@@ -86,6 +85,49 @@ module TimelogHelper
value)
end
def entries_to_csv(entries)
decimal_separator = l(:general_csv_decimal_separator)
custom_fields = TimeEntryCustomField.find(:all)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
headers = [l(:field_spent_on),
l(:field_user),
l(:field_activity),
l(:field_project),
l(:field_issue),
l(:field_tracker),
l(:field_subject),
l(:field_hours),
l(:field_comments)
]
# Export custom fields
headers += custom_fields.collect(&:name)
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8(
c.to_s,
l(:general_csv_encoding) ) }
# csv lines
entries.each do |entry|
fields = [format_date(entry.spent_on),
entry.user,
entry.activity,
entry.project,
(entry.issue ? entry.issue.id : nil),
(entry.issue ? entry.issue.tracker : nil),
(entry.issue ? entry.issue.subject : nil),
entry.hours.to_s.gsub('.', decimal_separator),
entry.comments
]
fields += custom_fields.collect {|f| show_value(entry.custom_field_values.detect {|v| v.custom_field_id == f.id}) }
csv << fields.collect {|c| Redmine::CodesetUtil.from_utf8(
c.to_s,
l(:general_csv_encoding) ) }
end
end
export
end
def format_criteria_value(criteria_options, value)
if value.blank?
"[#{l(:label_none)}]"
@@ -107,14 +149,14 @@ module TimelogHelper
# Column headers
headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) }
headers += report.periods
headers << l(:label_total_time)
headers << l(:label_total)
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8(
c.to_s,
l(:general_csv_encoding) ) }
# Content
report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours)
# Total row
str_total = Redmine::CodesetUtil.from_utf8(l(:label_total_time), l(:general_csv_encoding))
str_total = Redmine::CodesetUtil.from_utf8(l(:label_total), l(:general_csv_encoding))
row = [ str_total ] + [''] * (report.criteria.size - 1)
total = 0
report.periods.each do |period|

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -26,6 +26,15 @@ module UsersHelper
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s)
end
# Options for the new membership projects combo-box
def options_for_membership_project_select(user, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
options << project_tree_options_for_select(projects) do |p|
{:disabled => (user.projects.include?(p))}
end
options
end
def user_mail_notification_options(user)
user.valid_notification_options.collect {|o| [l(o.last), o.first]}
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -19,18 +19,10 @@
module VersionsHelper
def version_anchor(version)
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)
STATUS_BY_CRITERIAS = %w(category tracker status priority author assigned_to)
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]}
begin
@@ -44,8 +36,7 @@ module VersionsHelper
rescue ActiveRecord::RecordNotFound
# When grouping by an association, Rails throws this exception if there's no result (bug)
end
# Sort with nil keys in last position
counts = h.keys.sort {|a,b| a.nil? ? 1 : (b.nil? ? -1 : a <=> b)}.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
counts = h.keys.compact.sort.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
max = counts.collect {|c| c[:total]}.max
render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max}

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,63 +20,55 @@
module WatchersHelper
def watcher_tag(object, user, options={})
ActiveSupport::Deprecation.warn "#watcher_tag is deprecated and will be removed in Redmine 3.0. Use #watcher_link instead."
watcher_link(object, user)
content_tag("span", watcher_link(object, user), :class => watcher_css(object))
end
def watcher_link(objects, user)
return '' unless user && user.logged?
objects = Array.wrap(objects)
def watcher_link(object, user)
return '' unless user && user.logged? && object.respond_to?('watched_by?')
watched = object.watched_by?(user)
url = {:controller => 'watchers',
:action => (watched ? 'unwatch' : 'watch'),
:object_type => object.class.to_s.underscore,
:object_id => object.id}
link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
{:url => url},
:href => url_for(url),
:class => (watched ? 'icon icon-fav' : 'icon icon-fav-off'))
watched = objects.any? {|object| object.watched_by?(user)}
css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ')
text = watched ? l(:button_unwatch) : l(:button_watch)
url = watch_path(
:object_type => objects.first.class.to_s.underscore,
:object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort)
)
method = watched ? 'delete' : 'post'
link_to text, url, :remote => true, :method => method, :class => css
end
# Returns the css class used to identify watch links for a given +object+
def watcher_css(objects)
objects = Array.wrap(objects)
id = (objects.size == 1 ? objects.first.id : 'bulk')
"#{objects.first.class.to_s.underscore}-#{id}-watcher"
def watcher_css(object)
"#{object.class.to_s.underscore}-#{object.id}-watcher"
end
# Returns a comma separated list of users watching the given object
def watchers_list(object)
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|
s = ''.html_safe
s << avatar(user, :size => "16").to_s
s << link_to_user(user, :class => 'user')
s = avatar(user, :size => "16").to_s + link_to_user(user, :class => 'user').to_s
if remove_allowed
url = {:controller => 'watchers',
:action => 'destroy',
:object_type => object.class.to_s.underscore,
:object_id => object.id,
:user_id => user}
s << ' '
s << link_to(image_tag('delete.png'), url,
:remote => true, :method => 'delete', :class => "delete")
s += ' ' + link_to_remote(image_tag('delete.png'),
{:url => url},
:href => url_for(url),
:style => "vertical-align: middle",
:class => "delete")
end
content << content_tag('li', s, :class => "user-#{user.id}")
content_tag :li, s.html_safe
end
content.present? ? content_tag('ul', content, :class => 'watchers') : content
(lis.empty? ? "" : "<ul>#{ lis.join("\n") }</ul>").html_safe
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
content_tag 'label', "#{tag} #{h(user)}", :id => "issue_watcher_user_ids_#{user.id}", :class => "floating"
end.join
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -21,14 +21,14 @@ module WikiHelper
def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
pages = pages.group_by(&:parent) unless pages.is_a?(Hash)
s = ''.html_safe
s = ''
if pages.has_key?(parent)
pages[parent].each do |page|
attrs = "value='#{page.id}'"
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)
end
end
@@ -37,7 +37,7 @@ module WikiHelper
def wiki_page_breadcrumb(page)
breadcrumb(page.ancestors.reverse.collect {|parent|
link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project, :version => nil})
link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project})
})
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,15 +18,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# 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
@@ -16,7 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require "digest/md5"
require "fileutils"
class Attachment < ActiveRecord::Base
belongs_to :container, :polymorphic => true
@@ -25,7 +24,7 @@ class Attachment < ActiveRecord::Base
validates_presence_of :filename, :author
validates_length_of :filename, :maximum => 255
validates_length_of :disk_filename, :maximum => 255
validates_length_of :description, :maximum => 255
validates_length_of :description, :maximum => 255, :allow_blank => true
validate :validate_max_file_size
acts_as_event :title => :filename,
@@ -46,14 +45,20 @@ class Attachment < ActiveRecord::Base
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
cattr_accessor :storage_path
@@storage_path = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, "files")
cattr_accessor :thumbnails_storage_path
@@thumbnails_storage_path = File.join(Rails.root, "tmp", "thumbnails")
@@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{Rails.root}/files"
before_save :files_to_final_location
after_destroy :delete_from_disk
def container_with_blank_type_check
if container_type.blank?
nil
else
container_without_blank_type_check
end
end
alias_method_chain :container, :blank_type_check unless method_defined?(:container_without_blank_type_check)
# Returns an unsaved copy of the attachment
def copy(attributes=nil)
copy = self.class.new
@@ -93,6 +98,9 @@ class Attachment < ActiveRecord::Base
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
@@ -100,13 +108,7 @@ class Attachment < ActiveRecord::Base
# and computes its MD5 hash
def files_to_final_location
if @temp_file && (@temp_file.size > 0)
self.disk_directory = target_directory
self.disk_filename = Attachment.disk_filename(filename, disk_directory)
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") if logger
path = File.dirname(diskfile)
unless File.directory?(path)
FileUtils.mkdir_p(path)
end
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
md5 = Digest::MD5.new
File.open(diskfile, "wb") do |f|
if @temp_file.respond_to?(:read)
@@ -131,22 +133,14 @@ class Attachment < ActiveRecord::Base
# Deletes the file from the file system if it's not referenced by other attachments
def delete_from_disk
if Attachment.where("disk_filename = ? AND id <> ?", disk_filename, id).empty?
if Attachment.first(:conditions => ["disk_filename = ? AND id <> ?", disk_filename, id]).nil?
delete_from_disk!
end
end
# Returns file's location on disk
def diskfile
File.join(self.class.storage_path, disk_directory.to_s, disk_filename.to_s)
end
def title
title = filename.to_s
if description.present?
title << " (#{description})"
end
title
"#{@@storage_path}/#{self.disk_filename}"
end
def increment_download
@@ -158,59 +152,15 @@ class Attachment < ActiveRecord::Base
end
def visible?(user=User.current)
if container_id
container && container.attachments_visible?(user)
else
author == user
end
container && container.attachments_visible?(user)
end
def deletable?(user=User.current)
if container_id
container && container.attachments_deletable?(user)
else
author == user
end
container && container.attachments_deletable?(user)
end
def image?
!!(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
self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i
end
def is_text?
@@ -235,7 +185,7 @@ class Attachment < ActiveRecord::Base
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
attachment = Attachment.first(:conditions => {:id => attachment_id, :digest => attachment_digest})
if attachment && attachment.container.nil?
attachment
end
@@ -260,27 +210,8 @@ class Attachment < ActiveRecord::Base
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
# Moves an existing attachment to its target directory
def move_to_target_directory!
if !new_record? & readable?
src = diskfile
self.disk_directory = target_directory
dest = diskfile
if src != dest && FileUtils.mkdir_p(File.dirname(dest)) && FileUtils.mv(src, dest)
update_column :disk_directory, disk_directory
end
end
end
# Moves existing attachments that are stored at the root of the files
# directory (ie. created before Redmine 2.3) to their target subdirectories
def self.move_from_root_to_target_directory
Attachment.where("disk_directory IS NULL OR disk_directory = ''").find_each do |attachment|
attachment.move_to_target_directory!
end
attachments = Attachment.all(:conditions => ["created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age])
attachments.each(&:destroy)
end
private
@@ -300,15 +231,8 @@ class Attachment < ActiveRecord::Base
@filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_')
end
# Returns the subdirectory in which the attachment will be saved
def target_directory
time = created_on || DateTime.now
time.strftime("%Y/%m")
end
# Returns an ASCII or hashed filename that do not
# exists yet in the given subdirectory
def self.disk_filename(filename, directory=nil)
# Returns an ASCII or hashed filename
def self.disk_filename(filename)
timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
ascii = ''
if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
@@ -318,7 +242,7 @@ class Attachment < ActiveRecord::Base
# keep the extension if any
ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
end
while File.exist?(File.join(storage_path, directory.to_s, "#{timestamp}_#{ascii}"))
while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
timestamp.succ!
end
"#{timestamp}_#{ascii}"

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,7 +18,6 @@
# 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
include Redmine::SubclassFactory
@@ -48,24 +47,6 @@ class AuthSource < ActiveRecord::Base
write_ciphered_attribute(:account_password, arg)
end
def searchable?
false
end
def self.search(q)
results = []
AuthSource.all.each do |source|
begin
if source.searchable?
results += source.search(q)
end
rescue AuthSourceException => e
logger.error "Error while searching users in #{source.name}: #{e.message}"
end
end
results
end
def allow_password_changes?
self.class.allow_password_changes?
end
@@ -77,7 +58,7 @@ class AuthSource < ActiveRecord::Base
# Try to authenticate a user not yet registered against available sources
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
logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
attrs = source.authenticate(login, password)

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -15,9 +15,9 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv'
require 'net/ldap'
require 'net/ldap/dn'
require 'timeout'
class AuthSourceLdap < AuthSource
validates_presence_of :host, :port, :attr_login
@@ -25,11 +25,18 @@ class AuthSourceLdap < AuthSource
validates_length_of :account, :account_password, :base_dn, :filter, :maximum => 255, :allow_blank => 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 :timeout, :only_integer => true, :allow_blank => true
validate :validate_filter
before_validation :strip_ldap_attributes
def self.human_attribute_name(attribute_key_name, *args)
attr_name = attribute_key_name.to_s
if attr_name == "filter"
attr_name = "ldap_filter"
end
super(attr_name, *args)
end
def initialize(attributes=nil, *args)
super
self.port = 389 if self.port == 0
@@ -37,70 +44,30 @@ class AuthSourceLdap < AuthSource
def authenticate(login, password)
return nil if login.blank? || password.blank?
attrs = get_user_dn(login, password)
with_timeout do
attrs = get_user_dn(login, password)
if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
return attrs.except(:dn)
end
if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
return attrs.except(:dn)
end
rescue Net::LDAP::LdapError => e
rescue Net::LDAP::LdapError => e
raise AuthSourceException.new(e.message)
end
# test the connection to the LDAP
def test_connection
with_timeout do
ldap_con = initialize_ldap_con(self.account, self.account_password)
ldap_con.open { }
end
rescue Net::LDAP::LdapError => e
raise AuthSourceException.new(e.message)
ldap_con = initialize_ldap_con(self.account, self.account_password)
ldap_con.open { }
rescue Net::LDAP::LdapError => e
raise "LdapError: " + e.message
end
def auth_method_name
"LDAP"
end
# Returns true if this source can be searched for users
def searchable?
!account.to_s.include?("$login") && %w(login firstname lastname mail).all? {|a| send("attr_#{a}?")}
end
# Searches the source for users and returns an array of results
def search(q)
q = q.to_s.strip
return [] unless searchable? && q.present?
results = []
search_filter = base_filter & Net::LDAP::Filter.begins(self.attr_login, q)
ldap_con = initialize_ldap_con(self.account, self.account_password)
ldap_con.search(:base => self.base_dn,
:filter => search_filter,
:attributes => ['dn', self.attr_login, self.attr_firstname, self.attr_lastname, self.attr_mail],
:size => 10) do |entry|
attrs = get_user_attributes_from_ldap_entry(entry)
attrs[:login] = AuthSourceLdap.get_attr(entry, self.attr_login)
results << attrs
end
results
rescue Net::LDAP::LdapError => e
raise AuthSourceException.new(e.message)
end
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)
@@ -109,14 +76,6 @@ class AuthSourceLdap < AuthSource
nil
end
def base_filter
filter = Net::LDAP::Filter.eq("objectClass", "*")
if f = ldap_filter
filter = filter & f
end
filter
end
def validate_filter
if filter.present? && ldap_filter.nil?
errors.add(:filter, :invalid)
@@ -173,8 +132,14 @@ class AuthSourceLdap < AuthSource
else
ldap_con = initialize_ldap_con(self.account, self.account_password)
end
login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
attrs = {}
search_filter = base_filter & Net::LDAP::Filter.eq(self.attr_login, login)
search_filter = object_filter & login_filter
if f = ldap_filter
search_filter = search_filter & f
end
ldap_con.search( :base => self.base_dn,
:filter => search_filter,

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -21,38 +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 :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC"
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
acts_as_tree :dependent => :nullify
acts_as_list :scope => '(project_id = #{project_id} AND parent_id #{parent_id ? "= #{parent_id}" : "IS NULL"})'
acts_as_list :scope => :project_id
acts_as_watchable
validates_presence_of :name, :description
validates_length_of :name, :maximum => 30
validates_length_of :description, :maximum => 255
validate :validate_board
scope :visible, lambda {|*args|
includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args))
}
named_scope :visible, lambda {|*args| { :include => :project,
: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)
!user.nil? && user.allowed_to?(:view_messages, project)
end
def reload(*args)
@valid_parents = nil
super
end
def to_s
name
end
def valid_parents
@valid_parents ||= project.boards - self_and_descendants
end
def reset_counters!
self.class.reset_counters!(id)
end
@@ -65,26 +53,4 @@ class Board < ActiveRecord::Base
" last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})",
["id = ?", board_id])
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -15,10 +15,12 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv'
class Changeset < ActiveRecord::Base
belongs_to :repository
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 :parents,
:class_name => "Changeset",
@@ -47,9 +49,8 @@ class Changeset < ActiveRecord::Base
validates_uniqueness_of :revision, :scope => :repository_id
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
scope :visible, lambda {|*args|
includes(:repository => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args))
}
named_scope :visible, lambda {|*args| { :include => {:repository => :project},
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } }
after_create :scan_for_issues
before_create :before_create_cs
@@ -160,7 +161,7 @@ class Changeset < ActiveRecord::Base
tag = "#{repository.identifier}|#{tag}"
end
if ref_project && project && ref_project != project
tag = "#{project.identifier}:#{tag}"
tag = "#{project.identifier}:#{tag}"
end
tag
end
@@ -174,12 +175,18 @@ class Changeset < ActiveRecord::Base
# Returns the previous changeset
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
# Returns the next changeset
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
# Creates a new Change from it's common parameters

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,7 +18,7 @@
class CommentObserver < ActiveRecord::Observer
def after_create(comment)
if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added')
Mailer.news_comment_added(comment).deliver
Mailer.deliver_news_comment_added(comment)
end
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# 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
@@ -29,36 +29,10 @@ class CustomField < ActiveRecord::Base
validate :validate_custom_field
before_validation :set_searchable
after_save :handle_multiplicity_change
scope :sorted, lambda { order("#{table_name}.position ASC") }
CUSTOM_FIELDS_TABS = [
{:name => 'IssueCustomField', :partial => 'custom_fields/index',
:label => :label_issue_plural},
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
:label => :label_spent_time},
{:name => 'ProjectCustomField', :partial => 'custom_fields/index',
:label => :label_project_plural},
{:name => 'VersionCustomField', :partial => 'custom_fields/index',
:label => :label_version_plural},
{:name => 'UserCustomField', :partial => 'custom_fields/index',
:label => :label_user_plural},
{:name => 'GroupCustomField', :partial => 'custom_fields/index',
:label => :label_group_plural},
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
:label => TimeEntryActivity::OptionName},
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
:label => IssuePriority::OptionName},
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
:label => DocumentCategory::OptionName}
]
CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]}
def field_format=(arg)
# cannot change format of a saved custom field
super if new_record?
def initialize(attributes=nil, *args)
super
self.possible_values ||= []
end
def set_searchable
@@ -106,7 +80,7 @@ class CustomField < ActiveRecord::Base
when 'bool'
[[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
else
possible_values || []
read_possible_values_utf8_encoded || []
end
end
@@ -117,20 +91,14 @@ class CustomField < ActiveRecord::Base
when 'bool'
['1', '0']
else
values = super()
if values.is_a?(Array)
values.each do |value|
value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
end
end
values || []
read_possible_values_utf8_encoded
end
end
# Makes possible_values accept a multiline string
def possible_values=(arg)
if arg.is_a?(Array)
super(arg.compact.collect(&:strip).select {|v| !v.blank?})
write_attribute(:possible_values, arg.compact.collect(&:strip).select {|v| !v.blank?})
else
self.possible_values = arg.to_s.split(/[\n\r]+/)
end
@@ -157,117 +125,35 @@ class CustomField < ActiveRecord::Base
casted
end
def value_from_keyword(keyword, customized)
possible_values_options = possible_values_options(customized)
if possible_values_options.present?
keyword = keyword.to_s.downcase
if v = possible_values_options.detect {|text, id| text.downcase == keyword}
if v.is_a?(Array)
v.last
else
v
end
end
else
keyword
end
end
# Returns a ORDER BY clause that can used to sort customized
# objects by their value of the custom field.
# Returns nil if the custom field can not be used for sorting.
# Returns false, if the custom field can not be used for sorting.
def order_statement
return nil if multiple?
case field_format
when 'string', 'text', 'list', 'date', 'bool'
# COALESCE is here to make sure that blank and NULL values are sorted equally
"COALESCE(#{join_alias}.value, '')"
"COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
" WHERE cv_sort.customized_type='#{self.class.customized_class.name}'" +
" AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
" AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
when 'int', 'float'
# Make the database cast values into numeric
# Postgresql will raise an error if a value can not be casted!
# CustomValue validations should ensure that it doesn't occur
"CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))"
when 'user', 'version'
value_class.fields_for_order_statement(value_join_alias)
"(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" +
" WHERE cv_sort.customized_type='#{self.class.customized_class.name}'" +
" AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
" AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)"
else
nil
end
end
# Returns a GROUP BY clause that can used to group by custom value
# Returns nil if the custom field can not be used for grouping.
def group_statement
return nil if multiple?
case field_format
when 'list', 'date', 'bool', 'int'
order_statement
when 'user', 'version'
"COALESCE(#{join_alias}.value, '')"
else
nil
end
end
def join_for_order_statement
case field_format
when 'user', 'version'
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
" AND #{join_alias}.custom_field_id = #{id}" +
" AND #{join_alias}.value <> ''" +
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
" LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
" ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id"
when 'int', 'float'
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
" AND #{join_alias}.custom_field_id = #{id}" +
" AND #{join_alias}.value <> ''" +
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
when 'string', 'text', 'list', 'date', 'bool'
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
" AND #{join_alias}.custom_field_id = #{id}" +
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
else
nil
end
end
def join_alias
"cf_#{id}"
end
def value_join_alias
join_alias + "_" + field_format
end
def <=>(field)
position <=> field.position
end
# Returns the class that values represent
def value_class
case field_format
when 'user', 'version'
field_format.classify.constantize
else
nil
end
end
def self.customized_class
self.name =~ /^(.+)CustomField$/
begin; $1.constantize; rescue nil; end
@@ -275,7 +161,7 @@ class CustomField < ActiveRecord::Base
# to move in project_custom_field
def self.for_all
where(:is_for_all => true).order('position').all
find(:all, :conditions => ["is_for_all=?", true], :order => 'position')
end
def type_name
@@ -308,10 +194,6 @@ class CustomField < ActiveRecord::Base
validate_field_value(value).empty?
end
def format_in?(*args)
args.include?(field_format)
end
protected
# Returns the error message for the given value regarding its format
@@ -337,19 +219,13 @@ class CustomField < ActiveRecord::Base
errs
end
# Removes multiple values for the custom field after setting the multiple attribute to false
# We kepp the value with the highest id for each customized object
def handle_multiplicity_change
if !new_record? && multiple_was && !multiple
ids = custom_values.
where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" +
" AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" +
" AND cve.id > #{CustomValue.table_name}.id)").
pluck(:id)
if ids.any?
custom_values.where(:id => ids).delete_all
def read_possible_values_utf8_encoded
values = read_attribute(:possible_values)
if values.is_a?(Array)
values.each do |value|
value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
end
end
values
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# 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

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