Compare commits

..

141 Commits

Author SHA1 Message Date
Jean-Philippe Lang 071bc7bc46 tagged version 2.3.3
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/2.3.3@12137 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-09-14 06:47:41 +00:00
Jean-Philippe Lang 2c44829509 Updates for 2.3.3
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12136 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-09-14 06:47:11 +00:00
Jean-Philippe Lang d259dd2dd5 Merged r12131 and r12132 (#14798).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12134 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-09-13 17:53:35 +00:00
Jean-Philippe Lang ddc016d81d Merged r12126 from trunk (#13008).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12127 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-09-10 17:40:34 +00:00
Jean-Philippe Lang e7c82e3934 Backported r12079 from trunk (#14584).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12125 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-09-10 17:08:53 +00:00
Jean-Philippe Lang e503f41f6f Reverts r12123 (#14584).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12124 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-09-10 16:52:25 +00:00
Jean-Philippe Lang cd67243de5 Merged r12079 from trunk (#14584).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12123 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-09-10 16:43:54 +00:00
Toshi MARUYAMA b0c3b7a574 Merged r12120 from trunk to 2.3-stable (#14501)
fix Russian "description_date_range_interval" translation misprint.

Contributed by Artem Kondratyev.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12122 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-09-05 11:15:41 +00:00
Toshi MARUYAMA 9a67caf248 Merged r12118 from trunk to 2.3-stable (#14697)
fix wrong Russian translation in close project message.

Contributed by Artur Gadelshin.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12119 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-08-15 14:41:26 +00:00
Toshi MARUYAMA 67ee8653bd Merged r12115 from trunk to 2.3-stable (#14682, #14686)
Portuguese translation for 2.3-stable updated by Lije Also.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12117 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-08-14 06:55:19 +00:00
Jean-Philippe Lang 36978279c3 Merged r12076 from trunk (#14607).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12078 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-08-04 10:33:03 +00:00
Toshi MARUYAMA d155392b3c Merged r12070 from trunk to 2.3-stable
not use assert_not_nil in Errors#[].

r7593 etc. replaced Rails2 Errors#on.
Rails3 Errors#[] always return array.
So, Rails3 Errors#[] is always not nil.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12071 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-30 09:38:00 +00:00
Jean-Philippe Lang dc98cec17f Merged r12060 from trunk (#14369).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12066 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-28 20:49:41 +00:00
Jean-Philippe Lang e1e006f09e Merged r12017 and r12027 from trunk (#14422).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12065 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-28 20:48:52 +00:00
Jean-Philippe Lang e8757fec2b Merged r12056 from trunk (#14447).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12064 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-28 20:46:53 +00:00
Jean-Philippe Lang 591922c365 Merged r12057 from trunk (#14415).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12063 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-28 20:46:04 +00:00
Jean-Philippe Lang 5eeca35317 Merged r12058 from trunk (#14401).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12062 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-28 20:45:16 +00:00
Jean-Philippe Lang 2de51892ee Merged r12059 from trunk (#14340).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12061 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-28 20:44:18 +00:00
Toshi MARUYAMA 3fe5e3bb6f 2.3-stable: svn propset svn:eol-style native test fixtures (#14562)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12055 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-28 12:57:16 +00:00
Toshi MARUYAMA bbf3ffe0aa Merged r12046 from trunk to 2.3-stable (#14562)
fix diff of CJK (Chinese/Japanese/Korean) is broken on Ruby 1.8.

Contributed by Jun NAITOH.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12054 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-28 12:53:51 +00:00
Jean-Philippe Lang b724eb4fec Merged r12052 from trunk (#14511).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12053 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-28 12:09:47 +00:00
Jean-Philippe Lang 493119e795 Merged r12042 from trunk (#14366).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12043 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-28 10:00:35 +00:00
Toshi MARUYAMA f1b6b4ef33 Merged r12038 from trunk to 2.3-stable (#14531, #14521)
Spanish translations for 2.3.x updated by Jorge López.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12040 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-24 11:28:35 +00:00
Toshi MARUYAMA ce002ee3df Merged r12034 from trunk to 2.3-stable (#14502, #14501)
Russian translation for 2.3-stable updated by Alex Stein.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12037 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-19 16:12:18 +00:00
Toshi MARUYAMA eb7862445c Merged r12033 from trunk to 2.3-stable.
update ruby-openid version to 2.2.3.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12036 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-19 16:12:06 +00:00
Toshi MARUYAMA b31b5328e4 Merged r12028 from trunk to 2.3-stable (#14485, #14458)
Traditional Chinese translation for 2.3-stable updated by ChunChang Lo.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12032 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-18 10:13:38 +00:00
Jean-Philippe Lang 8efcf60319 Updates for 2.3.2 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12023 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-14 14:50:07 +00:00
Jean-Philippe Lang e07bc81c5e Add more info about the ruby version (#14419).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12002 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-11 18:43:48 +00:00
Jean-Philippe Lang 5c6349a7ca Merged r11905 from trunk (#14103).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12001 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-11 18:37:47 +00:00
Jean-Philippe Lang 37fbdb1457 Merged r11906 from trunk (#14101).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@12000 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-11 18:37:00 +00:00
Jean-Philippe Lang a13acb7851 Merged r11869 from trunk (#12888).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11999 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-11 18:36:02 +00:00
Jean-Philippe Lang 01887f1e67 Merged r11915 from trunk (#14186).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11998 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-11 18:33:54 +00:00
Jean-Philippe Lang 3d9f274140 Merged r11839 from trunk (#14020).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11997 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-11 18:33:07 +00:00
Jean-Philippe Lang 7375dff6d6 Merged r11827 to r11833 from trunk (#14015).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11996 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-11 18:28:54 +00:00
Jean-Philippe Lang 9874a61ec3 Merged r11854 from trunk (#13783).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11995 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-07-11 17:51:19 +00:00
Toshi MARUYAMA b5998b59ab Merged r11982 from trunk to 2.3-stable (#14346)
fix Latvian "button_log_time" translation by Arnis Juraga.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11984 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-06-26 11:49:38 +00:00
Toshi MARUYAMA c590cded4b Merged r11980 from trunk to 2.3-stable (#9996)
replace "email.yml" to "configuration.yml" in pl.yml.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11981 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-06-15 12:55:48 +00:00
Toshi MARUYAMA 5b8af329e2 2.3-stable: pin nokogiri version < 1.6.0 (#14245)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11953 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-06-10 08:24:46 +00:00
Toshi MARUYAMA 0badc79162 Merged r11942 from trunk to 2.3-stable (#14242)
fix that project auto generation fails when projects created in the same time.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11945 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-06-08 11:39:56 +00:00
Toshi MARUYAMA 5e57cbfdc4 Merged r11932 from trunk to 2.3-stable (#14221)
translate x_hours in many languages.

Contributed by Filou Centrinov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11935 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-06-06 06:13:53 +00:00
Toshi MARUYAMA 489527ada7 Merged r11925 from trunk to 2.3-stable (#13692)
prevent coderay "warning: already initialized constant" on Ruby 1.8.7.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11929 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-06-05 04:06:47 +00:00
Toshi MARUYAMA 6ce685a63c Merged r11920 and r11921 from trunk to 2.3-stable (#14178)
pdf: restore "col_id_width" parameter of issues_to_pdf_draw_borders.

Contributed by Massimo Rossello.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11924 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-06-04 12:42:29 +00:00
Toshi MARUYAMA 8045385990 Merged r11919 from trunk to 2.3-stable (#14196)
Italian translation for 2.3-stable by Riccardo Rocca.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11922 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-06-04 11:40:41 +00:00
Toshi MARUYAMA 3a4c46089f Merged r11911 from trunk to 2.3-stable (#14182, #14180)
pt-BR translation for 2.3-stable updated by Marcela Oliveira.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11914 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-31 11:05:39 +00:00
Toshi MARUYAMA 53d45273a9 Merged r11901 from trunk to 2.3-stable (#14145)
German translation of x_hours updated by Filou Centrinov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11903 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-25 06:04:36 +00:00
Jean-Philippe Lang cc23ab9652 Merged r11850 from trunk (#14051).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11872 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-18 19:35:26 +00:00
Jean-Philippe Lang ea33a66c6b Merged r11851 from trunk (#14023).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11871 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-18 19:34:19 +00:00
Jean-Philippe Lang a8c27df9fe Merged r11843 from trunk (#13910).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11870 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-18 19:33:27 +00:00
Jean-Philippe Lang af632568e3 Merged r11760 from trunk (#13850).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11837 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-12 09:45:52 +00:00
Jean-Philippe Lang 2c408ca6a5 Merged r11761 from trunk (#13821).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11836 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-12 09:44:52 +00:00
Jean-Philippe Lang 2a6dadf787 Merged r11762 and r11763 from trunk (#13783).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11835 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-12 09:43:59 +00:00
Toshi MARUYAMA 958f1accec Merged r11798 from trunk to 2.3-stable (#14003, #14005)
Swedish Translation for 2.3-stable updated by Nicklas Holm.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11801 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-08 01:36:46 +00:00
Toshi MARUYAMA 77a2cbfa6a Merged r11791 from trunk to 2.3-stable (#13950)
remove duplicate Lithuanian "error_attachment_too_big" translation keys.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11792 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-05 10:38:05 +00:00
Toshi MARUYAMA 73b4b614ee Merged r11724 from trunk to 2.3-stable.
Add TODO comment about gantt issues sort (#7335).

Sorting by date was dropped by r4581 (#7128).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11769 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-02 22:44:54 +00:00
Jean-Philippe Lang 1a71316b37 Merged r11757 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11758 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-01 15:14:28 +00:00
Jean-Philippe Lang 5fa087a792 Merged r11592 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11756 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-01 15:12:24 +00:00
Jean-Philippe Lang 425a0fe988 Merged r11754 from trunk (#12684).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11755 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-01 13:14:21 +00:00
Jean-Philippe Lang 606762dcad Merged r11750 from trunk (#12650).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11751 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-01 12:53:10 +00:00
Jean-Philippe Lang 07dc1e34ff Merged r11748 from trunk (#13712).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11749 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-01 12:36:48 +00:00
Jean-Philippe Lang 1afe67bf74 Merged r11698 from trunk (#13541).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11747 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-01 12:17:31 +00:00
Jean-Philippe Lang f9f4591cff Merged r11680 from trunk (#13541).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11746 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-01 12:16:56 +00:00
Jean-Philippe Lang aa4fc1b58a Merged r11696 from trunk (#13618).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11745 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-05-01 12:15:49 +00:00
Toshi MARUYAMA 8c17237638 Merged r11737 and r11738 from trunk to 2.3-stable (#13823)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11739 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-04-21 09:00:27 +00:00
Toshi MARUYAMA 06ab582e5d Merged r11728, r11729, r11730, r11731, r11732 and r11733 from trunk to 2.3-stable (#13811)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11734 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-04-20 11:26:12 +00:00
Toshi MARUYAMA af66c42d9e 2.3-stable: svn propset svn:eol-style native test fixtures (#13644)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11710 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-04-05 09:59:21 +00:00
Toshi MARUYAMA 655c50849d Merged r11706 and r11707 from trunk to 2.3-stable (#13644)
fix diff error in case of line_left out of range.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11709 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-04-05 09:56:14 +00:00
Toshi MARUYAMA bd5b7428d8 Merged r11700 from trunk to 2.3-stable (#13678, #13674)
Lithuanian translation for 2.3-stable updated by Vasaris Vėjas.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11703 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-04-04 12:52:40 +00:00
Toshi MARUYAMA 4ed30a4495 Merged r11699 from trunk to 2.3-stable (#13678)
remove duplicate "text_repository_usernames_mapping" from Lithuanian translation.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11702 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-04-04 12:52:29 +00:00
Jean-Philippe Lang ca39b05420 Merged r11692 from trunk (#13586).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11697 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-04-03 17:04:40 +00:00
Etienne Massip e189641e8c Merged r11693 from trunk (#13630).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11695 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-04-02 17:50:39 +00:00
Toshi MARUYAMA 102dd4c11b 2.3-stable: svn:eol-style native jquery.ui.datepicker-pt.js (#13584)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11691 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-25 14:56:22 +00:00
Toshi MARUYAMA c1d853d957 Merged r11686 from trunk to 2.3-stable (#13584)
add missing Portuguese jQuery UI date picker.

jquery.ui.datepicker-pt.js is from
https://raw.github.com/jquery/jquery-ui/49f9b849b9c5023f13/ui/i18n/jquery.ui.datepicker-pt.js

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11690 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-25 14:51:06 +00:00
Toshi MARUYAMA 225f99377a Merged r11682 and r11685 from trunk to 2.3-stable (#13579)
fix that datepicker uses Simplified Chinese in Traditional Chinese locale.

Contributed by Chage Juan.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11689 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-25 14:50:55 +00:00
Toshi MARUYAMA d8cf4c57d3 Merged r11675 from trunk to 2.3-stable (#13552, #13551)
Dutch translations for 2.3-stable updated by Pieter Nicolai.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11679 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-22 01:04:33 +00:00
Toshi MARUYAMA 4b5fa08f66 Merged r11671, r11672 and r11674 from trunk to 2.3-stable (#13405)
Fixed commit link title escaping.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11678 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-22 01:04:22 +00:00
Toshi MARUYAMA 4380c0af73 Merged r11664 from trunk to 2.3-stable (#13531 ,#13528)
Traditional Chinese translation for 2.3-stable updated by ChunChang Lo.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11669 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-20 07:01:37 +00:00
Jean-Philippe Lang 61a32a5002 Merged r11657 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11658 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-19 19:22:59 +00:00
Jean-Philippe Lang 5745a2a2e3 Merged r11641 and r11642 from trunk (#8794).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11656 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-19 18:40:26 +00:00
Jean-Philippe Lang f98f9b9ae1 Merged r11640 from trunk (#12968).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11655 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-19 18:37:33 +00:00
Toshi MARUYAMA cfec2018e3 Merged r11648, r11649, r11650 from trunk to 2.3-stable.
upgrade Rails 3.2.13.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11652 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-19 08:21:46 +00:00
Toshi MARUYAMA 0083420829 Merged r11645 from trunk to 2.3-stable (#13514)
fix pt-BR "permission_set_notes_private" translation.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11646 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-19 02:55:35 +00:00
Toshi MARUYAMA 338d7ea91d Merged r11637 from trunk to 2.3-stable (#13354)
PDF: fix incompatible character encodings: UTF-8 and ASCII-8BIT.

Contributed by Jun NAITOH.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11639 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-16 08:04:42 +00:00
Toshi MARUYAMA 73fb7e3427 Merged r11622 from trunk to 2.3-stable (#13475)
fix pt-BR translation.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11623 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-15 01:28:24 +00:00
Toshi MARUYAMA edb7f2d2c5 Merged r11620 from trunk to 2.3-stable (#13463)
Russian translation updated by Kirill Bezrukov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11621 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-13 08:06:23 +00:00
Toshi MARUYAMA 594d9e9da2 Merged r11613 from trunk to 2.3-stable.
Fixing HTML in groups index view.

Contributed by Gregor Schmidt.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11619 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-13 00:32:53 +00:00
Toshi MARUYAMA 7ebc62387a Merged r11615 from trunk to 2.3-stable (#13458)
Bulgarian translation ordered by Ivan Cenov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11618 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-13 00:08:24 +00:00
Toshi MARUYAMA 3269c42cdc Merged r11614 from trunk to 2.3-stable (#13450)
Czech translation changed by Karel Pičman.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11617 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-13 00:08:13 +00:00
Toshi MARUYAMA 9436318987 Merged r11556 from trunk to 2.3-stable (#13391)
Czech translation changed by Karel Pičman.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11616 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-13 00:08:02 +00:00
Jean-Philippe Lang 94ecabbaf9 Merged r11605 from trunk (#13301).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11606 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-12 17:09:28 +00:00
Toshi MARUYAMA 063c9a2a83 Merged r11603 from trunk to 2.3-stable (#13447)
German translation changed by Filou Centrinov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11604 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-12 14:41:21 +00:00
Toshi MARUYAMA 376e8d4aa3 Merged r11599, r11600, r11601 from trunk to 2.3-stable (#13438)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11602 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-12 12:15:06 +00:00
Toshi MARUYAMA 20114bd8e0 Merged r11597 from trunk to 2.3-stable (#13437)
German translation of setting_emails_header changed by Filou Centrinov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11598 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-12 10:37:48 +00:00
Jean-Philippe Lang cfdd85173f Merged r11526 and r11590 from trunk (#13341).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11591 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-11 18:00:39 +00:00
Toshi MARUYAMA 5de8e9f04c Merged r11588 from trunk to 2.3-stable (#13420)
Korean translation changed by Jongwook Choi.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11589 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-11 00:49:50 +00:00
Toshi MARUYAMA 79db8fd3e0 Merged r11585 from trunk to 2.3-stable (#13420)
Korean translation updated by Jongwook Choi.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11587 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-10 23:19:41 +00:00
Toshi MARUYAMA 98b7900c5c Merged r11584 from trunk to 2.3-stable (#13420)
Korean translation changed by Jongwook Choi.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11586 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-10 23:19:30 +00:00
Jean-Philippe Lang 23c28c1ef7 Merged r11582 from trunk (#13337).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11583 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-10 13:16:09 +00:00
Jean-Philippe Lang 974863e8f4 Merged r11525 from trunk (#11498).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11581 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-10 12:36:44 +00:00
Jean-Philippe Lang 58af20746b Merged r11522 from trunk (#13340).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11580 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-10 12:35:20 +00:00
Toshi MARUYAMA efcd602444 Merged r11578 from trunk to 2.3-stable (#13414)
Bulgarian translation updated and changed by Ivan Cenov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11579 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-10 04:41:45 +00:00
Jean-Philippe Lang 20cd146e93 Backported r11494 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11577 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 11:07:55 +00:00
Jean-Philippe Lang 8a97dfdeab Merged r11507 from trunk (#13329).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11576 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 11:01:28 +00:00
Jean-Philippe Lang b0b7f4d7d6 Merged r11506 from trunk (#13329).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11575 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 11:00:33 +00:00
Jean-Philippe Lang 53680edb2d Merged r11497 from trunk (#13329).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11574 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 10:59:22 +00:00
Jean-Philippe Lang ddf0307718 Merged r11488 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11573 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 10:47:27 +00:00
Jean-Philippe Lang 0ab90145fe Merged r11571 from trunk (#12122).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11572 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 10:45:12 +00:00
Jean-Philippe Lang f4def66c58 Merged r11518 from trunk (#8529).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11570 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 10:19:39 +00:00
Jean-Philippe Lang 4413e0e52e Merged r11519 and r11520 from trunk (#13335).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11569 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 10:17:26 +00:00
Jean-Philippe Lang b2e1080007 Merged r11567 from trunk (#13272).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11568 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 10:01:42 +00:00
Jean-Philippe Lang 8245eaa9f3 Merged r11474 from trunk (#10277).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11566 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 09:17:37 +00:00
Jean-Philippe Lang 83430dacd9 Merged r11471 from trunk (#5329).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11565 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 09:15:42 +00:00
Jean-Philippe Lang 998a29cbaf Merged r11473 from trunk (#3676).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11564 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 09:14:22 +00:00
Jean-Philippe Lang 511099e9ca Merged r11521 from trunk (#3371).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11563 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 09:13:01 +00:00
Jean-Philippe Lang a18db94c06 Merged r11472 from trunk (#3107).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11562 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-09 09:11:44 +00:00
Toshi MARUYAMA fe3a4cdbd1 Merged r11560 from trunk to 2.3-stable (#13399)
Korean translation changed by Lucas Yang.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11561 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-08 10:08:34 +00:00
Toshi MARUYAMA 67bb69d68e Merged r11554 from trunk to 2.3-stable (#13391)
Czech translation for 2.3-stable updated by Karel Pičman.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11559 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-08 01:09:34 +00:00
Toshi MARUYAMA 639b6f5c85 Merged r11553 from trunk to 2.3-stable (#13398, #13391)
Czech translation for 2.2-stable updated by Karel Pičman.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11558 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-08 01:09:22 +00:00
Toshi MARUYAMA b783bbf3bb 2.3-stable: svn propset svn:eol-style native to fixtures (#12641)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11552 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-07 21:07:51 +00:00
Toshi MARUYAMA a4b6928a26 Merged r11544, r11545, r11546, r11547, r11549 from trunk to 2.3-stable (#12641)
fix that diff outputs become ??? in some non ASCII words.

Contributed by Filou Centrinov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11551 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-07 21:03:55 +00:00
Toshi MARUYAMA 0e92038047 Merged r11542 from trunk to 2.3-stable.
use %r{} instead of // at lib/redmine/unified_diff.rb.

Syntax highlight is broken in gedit 2.28.4 on CentOS 6.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11548 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-07 10:29:50 +00:00
Toshi MARUYAMA 35b17d3bdc Merged from r11537 trunk to 2.3-stable (#13350)
fix some Japanese "issue" translations.

Contributed by Go MAEDA.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11541 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-06 09:50:23 +00:00
Toshi MARUYAMA 8672114648 Merged from r11536 trunk to 2.3-stable (#13349)
Japanese translation updated by Go MAEDA.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11540 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-06 09:50:12 +00:00
Toshi MARUYAMA 699fa9ac3f Merged r11530 from trunk to 2.3-stable (#13339)
Vietnamese translation changed by Minh Thien Nguyen.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11535 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-03 12:11:11 +00:00
Toshi MARUYAMA a5a1bd5a35 Merged r11528 and r11529 from trunk to 2.3-stable (#13343, #13339)
Vietnamese translation for 2.2-stable and 2.3-stable updated by Minh Thien Nguyen.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11534 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-03 12:11:00 +00:00
Toshi MARUYAMA 4100d3beeb Merged r11527 from trunk to 2.3-stable (#13338, #13329)
Ruby2.0: remove "warning: class variable access from toplevel" in lib/plugins/rfpdf/lib/tcpdf.rb.

Contributed by Jun NAITOH.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11533 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-03 12:10:48 +00:00
Jean-Philippe Lang 58ebb87ae6 Merged r11510 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11517 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-01 16:22:37 +00:00
Jean-Philippe Lang 33ef9fbe29 Merged r11508 from trunk (#13301).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11516 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-01 16:22:02 +00:00
Jean-Philippe Lang b0fa5e7305 Merged r11513 from trunk (#13328).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11515 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-01 16:20:27 +00:00
Jean-Philippe Lang 5bb2f5e211 Merged r11509 and r11512 from trunk (#13309).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11514 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-01 16:19:05 +00:00
Toshi MARUYAMA 2293a5d3f4 Merged r11504 from trunk to 2.3-stable (#13324)
pt-BR translation changed.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11505 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-01 07:08:23 +00:00
Toshi MARUYAMA d14cd42a78 Merged r11499 from trunk to 2.3-stable (#13324)
pt-BR translation for 2.3-stable updated.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11503 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-01 02:34:33 +00:00
Toshi MARUYAMA 2a53538616 Merged r11498 from trunk to 2.3-stable (#13325, #13324)
pt-BR translation for 2.2-stable updated.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11501 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-03-01 01:57:52 +00:00
Toshi MARUYAMA d1f63717dd Merged r11491 from trunk to 2.3-stable (#13310)
pt-BR "label_last_n_weeks" translation updated.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11493 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-02-28 08:35:41 +00:00
Toshi MARUYAMA d22b085d1d Merged r11483 from trunk to 2.3-stable (#13281)
Russian translation updated by Kirill Bezrukov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11485 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-02-25 15:40:31 +00:00
Toshi MARUYAMA 15751a6931 Merged r11482 from trunk to 2.3-stable (#13280)
German translation changed by Filou Centrinov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11484 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-02-25 14:58:14 +00:00
Toshi MARUYAMA 052cf73dfd Merged r11476 from trunk to 2.3-stable (#13246)
German translation changed by Filou Centrinov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11481 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-02-25 11:44:44 +00:00
Jean-Philippe Lang a4bee12e5a Merged r11464 from trunk (#13173).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11465 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-02-24 10:14:22 +00:00
Jean-Philippe Lang 92507382b4 Merged r11461, r11462 from trunk (#13251).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11463 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-02-24 10:06:27 +00:00
Jean-Philippe Lang e6d63a4e0d Merged r11459 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11460 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-02-24 09:22:41 +00:00
Jean-Philippe Lang 346085c5fc Set stable version.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11455 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-02-23 14:30:18 +00:00
Jean-Philippe Lang dcfc9170e6 Added 2.3-stable branch.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11454 e93f8b46-1217-0410-a6f0-8f06a7374b81
2013-02-23 14:26:18 +00:00
994 changed files with 10505 additions and 35760 deletions

8
.gitignore vendored
View File

@ -1,7 +1,5 @@
/.project /.project
/.loadpath /.loadpath
/.powrc
/.rvmrc
/config/additional_environment.rb /config/additional_environment.rb
/config/configuration.yml /config/configuration.yml
/config/database.yml /config/database.yml
@ -17,14 +15,8 @@
/lib/redmine/scm/adapters/mercurial/redminehelper.pyo /lib/redmine/scm/adapters/mercurial/redminehelper.pyo
/log/*.log* /log/*.log*
/log/mongrel_debug /log/mongrel_debug
/plugins/*
!/plugins/README
/public/dispatch.* /public/dispatch.*
/public/plugin_assets /public/plugin_assets
/public/themes/*
!/public/themes/alternate
!/public/themes/classic
!/public/themes/README
/tmp/* /tmp/*
/tmp/cache/* /tmp/cache/*
/tmp/pdf/* /tmp/pdf/*

View File

@ -2,8 +2,6 @@ syntax: glob
.project .project
.loadpath .loadpath
.powrc
.rvmrc
config/additional_environment.rb config/additional_environment.rb
config/configuration.yml config/configuration.yml
config/database.yml config/database.yml

View File

@ -1,38 +0,0 @@
# Redmine runs tests on own continuous integration server.
# http://www.redmine.org/projects/redmine/wiki/Continuous_integration
# You can also run tests on your environment.
language: ruby
rvm:
- 1.8.7
- 1.9.3
- 2.0
- 2.1
- jruby
matrix:
allow_failures:
# SCM tests fail randomly due to IO.popen().
# https://github.com/jruby/jruby/issues/779
- rvm: jruby
env:
- "TEST_SUITE=units DATABASE_ADAPTER=postgresql"
- "TEST_SUITE=functionals DATABASE_ADAPTER=postgresql"
- "TEST_SUITE=integration DATABASE_ADAPTER=postgresql"
- "TEST_SUITE=units DATABASE_ADAPTER=mysql"
- "TEST_SUITE=functionals DATABASE_ADAPTER=mysql"
- "TEST_SUITE=integration DATABASE_ADAPTER=mysql"
- "TEST_SUITE=units DATABASE_ADAPTER=sqlite3"
- "TEST_SUITE=functionals DATABASE_ADAPTER=sqlite3"
- "TEST_SUITE=integration DATABASE_ADAPTER=sqlite3"
before_install:
- "sudo apt-get update -qq"
- "sudo apt-get --no-install-recommends install bzr cvs git mercurial subversion"
script:
- "SCMS=bazaar,cvs,subversion,git,mercurial,filesystem"
- "export SCMS"
- "git --version"
- "bundle install"
- "RUN_ON_NOT_OFFICIAL='' RUBY_VER=1.9 BRANCH=trunk bundle exec rake config/database.yml"
- "bundle install"
- "JRUBY_OPTS=-J-Xmx1024m bundle exec rake ci"
notifications:
email: false

View File

@ -1,8 +0,0 @@
**Do not send a pull requst to this github repository**.
For more detail, please see [official website] wiki [Contribute].
[official website]: http://www.redmine.org
[Contribute]: http://www.redmine.org/projects/redmine/wiki/Contribute

31
Gemfile
View File

@ -1,13 +1,11 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem "rails", "3.2.18" gem "rails", "3.2.13"
gem "rake", "~> 10.1.1"
gem "jquery-rails", "~> 2.0.2" gem "jquery-rails", "~> 2.0.2"
gem "coderay", "~> 1.1.0" gem "i18n", "~> 0.6.0"
gem "coderay", "~> 1.0.9"
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby] gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
gem "builder", "3.0.0" gem "builder", "3.0.0"
gem "request_store"
gem "mime-types"
# Optional gem for LDAP authentication # Optional gem for LDAP authentication
group :ldap do group :ldap do
@ -16,30 +14,24 @@ end
# Optional gem for OpenID authentication # Optional gem for OpenID authentication
group :openid do group :openid do
gem "ruby-openid", "~> 2.3.0", :require => "openid" gem "ruby-openid", "~> 2.2.3", :require => "openid"
gem "rack-openid" gem "rack-openid"
end end
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
platforms :mri, :mingw do platforms :mri, :mingw do
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
group :rmagick do group :rmagick do
# RMagick 2 supports ruby 1.9 # RMagick 2 supports ruby 1.9
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support # RMagick 1 would be fine for ruby 1.8 but Bundler does not support
# different requirements for the same gem on different platforms # different requirements for the same gem on different platforms
gem "rmagick", ">= 2.0.0" gem "rmagick", ">= 2.0.0"
end end
# Optional Markdown support, not for JRuby
group :markdown do
# TODO: upgrade to redcarpet 3.x when ruby1.8 support is dropped
gem "redcarpet", "~> 2.3.0"
end
end end
platforms :jruby do platforms :jruby do
# jruby-openssl is bundled with JRuby 1.7.0 # jruby-openssl is bundled with JRuby 1.7.0
gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0' gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0'
gem "activerecord-jdbc-adapter", "~> 1.3.2" gem "activerecord-jdbc-adapter", "1.2.5"
end end
# Include database gems for the adapters found in the database # Include database gems for the adapters found in the database
@ -86,11 +78,9 @@ end
group :test do group :test do
gem "shoulda", "~> 3.3.2" gem "shoulda", "~> 3.3.2"
gem "mocha", "~> 1.0.0", :require => 'mocha/api' gem "mocha", "~> 0.13.3"
if RUBY_VERSION >= '1.9.3' gem 'capybara', '~> 2.0.0'
gem "capybara", "~> 2.1.0" gem 'nokogiri', '< 1.6.0'
gem "selenium-webdriver"
end
end end
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
@ -102,6 +92,5 @@ end
# Load plugins' Gemfiles # Load plugins' Gemfiles
Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file| Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file|
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v` puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
#TODO: switch to "eval_gemfile file" when bundler >= 1.2.0 will be required (rails 4) instance_eval File.read(file)
instance_eval File.read(file), file
end end

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -20,21 +20,13 @@ class AccountController < ApplicationController
include CustomFieldsHelper include CustomFieldsHelper
# prevents login action to be filtered by check_if_login_required application scope filter # prevents login action to be filtered by check_if_login_required application scope filter
skip_before_filter :check_if_login_required, :check_password_change skip_before_filter :check_if_login_required
# Overrides ApplicationController#verify_authenticity_token to disable
# token verification on openid callbacks
def verify_authenticity_token
unless using_open_id?
super
end
end
# Login request and validation # Login request and validation
def login def login
if request.get? if request.get?
if User.current.logged? if User.current.logged?
redirect_back_or_default home_url, :referer => true redirect_to home_url
end end
else else
authenticate_user authenticate_user
@ -83,15 +75,11 @@ class AccountController < ApplicationController
else else
if request.post? if request.post?
user = User.find_by_mail(params[:mail].to_s) user = User.find_by_mail(params[:mail].to_s)
# user not found # user not found or not active
unless user unless user && user.active?
flash.now[:error] = l(:notice_account_unknown_email) flash.now[:error] = l(:notice_account_unknown_email)
return return
end end
unless user.active?
handle_inactive_user(user, lost_password_path)
return
end
# user cannot change its password # user cannot change its password
unless user.change_password_allowed? unless user.change_password_allowed?
flash.now[:error] = l(:notice_can_t_change_password) flash.now[:error] = l(:notice_can_t_change_password)
@ -164,19 +152,6 @@ class AccountController < ApplicationController
redirect_to signin_path redirect_to signin_path
end end
# Sends a new account activation email
def activation_email
if session[:registered_user_id] && Setting.self_registration == '1'
user_id = session.delete(:registered_user_id).to_i
user = User.find_by_id(user_id)
if user && user.registered?
register_by_email_activation(user)
return
end
end
redirect_to(home_url)
end
private private
def authenticate_user def authenticate_user
@ -188,7 +163,7 @@ class AccountController < ApplicationController
end end
def password_authentication def password_authentication
user = User.try_to_login(params[:username], params[:password], false) user = User.try_to_login(params[:username], params[:password])
if user.nil? if user.nil?
invalid_credentials invalid_credentials
@ -196,31 +171,27 @@ class AccountController < ApplicationController
onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id }) onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id })
else else
# Valid user # Valid user
if user.active? successful_authentication(user)
successful_authentication(user)
else
handle_inactive_user(user)
end
end end
end end
def open_id_authenticate(openid_url) def open_id_authenticate(openid_url)
back_url = signin_url(:autologin => params[:autologin]) back_url = signin_url(:autologin => params[:autologin])
authenticate_with_open_id(
openid_url, :required => [:nickname, :fullname, :email], authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => back_url, :method => :post) do |result, identity_url, registration|
:return_to => back_url, :method => :post
) do |result, identity_url, registration|
if result.successful? if result.successful?
user = User.find_or_initialize_by_identity_url(identity_url) user = User.find_or_initialize_by_identity_url(identity_url)
if user.new_record? if user.new_record?
# Self-registration off # 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 # Create on the fly
user.login = registration['nickname'] unless registration['nickname'].nil? user.login = registration['nickname'] unless registration['nickname'].nil?
user.mail = registration['email'] unless registration['email'].nil? user.mail = registration['email'] unless registration['email'].nil?
user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil? user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
user.random_password user.random_password
user.register user.register
case Setting.self_registration case Setting.self_registration
when '1' when '1'
register_by_email_activation(user) do register_by_email_activation(user) do
@ -240,7 +211,7 @@ class AccountController < ApplicationController
if user.active? if user.active?
successful_authentication(user) successful_authentication(user)
else else
handle_inactive_user(user) account_pending
end end
end end
end end
@ -290,7 +261,7 @@ class AccountController < ApplicationController
token = Token.new(:user => user, :action => "register") token = Token.new(:user => user, :action => "register")
if user.save and token.save if user.save and token.save
Mailer.register(token).deliver Mailer.register(token).deliver
flash[:notice] = l(:notice_account_register_done, :email => user.mail) flash[:notice] = l(:notice_account_register_done)
redirect_to signin_path redirect_to signin_path
else else
yield if block_given? yield if block_given?
@ -320,32 +291,14 @@ class AccountController < ApplicationController
if user.save if user.save
# Sends an email to the administrators # Sends an email to the administrators
Mailer.account_activation_request(user).deliver Mailer.account_activation_request(user).deliver
account_pending(user) account_pending
else else
yield if block_given? yield if block_given?
end end
end end
def handle_inactive_user(user, redirect_path=signin_path) def account_pending
if user.registered? flash[:notice] = l(:notice_account_pending)
account_pending(user, redirect_path) redirect_to signin_path
else
account_locked(user, redirect_path)
end
end
def account_pending(user, redirect_path=signin_path)
if Setting.self_registration == '1'
flash[:error] = l(:notice_account_not_activated_yet, :url => activation_email_path)
session[:registered_user_id] = user.id
else
flash[:error] = l(:notice_account_pending)
end
redirect_to redirect_path
end
def account_locked(user, redirect_path=signin_path)
flash[:error] = l(:notice_account_locked)
redirect_to redirect_path
end end
end end

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -65,7 +65,7 @@ class AdminController < ApplicationController
@test = Mailer.test_email(User.current).deliver @test = Mailer.test_email(User.current).deliver
flash[:notice] = l(:notice_email_sent, User.current.mail) flash[:notice] = l(:notice_email_sent, User.current.mail)
rescue Exception => e rescue Exception => e
flash[:error] = l(:notice_email_error, Redmine::CodesetUtil.replace_invalid_utf8(e.message)) flash[:error] = l(:notice_email_error, e.message)
end end
ActionMailer::Base.raise_delivery_errors = raise_delivery_errors ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
redirect_to settings_path(:tab => 'notifications') redirect_to settings_path(:tab => 'notifications')
@ -77,8 +77,7 @@ class AdminController < ApplicationController
[:text_default_administrator_account_changed, User.default_admin_account_changed?], [:text_default_administrator_account_changed, User.default_admin_account_changed?],
[:text_file_repository_writable, File.writable?(Attachment.storage_path)], [:text_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)], [:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)],
[:text_rmagick_available, Object.const_defined?(:Magick)], [:text_rmagick_available, Object.const_defined?(:Magick)]
[:text_convert_available, Redmine::Thumbnail.convert_available?]
] ]
end end
end end

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -33,24 +33,14 @@ class ApplicationController < ActionController::Base
layout 'base' layout 'base'
protect_from_forgery protect_from_forgery
def verify_authenticity_token
unless api_request?
super
end
end
def handle_unverified_request def handle_unverified_request
unless api_request? super
super cookies.delete(autologin_cookie_name)
cookies.delete(autologin_cookie_name)
self.logged_user = nil
render_error :status => 422, :message => "Invalid form authenticity token."
end
end end
before_filter :session_expiration, :user_setup, :check_if_login_required, :check_password_change, :set_localization before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
rescue_from ::Unauthorized, :with => :deny_access rescue_from ::Unauthorized, :with => :deny_access
rescue_from ::ActionView::MissingTemplate, :with => :missing_template rescue_from ::ActionView::MissingTemplate, :with => :missing_template
@ -88,9 +78,6 @@ class ApplicationController < ActionController::Base
session[:user_id] = user.id session[:user_id] = user.id
session[:ctime] = Time.now.utc.to_i session[:ctime] = Time.now.utc.to_i
session[:atime] = Time.now.utc.to_i session[:atime] = Time.now.utc.to_i
if user.must_change_password?
session[:pwd] = '1'
end
end end
def user_setup def user_setup
@ -120,15 +107,11 @@ class ApplicationController < ActionController::Base
if (key = api_key_from_request) if (key = api_key_from_request)
# Use API key # Use API key
user = User.find_by_api_key(key) user = User.find_by_api_key(key)
elsif request.authorization.to_s =~ /\ABasic /i else
# HTTP Basic, either username/password or API key/random # HTTP Basic, either username/password or API key/random
authenticate_with_http_basic do |username, password| authenticate_with_http_basic do |username, password|
user = User.try_to_login(username, password) || User.find_by_api_key(username) user = User.try_to_login(username, password) || User.find_by_api_key(username)
end end
if user && user.must_change_password?
render_error :message => 'You must change your password', :status => 403
return
end
end end
# Switch user if requested by an admin user # Switch user if requested by an admin user
if user && user.admin? && (username = api_switch_user_from_request) if user && user.admin? && (username = api_switch_user_from_request)
@ -187,22 +170,12 @@ class ApplicationController < ActionController::Base
require_login if Setting.login_required? require_login if Setting.login_required?
end end
def check_password_change
if session[:pwd]
if User.current.must_change_password?
redirect_to my_password_path
else
session.delete(:pwd)
end
end
end
def set_localization def set_localization
lang = nil lang = nil
if User.current.logged? if User.current.logged?
lang = find_language(User.current.language) lang = find_language(User.current.language)
end end
if lang.nil? && !Setting.force_default_language_for_anonymous? && request.env['HTTP_ACCEPT_LANGUAGE'] if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
if !accept_lang.blank? if !accept_lang.blank?
accept_lang = accept_lang.downcase accept_lang = accept_lang.downcase
@ -222,13 +195,7 @@ class ApplicationController < ActionController::Base
url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id]) url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
end end
respond_to do |format| respond_to do |format|
format.html { format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
if request.xhr?
head :unauthorized
else
redirect_to :controller => "account", :action => "login", :back_url => url
end
}
format.atom { redirect_to :controller => "account", :action => "login", :back_url => url } format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
@ -331,7 +298,7 @@ class ApplicationController < ActionController::Base
# Find issues with a single :id param or :ids array param # Find issues with a single :id param or :ids array param
# Raises a Unauthorized exception if one of the issues is not visible # Raises a Unauthorized exception if one of the issues is not visible
def find_issues def find_issues
@issues = Issue.where(:id => (params[:id] || params[:ids])).preload(:project, :status, :tracker, :priority, :author, :assigned_to, :relations_to).to_a @issues = Issue.find_all_by_id(params[:id] || params[:ids])
raise ActiveRecord::RecordNotFound if @issues.empty? raise ActiveRecord::RecordNotFound if @issues.empty?
raise Unauthorized unless @issues.all?(&:visible?) raise Unauthorized unless @issues.all?(&:visible?)
@projects = @issues.collect(&:project).compact.uniq @projects = @issues.collect(&:project).compact.uniq
@ -374,13 +341,13 @@ class ApplicationController < ActionController::Base
url url
end end
def redirect_back_or_default(default, options={}) def redirect_back_or_default(default)
back_url = params[:back_url].to_s back_url = params[:back_url].to_s
if back_url.present? if back_url.present?
begin begin
uri = URI.parse(back_url) uri = URI.parse(back_url)
# do not redirect user to another host or to the login or register page # do not redirect user to another host or to the login or register page
if ((uri.relative? && back_url.match(%r{\A/(\w.*)?\z})) || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)}) if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
redirect_to(back_url) redirect_to(back_url)
return return
end end
@ -388,9 +355,6 @@ class ApplicationController < ActionController::Base
logger.warn("Could not redirect to invalid URL #{back_url}") logger.warn("Could not redirect to invalid URL #{back_url}")
# redirect to default # redirect to default
end end
elsif options[:referer]
redirect_to_referer_or default
return
end end
redirect_to default redirect_to default
false false
@ -463,6 +427,13 @@ class ApplicationController < ActionController::Base
request.xhr? ? false : 'base' request.xhr? ? false : 'base'
end end
def invalid_authenticity_token
if api_request?
logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
end
render_error "Invalid form authenticity token."
end
def render_feed(items, options={}) def render_feed(items, options={})
@items = items || [] @items = items || []
@items.sort! {|x,y| y.event_datetime <=> x.event_datetime } @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
@ -558,7 +529,7 @@ class ApplicationController < ActionController::Base
# Returns a string that can be used as filename value in Content-Disposition header # Returns a string that can be used as filename value in Content-Disposition header
def filename_for_content_disposition(name) def filename_for_content_disposition(name)
request.env['HTTP_USER_AGENT'] =~ %r{(MSIE|Trident)} ? ERB::Util.url_encode(name) : name request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
end end
def api_request? def api_request?
@ -584,6 +555,21 @@ class ApplicationController < ActionController::Base
flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present? flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
end end
# Sets the `flash` notice or error based the number of issues that did not save
#
# @param [Array, Issue] issues all of the saved and unsaved Issues
# @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
if unsaved_issue_ids.empty?
flash[:notice] = l(:notice_successful_update) unless issues.empty?
else
flash[:error] = l(:notice_failed_to_save_issues,
:count => unsaved_issue_ids.size,
:total => issues.size,
:ids => '#' + unsaved_issue_ids.join(', #'))
end
end
# Rescues an invalid query statement. Just in case... # Rescues an invalid query statement. Just in case...
def query_statement_invalid(exception) def query_statement_invalid(exception)
logger.error "Query::StatementInvalid: #{exception.message}" if logger logger.error "Query::StatementInvalid: #{exception.message}" if logger

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -25,7 +25,7 @@ class BoardsController < ApplicationController
helper :watchers helper :watchers
def index def index
@boards = @project.boards.includes(:project, :last_message => :author).all @boards = @project.boards.includes(:last_message => :author).all
# show the board if there is only one # show the board if there is only one
if @boards.size == 1 if @boards.size == 1
@board = @boards.first @board = @boards.first

View File

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

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -19,15 +19,17 @@ class ContextMenusController < ApplicationController
helper :watchers helper :watchers
helper :issues helper :issues
before_filter :find_issues, :only => :issues
def issues def issues
@issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project)
(render_404; return) unless @issues.present?
if (@issues.size == 1) if (@issues.size == 1)
@issue = @issues.first @issue = @issues.first
end end
@issue_ids = @issues.map(&:id).sort @issue_ids = @issues.map(&:id).sort
@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&) @allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
@projects = @issues.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1
@can = {:edit => User.current.allowed_to?(:edit_issues, @projects), @can = {:edit => User.current.allowed_to?(:edit_issues, @projects),
:log_time => (@project && User.current.allowed_to?(:log_time, @project)), :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
@ -55,10 +57,12 @@ class ContextMenusController < ApplicationController
@options_by_custom_field = {} @options_by_custom_field = {}
if @can[:edit] if @can[:edit]
custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?) custom_fields = @issues.map(&:available_custom_fields).reduce(:&).select do |f|
%w(bool list user version).include?(f.field_format) && !f.multiple?
end
custom_fields.each do |field| custom_fields.each do |field|
values = field.possible_values_options(@projects) values = field.possible_values_options(@projects)
if values.present? if values.any?
@options_by_custom_field[field] = values @options_by_custom_field[field] = values
end end
end end
@ -69,7 +73,8 @@ class ContextMenusController < ApplicationController
end end
def time_entries def time_entries
@time_entries = TimeEntry.where(:id => params[:ids]).preload(:project).to_a @time_entries = TimeEntry.all(
:conditions => {:id => params[:ids]}, :include => :project)
(render_404; return) unless @time_entries.present? (render_404; return) unless @time_entries.present?
@projects = @time_entries.collect(&:project).compact.uniq @projects = @time_entries.collect(&:project).compact.uniq

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -21,22 +21,13 @@ class CustomFieldsController < ApplicationController
before_filter :require_admin before_filter :require_admin
before_filter :build_new_custom_field, :only => [:new, :create] before_filter :build_new_custom_field, :only => [:new, :create]
before_filter :find_custom_field, :only => [:edit, :update, :destroy] before_filter :find_custom_field, :only => [:edit, :update, :destroy]
accept_api_auth :index
def index def index
respond_to do |format| @custom_fields_by_type = CustomField.all.group_by {|f| f.class.name }
format.html { @tab = params[:tab] || 'IssueCustomField'
@custom_fields_by_type = CustomField.all.group_by {|f| f.class.name }
}
format.api {
@custom_fields = CustomField.all
}
end
end end
def new def new
@custom_field.field_format = 'string' if @custom_field.field_format.blank?
@custom_field.default_value = nil
end end
def create def create
@ -76,7 +67,9 @@ class CustomFieldsController < ApplicationController
def build_new_custom_field def build_new_custom_field
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field]) @custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
if @custom_field.nil? if @custom_field.nil?
render :action => 'select_type' render_404
else
@custom_field.default_value = nil
end end
end end

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -90,7 +90,7 @@ class GroupsController < ApplicationController
end end
def add_users def add_users
@users = User.where(:id => (params[:user_id] || params[:user_ids])).all @users = User.find_all_by_id(params[:user_id] || params[:user_ids])
@group.users << @users if request.post? @group.users << @users if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'users') } format.html { redirect_to edit_group_path(@group, :tab => 'users') }

View File

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

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -29,7 +29,7 @@ class IssueStatusesController < ApplicationController
render :action => "index", :layout => false if request.xhr? render :action => "index", :layout => false if request.xhr?
} }
format.api { format.api {
@issue_statuses = IssueStatus.order('position').all @issue_statuses = IssueStatus.all(:order => 'position')
} }
end end
end end
@ -40,7 +40,7 @@ class IssueStatusesController < ApplicationController
def create def create
@issue_status = IssueStatus.new(params[:issue_status]) @issue_status = IssueStatus.new(params[:issue_status])
if @issue_status.save if request.post? && @issue_status.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to issue_statuses_path redirect_to issue_statuses_path
else else
@ -54,7 +54,7 @@ class IssueStatusesController < ApplicationController
def update def update
@issue_status = IssueStatus.find(params[:id]) @issue_status = IssueStatus.find(params[:id])
if @issue_status.update_attributes(params[:issue_status]) if request.put? && @issue_status.update_attributes(params[:issue_status])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to issue_statuses_path redirect_to issue_statuses_path
else else

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -62,14 +62,10 @@ class IssuesController < ApplicationController
case params[:format] case params[:format]
when 'csv', 'pdf' when 'csv', 'pdf'
@limit = Setting.issues_export_limit.to_i @limit = Setting.issues_export_limit.to_i
if params[:columns] == 'all'
@query.column_names = @query.available_inline_columns.map(&:name)
end
when 'atom' when 'atom'
@limit = Setting.feeds_limit.to_i @limit = Setting.feeds_limit.to_i
when 'xml', 'json' when 'xml', 'json'
@offset, @limit = api_offset_and_limit @offset, @limit = api_offset_and_limit
@query.column_names = %w(author)
else else
@limit = per_page_option @limit = per_page_option
end end
@ -107,9 +103,6 @@ class IssuesController < ApplicationController
@journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
@journals.each_with_index {|j,i| j.indice = i+1} @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.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
Journal.preload_journals_details_custom_fields(@journals)
# TODO: use #select! when ruby1.8 support is dropped
@journals.reject! {|journal| !journal.notes? && journal.visible_details.empty?}
@journals.reverse! if User.current.wants_comments_in_reverse_order? @journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.all @changesets = @issue.changesets.visible.all
@ -120,8 +113,6 @@ class IssuesController < ApplicationController
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@priorities = IssuePriority.active @priorities = IssuePriority.active
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@relation = IssueRelation.new
respond_to do |format| respond_to do |format|
format.html { format.html {
retrieve_previous_and_next_issue_ids retrieve_previous_and_next_issue_ids
@ -185,7 +176,7 @@ class IssuesController < ApplicationController
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads])) @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
saved = false saved = false
begin begin
saved = save_issue_with_child_records saved = @issue.save_issue_with_child_records(params, @time_entry)
rescue ActiveRecord::StaleObjectError rescue ActiveRecord::StaleObjectError
@conflict = true @conflict = true
if params[:last_journal_id] if params[:last_journal_id]
@ -237,7 +228,7 @@ class IssuesController < ApplicationController
else else
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&) @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
end end
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields.visible}.reduce(:&) @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
@assignables = target_projects.map(&:assignable_users).reduce(:&) @assignables = target_projects.map(&:assignable_users).reduce(:&)
@trackers = target_projects.map(&:trackers).reduce(:&) @trackers = target_projects.map(&:trackers).reduce(:&)
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&) @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
@ -248,9 +239,7 @@ class IssuesController < ApplicationController
end end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&) @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
render :layout => false if request.xhr?
@issue_params = params[:issue] || {}
@issue_params[:custom_field_values] ||= {}
end end
def bulk_update def bulk_update
@ -258,8 +247,8 @@ class IssuesController < ApplicationController
@copy = params[:copy].present? @copy = params[:copy].present?
attributes = parse_params_for_bulk_issue_attributes(params) attributes = parse_params_for_bulk_issue_attributes(params)
unsaved_issues = [] unsaved_issue_ids = []
saved_issues = [] moved_issues = []
if @copy && params[:copy_subtasks].present? if @copy && params[:copy_subtasks].present?
# Descendant issues will be copied with the parent task # Descendant issues will be copied with the parent task
@ -267,62 +256,52 @@ class IssuesController < ApplicationController
@issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}} @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
end end
@issues.each do |orig_issue| @issues.each do |issue|
orig_issue.reload issue.reload
if @copy if @copy
issue = orig_issue.copy({}, issue = issue.copy({},
:attachments => params[:copy_attachments].present?, :attachments => params[:copy_attachments].present?,
:subtasks => params[:copy_subtasks].present? :subtasks => params[:copy_subtasks].present?
) )
else
issue = orig_issue
end end
journal = issue.init_journal(User.current, params[:notes]) journal = issue.init_journal(User.current, params[:notes])
issue.safe_attributes = attributes issue.safe_attributes = attributes
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
if issue.save if issue.save
saved_issues << issue moved_issues << issue
else else
unsaved_issues << orig_issue # Keep unsaved issue ids to display them in flash error
unsaved_issue_ids << issue.id
end end
end end
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
if unsaved_issues.empty? if params[:follow]
flash[:notice] = l(:notice_successful_update) unless saved_issues.empty? if @issues.size == 1 && moved_issues.size == 1
if params[:follow] redirect_to issue_path(moved_issues.first)
if @issues.size == 1 && saved_issues.size == 1 elsif moved_issues.map(&:project).uniq.size == 1
redirect_to issue_path(saved_issues.first) redirect_to project_issues_path(moved_issues.map(&:project).first)
elsif saved_issues.map(&:project).uniq.size == 1
redirect_to project_issues_path(saved_issues.map(&:project).first)
end
else
redirect_back_or_default _project_issues_path(@project)
end end
else else
@saved_issues = @issues redirect_back_or_default _project_issues_path(@project)
@unsaved_issues = unsaved_issues
@issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).all
bulk_edit
render :action => 'bulk_edit'
end end
end end
def destroy def destroy
@hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
if @hours > 0 if @hours > 0
case params[:todo] case params[:todo]
when 'destroy' when 'destroy'
# nothing to do # nothing to do
when 'nullify' when 'nullify'
TimeEntry.where(['issue_id IN (?)', @issues]).update_all('issue_id = NULL') TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
when 'reassign' when 'reassign'
reassign_to = @project.issues.find_by_id(params[:reassign_to_id]) reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
if reassign_to.nil? if reassign_to.nil?
flash.now[:error] = l(:error_issue_not_found_in_project) flash.now[:error] = l(:error_issue_not_found_in_project)
return return
else else
TimeEntry.where(['issue_id IN (?)', @issues]). TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
update_all("issue_id = #{reassign_to.id}")
end end
else else
# display the destroy form if it's a user request # display the destroy form if it's a user request
@ -431,11 +410,8 @@ class IssuesController < ApplicationController
@issue.safe_attributes = params[:issue] @issue.safe_attributes = params[:issue]
@priorities = IssuePriority.active @priorities = IssuePriority.active
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, @issue.new_record?) @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
@available_watchers = @issue.watcher_users @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
if @issue.project.users.count <= 20
@available_watchers = (@available_watchers + @issue.project.users.sort).uniq
end
end end
def check_for_default_issue_status def check_for_default_issue_status
@ -460,26 +436,4 @@ class IssuesController < ApplicationController
end end
attributes attributes
end end
# Saves @issue and a time_entry from the parameters
def save_issue_with_child_records
Issue.transaction do
if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
time_entry = @time_entry || TimeEntry.new
time_entry.project = @issue.project
time_entry.issue = @issue
time_entry.user = User.current
time_entry.spent_on = User.current.today
time_entry.attributes = params[:time_entry]
@issue.time_entries << time_entry
end
call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
if @issue.save
call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
else
raise ActiveRecord::Rollback
end
end
end
end end

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -66,7 +66,7 @@ class JournalsController < ApplicationController
text = @issue.description text = @issue.description
end end
# Replaces pre blocks with [...] # Replaces pre blocks with [...]
text = text.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]') text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
@content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> " @content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
@content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" @content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -28,11 +28,12 @@ class MembersController < ApplicationController
@member_count = @project.member_principals.count @member_count = @project.member_principals.count
@member_pages = Paginator.new @member_count, @limit, params['page'] @member_pages = Paginator.new @member_count, @limit, params['page']
@offset ||= @member_pages.offset @offset ||= @member_pages.offset
@members = @project.member_principals. @members = @project.member_principals.all(
order("#{Member.table_name}.id"). :order => "#{Member.table_name}.id",
limit(@limit). :limit => @limit,
offset(@offset). :offset => @offset
all )
respond_to do |format| respond_to do |format|
format.html { head 406 } format.html { head 406 }
format.api format.api

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -35,7 +35,7 @@ class MessagesController < ApplicationController
page = params[:page] page = params[:page]
# Find the page of the requested reply # Find the page of the requested reply
if params[:r] && page.nil? if params[:r] && page.nil?
offset = @topic.children.where("#{Message.table_name}.id < ?", params[:r].to_i).count offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i])
page = 1 + offset / REPLIES_PER_PAGE page = 1 + offset / REPLIES_PER_PAGE
end end
@ -113,7 +113,7 @@ class MessagesController < ApplicationController
@subject = "RE: #{@subject}" unless @subject.starts_with?('RE:') @subject = "RE: #{@subject}" unless @subject.starts_with?('RE:')
@content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> " @content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> "
@content << @message.content.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" @content << @message.content.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
end end
def preview def preview
@ -126,14 +126,14 @@ class MessagesController < ApplicationController
private private
def find_message def find_message
return unless find_board return unless find_board
@message = @board.messages.includes(:parent).find(params[:id]) @message = @board.messages.find(params[:id], :include => :parent)
@topic = @message.root @topic = @message.root
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def find_board def find_board
@board = Board.includes(:project).find(params[:board_id]) @board = Board.find(params[:board_id], :include => :project)
@project = @board.project @project = @board.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -17,8 +17,6 @@
class MyController < ApplicationController class MyController < ApplicationController
before_filter :require_login before_filter :require_login
# let user change user's password when user has to
skip_before_filter :check_password_change, :only => :password
helper :issues helper :issues
helper :users helper :users
@ -57,6 +55,7 @@ class MyController < ApplicationController
@user.pref.attributes = params[:pref] @user.pref.attributes = params[:pref]
if @user.save if @user.save
@user.pref.save @user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
set_language_if_valid @user.language set_language_if_valid @user.language
flash[:notice] = l(:notice_account_updated) flash[:notice] = l(:notice_account_updated)
redirect_to my_account_path redirect_to my_account_path
@ -92,17 +91,14 @@ class MyController < ApplicationController
return return
end end
if request.post? if request.post?
if !@user.check_password?(params[:password]) if @user.check_password?(params[:password])
flash.now[:error] = l(:notice_account_wrong_password)
elsif params[:password] == params[:new_password]
flash.now[:error] = l(:notice_new_password_must_be_different)
else
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
@user.must_change_passwd = false
if @user.save if @user.save
flash[:notice] = l(:notice_account_password_updated) flash[:notice] = l(:notice_account_password_updated)
redirect_to my_account_path redirect_to my_account_path
end end
else
flash[:error] = l(:notice_account_wrong_password)
end end
end end
end end
@ -139,7 +135,7 @@ class MyController < ApplicationController
@blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
@block_options = [] @block_options = []
BLOCKS.each do |k, v| BLOCKS.each do |k, v|
unless @blocks.values.flatten.include?(k) 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] @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]
end end
end end

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -42,11 +42,11 @@ class NewsController < ApplicationController
@news_count = scope.count @news_count = scope.count
@news_pages = Paginator.new @news_count, @limit, params['page'] @news_pages = Paginator.new @news_count, @limit, params['page']
@offset ||= @news_pages.offset @offset ||= @news_pages.offset
@newss = scope.includes([:author, :project]). @newss = scope.all(:include => [:author, :project],
order("#{News.table_name}.created_on DESC"). :order => "#{News.table_name}.created_on DESC",
limit(@limit). :offset => @offset,
offset(@offset). :limit => @limit)
all
respond_to do |format| respond_to do |format|
format.html { format.html {
@news = News.new # for adding news inline @news = News.new # for adding news inline

View File

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

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -82,7 +82,7 @@ class ProjectsController < ApplicationController
if validate_parent_id && @project.save if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
# Add current user as a project member if current user is not admin # Add current user as a project member if he is not admin
unless User.current.admin? unless User.current.admin?
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
m = Member.new(:user => User.current, :roles => [r]) m = Member.new(:user => User.current, :roles => [r])
@ -151,11 +151,11 @@ class ProjectsController < ApplicationController
cond = @project.project_condition(Setting.display_subprojects_issues?) cond = @project.project_condition(Setting.display_subprojects_issues?)
@open_issues_by_tracker = Issue.visible.open.where(cond).group(:tracker).count @open_issues_by_tracker = Issue.visible.open.where(cond).count(:group => :tracker)
@total_issues_by_tracker = Issue.visible.where(cond).group(:tracker).count @total_issues_by_tracker = Issue.visible.where(cond).count(:group => :tracker)
if User.current.allowed_to?(:view_time_entries, @project) if User.current.allowed_to?(:view_time_entries, @project)
@total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f @total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
end end
@key = User.current.rss_key @key = User.current.rss_key

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -31,13 +31,11 @@ class QueriesController < ApplicationController
else else
@limit = per_page_option @limit = per_page_option
end end
@query_count = IssueQuery.visible.count @query_count = IssueQuery.visible.count
@query_pages = Paginator.new @query_count, @limit, params['page'] @query_pages = Paginator.new @query_count, @limit, params['page']
@queries = IssueQuery.visible. @queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
order("#{Query.table_name}.name").
limit(@limit).
offset(@offset).
all
respond_to do |format| respond_to do |format|
format.api format.api
end end
@ -47,7 +45,7 @@ class QueriesController < ApplicationController
@query = IssueQuery.new @query = IssueQuery.new
@query.user = User.current @query.user = User.current
@query.project = @project @query.project = @project
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params) @query.build_from_params(params)
end end
@ -55,13 +53,13 @@ class QueriesController < ApplicationController
@query = IssueQuery.new(params[:query]) @query = IssueQuery.new(params[:query])
@query.user = User.current @query.user = User.current
@query.project = params[:query_is_for_all] ? nil : @project @query.project = params[:query_is_for_all] ? nil : @project
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params) @query.build_from_params(params)
@query.column_names = nil if params[:default_columns] @query.column_names = nil if params[:default_columns]
if @query.save if @query.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to_issues(:query_id => @query) redirect_to _project_issues_path(@project, :query_id => @query)
else else
render :action => 'new', :layout => !request.xhr? render :action => 'new', :layout => !request.xhr?
end end
@ -73,13 +71,13 @@ class QueriesController < ApplicationController
def update def update
@query.attributes = params[:query] @query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all] @query.project = nil if params[:query_is_for_all]
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params) @query.build_from_params(params)
@query.column_names = nil if params[:default_columns] @query.column_names = nil if params[:default_columns]
if @query.save if @query.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to_issues(:query_id => @query) redirect_to _project_issues_path(@project, :query_id => @query)
else else
render :action => 'edit' render :action => 'edit'
end end
@ -87,7 +85,7 @@ class QueriesController < ApplicationController
def destroy def destroy
@query.destroy @query.destroy
redirect_to_issues(:set_filter => 1) redirect_to _project_issues_path(@project, :set_filter => 1)
end end
private private
@ -105,16 +103,4 @@ private
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def redirect_to_issues(options)
if params[:gantt]
if @project
redirect_to project_gantt_path(@project, options)
else
redirect_to issues_gantt_path(options)
end
else
redirect_to _project_issues_path(@project, options)
end
end
end end

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -18,7 +18,7 @@
require 'SVG/Graph/Bar' require 'SVG/Graph/Bar'
require 'SVG/Graph/BarHorizontal' require 'SVG/Graph/BarHorizontal'
require 'digest/sha1' require 'digest/sha1'
require 'redmine/scm/adapters' require 'redmine/scm/adapters/abstract_adapter'
class ChangesetNotFound < Exception; end class ChangesetNotFound < Exception; end
class InvalidRevisionParam < Exception; end class InvalidRevisionParam < Exception; end
@ -94,7 +94,7 @@ class RepositoriesController < ApplicationController
@committers = @repository.committers @committers = @repository.committers
@users = @project.users @users = @project.users
additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id) additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
@users += User.where(:id => additional_user_ids).all unless additional_user_ids.empty? @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
@users.compact! @users.compact!
@users.sort! @users.sort!
if request.post? && params[:committers].is_a?(Hash) if request.post? && params[:committers].is_a?(Hash)
@ -111,7 +111,7 @@ class RepositoriesController < ApplicationController
end end
def show def show
@repository.fetch_changesets if @project.active? && Setting.autofetch_changesets? && @path.empty? @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
@entries = @repository.entries(@path, @rev) @entries = @repository.entries(@path, @rev)
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
@ -229,8 +229,7 @@ class RepositoriesController < ApplicationController
# Adds a related issue to a changeset # Adds a related issue to a changeset
# POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
def add_related_issue def add_related_issue
issue_id = params[:issue_id].to_s.sub(/^#/,'') @issue = @changeset.find_referenced_issue_by_id(params[:issue_id])
@issue = @changeset.find_referenced_issue_by_id(issue_id)
if @issue && (!@issue.visible? || @changeset.issues.include?(@issue)) if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
@issue = nil @issue = nil
end end
@ -353,18 +352,15 @@ class RepositoriesController < ApplicationController
@date_to = Date.today @date_to = Date.today
@date_from = @date_to << 11 @date_from = @date_to << 11
@date_from = Date.civil(@date_from.year, @date_from.month, 1) @date_from = Date.civil(@date_from.year, @date_from.month, 1)
commits_by_day = Changeset. commits_by_day = Changeset.count(
where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to). :all, :group => :commit_date,
group(:commit_date). :conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
count
commits_by_month = [0] * 12 commits_by_month = [0] * 12
commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
changes_by_day = Change. changes_by_day = Change.count(
joins(:changeset). :all, :group => :commit_date, :include => :changeset,
where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to). :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
group(:commit_date).
count
changes_by_month = [0] * 12 changes_by_month = [0] * 12
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
@ -397,10 +393,10 @@ class RepositoriesController < ApplicationController
end end
def graph_commits_per_author(repository) def graph_commits_per_author(repository)
commits_by_author = Changeset.where("repository_id = ?", repository.id).group(:committer).count commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id])
commits_by_author.to_a.sort! {|x, y| x.last <=> y.last} commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
changes_by_author = Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", repository.id).group(:committer).count changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id])
h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o} h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
fields = commits_by_author.collect {|r| r.first} fields = commits_by_author.collect {|r| r.first}
@ -411,11 +407,11 @@ class RepositoriesController < ApplicationController
commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
# Remove email address in usernames # Remove email adress in usernames
fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
graph = SVG::Graph::BarHorizontal.new( graph = SVG::Graph::BarHorizontal.new(
:height => 30 * commits_data.length, :height => 400,
:width => 800, :width => 800,
:fields => fields, :fields => fields,
:stack => :side, :stack => :side,

View File

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

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -33,7 +33,9 @@ class SettingsController < ApplicationController
if request.post? && params[:settings] && params[:settings].is_a?(Hash) if request.post? && params[:settings] && params[:settings].is_a?(Hash)
settings = (params[:settings] || {}).dup.symbolize_keys settings = (params[:settings] || {}).dup.symbolize_keys
settings.each do |name, value| settings.each do |name, value|
Setting.set_from_params name, value # remove blank values in array settings
value.delete_if {|v| v.blank? } if value.is_a?(Array)
Setting[name] = value
end end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_path(:tab => params[:tab]) redirect_to settings_path(:tab => params[:tab])
@ -46,9 +48,6 @@ class SettingsController < ApplicationController
@guessed_host_and_path = request.host_with_port.dup @guessed_host_and_path = request.host_with_port.dup
@guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank? @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
@commit_update_keywords = Setting.commit_update_keywords.dup
@commit_update_keywords = [{}] unless @commit_update_keywords.is_a?(Array) && @commit_update_keywords.any?
Redmine::Themes.rescan Redmine::Themes.rescan
end end
end end

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -19,7 +19,11 @@ class SysController < ActionController::Base
before_filter :check_enabled before_filter :check_enabled
def projects def projects
p = Project.active.has_module(:repository).order("#{Project.table_name}.identifier").preload(:repository).all p = Project.active.has_module(:repository).find(
:all,
:include => :repository,
:order => "#{Project.table_name}.identifier"
)
# extra_info attribute from repository breaks activeresource client # extra_info attribute from repository breaks activeresource client
render :xml => p.to_xml( render :xml => p.to_xml(
:only => [:id, :identifier, :name, :is_public, :status], :only => [:id, :identifier, :name, :is_public, :status],

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -46,15 +46,18 @@ class TimelogController < ApplicationController
sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria) sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns) sort_update(@query.sortable_columns)
scope = time_entry_scope(:order => sort_clause). scope = time_entry_scope(:order => sort_clause)
includes(:project, :user, :issue).
preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
respond_to do |format| respond_to do |format|
format.html { format.html {
# Paginate results
@entry_count = scope.count @entry_count = scope.count
@entry_pages = Paginator.new @entry_count, per_page_option, params['page'] @entry_pages = Paginator.new @entry_count, per_page_option, params['page']
@entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).all @entries = scope.all(
:include => [:project, :activity, :user, {:issue => :tracker}],
:limit => @entry_pages.per_page,
:offset => @entry_pages.offset
)
@total_hours = scope.sum(:hours).to_f @total_hours = scope.sum(:hours).to_f
render :layout => !request.xhr? render :layout => !request.xhr?
@ -62,15 +65,24 @@ class TimelogController < ApplicationController
format.api { format.api {
@entry_count = scope.count @entry_count = scope.count
@offset, @limit = api_offset_and_limit @offset, @limit = api_offset_and_limit
@entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).all @entries = scope.all(
:include => [:project, :activity, :user, {:issue => :tracker}],
:limit => @limit,
:offset => @offset
)
} }
format.atom { format.atom {
entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").all entries = scope.reorder("#{TimeEntry.table_name}.created_on DESC").all(
:include => [:project, :activity, :user, {:issue => :tracker}],
:limit => Setting.feeds_limit.to_i
)
render_feed(entries, :title => l(:label_spent_time)) render_feed(entries, :title => l(:label_spent_time))
} }
format.csv { format.csv {
# Export all entries # Export all entries
@entries = scope.all @entries = scope.all(
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}]
)
send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv') send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv')
} }
end end
@ -182,7 +194,6 @@ class TimelogController < ApplicationController
time_entry.safe_attributes = attributes time_entry.safe_attributes = attributes
call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry }) call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
unless time_entry.save unless time_entry.save
logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info
# Keep unsaved time_entry ids to display them in flash error # Keep unsaved time_entry ids to display them in flash error
unsaved_time_entry_ids << time_entry.id unsaved_time_entry_ids << time_entry.id
end end
@ -232,7 +243,7 @@ private
end end
def find_time_entries def find_time_entries
@time_entries = TimeEntry.where(:id => params[:id] || params[:ids]).all @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
raise ActiveRecord::RecordNotFound if @time_entries.empty? raise ActiveRecord::RecordNotFound if @time_entries.empty?
@projects = @time_entries.collect(&:project).compact.uniq @projects = @time_entries.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1 @project = @projects.first if @projects.size == 1

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -60,7 +60,7 @@ class UsersController < ApplicationController
def show def show
# show projects based on current user visibility # show projects based on current user visibility
@memberships = @user.memberships.where(Project.visible_condition(User.current)).all @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current))
events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10) events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
@events_by_day = events.group_by(&:event_date) @events_by_day = events.group_by(&:event_date)
@ -80,7 +80,6 @@ class UsersController < ApplicationController
def new def new
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
@user.safe_attributes = params[:user]
@auth_sources = AuthSource.all @auth_sources = AuthSource.all
end end
@ -90,17 +89,19 @@ class UsersController < ApplicationController
@user.admin = params[:user][:admin] || false @user.admin = params[:user][:admin] || false
@user.login = params[:user][:login] @user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
@user.pref.attributes = params[:pref]
if @user.save if @user.save
Mailer.account_information(@user, @user.password).deliver if params[:send_information] @user.pref.attributes = params[:pref]
@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]
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user))) flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
if params[:continue] if params[:continue]
attrs = params[:user].slice(:generate_password) redirect_to new_user_path
redirect_to new_user_path(:user => attrs)
else else
redirect_to edit_user_path(@user) redirect_to edit_user_path(@user)
end end
@ -138,11 +139,12 @@ class UsersController < ApplicationController
if @user.save if @user.save
@user.pref.save @user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
if was_activated if was_activated
Mailer.account_activated(@user).deliver Mailer.account_activated(@user).deliver
elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil? elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
Mailer.account_information(@user, @user.password).deliver Mailer.account_information(@user, params[:user][:password]).deliver
end end
respond_to do |format| respond_to do |format|

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -46,11 +46,11 @@ class VersionsController < ApplicationController
@issues_by_version = {} @issues_by_version = {}
if @selected_tracker_ids.any? && @versions.any? if @selected_tracker_ids.any? && @versions.any?
issues = Issue.visible. issues = Issue.visible.all(
includes(:project, :tracker). :include => [:project, :status, :tracker, :priority, :fixed_version],
preload(:status, :priority, :fixed_version). :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)},
where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)). :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id"
order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id") )
@issues_by_version = issues.group_by(&:fixed_version) @issues_by_version = issues.group_by(&:fixed_version)
end end
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?} @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -30,7 +30,6 @@ class WatchersController < ApplicationController
accept_api_auth :create, :destroy accept_api_auth :create, :destroy
def new def new
@users = users_for_new_watcher
end end
def create def create
@ -45,7 +44,7 @@ class WatchersController < ApplicationController
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}} format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
format.js { @users = users_for_new_watcher } format.js
format.api { render_api_ok } format.api { render_api_ok }
end end
end end
@ -53,10 +52,7 @@ class WatchersController < ApplicationController
def append def append
if params[:watcher].is_a?(Hash) if params[:watcher].is_a?(Hash)
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]] user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
@users = User.active.where(:id => user_ids).all @users = User.active.find_all_by_id(user_ids)
end
if @users.blank?
render :nothing => true
end end
end end
@ -70,7 +66,10 @@ class WatchersController < ApplicationController
end end
def autocomplete_for_user def autocomplete_for_user
@users = users_for_new_watcher @users = User.active.sorted.like(params[:q]).limit(100).all
if @watched
@users -= @watched.watcher_users
end
render :layout => false render :layout => false
end end
@ -92,14 +91,8 @@ class WatchersController < ApplicationController
def find_watchables def find_watchables
klass = Object.const_get(params[:object_type].camelcase) rescue nil klass = Object.const_get(params[:object_type].camelcase) rescue nil
if klass && klass.respond_to?('watched_by') if klass && klass.respond_to?('watched_by')
@watchables = klass.where(:id => Array.wrap(params[:object_id])).all @watchables = klass.find_all_by_id(Array.wrap(params[:object_id]))
raise Unauthorized if @watchables.any? {|w| raise Unauthorized if @watchables.any? {|w| w.respond_to?(:visible?) && !w.visible?}
if w.respond_to?(:visible?)
!w.visible?
elsif w.respond_to?(:project) && w.project
!w.project.visible?
end
}
end end
render_404 unless @watchables.present? render_404 unless @watchables.present?
end end
@ -113,17 +106,4 @@ class WatchersController < ApplicationController
format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} } format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} }
end end
end end
def users_for_new_watcher
users = []
if params[:q].blank? && @project.present?
users = @project.users.sorted
else
users = User.active.sorted.like(params[:q]).limit(100)
end
if @watched
users -= @watched.watcher_users
end
users
end
end end

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -15,6 +15,8 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'diff'
# The WikiController follows the Rails REST controller pattern but with # The WikiController follows the Rails REST controller pattern but with
# a few differences # a few differences
# #
@ -62,12 +64,7 @@ class WikiController < ApplicationController
# display a page (in editing mode if it doesn't exist) # display a page (in editing mode if it doesn't exist)
def show def show
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project) if @page.new_record?
deny_access
return
end
@content = @page.content_for_version(params[:version])
if @content.nil?
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request? if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
edit edit
render :action => 'edit' render :action => 'edit'
@ -76,6 +73,11 @@ class WikiController < ApplicationController
end end
return return
end end
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
deny_access
return
end
@content = @page.content_for_version(params[:version])
if User.current.allowed_to?(:export_wiki_pages, @project) if User.current.allowed_to?(:export_wiki_pages, @project)
if params[:format] == 'pdf' if params[:format] == 'pdf'
send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf") send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
@ -104,19 +106,19 @@ class WikiController < ApplicationController
def edit def edit
return render_403 unless editable? return render_403 unless editable?
if @page.new_record? if @page.new_record?
@page.content = WikiContent.new(:page => @page)
if params[:parent].present? if params[:parent].present?
@page.parent = @page.wiki.find_page(params[:parent].to_s) @page.parent = @page.wiki.find_page(params[:parent].to_s)
end end
end end
@content = @page.content_for_version(params[:version]) @content = @page.content_for_version(params[:version])
@content ||= WikiContent.new(:page => @page)
@content.text = initial_page_content(@page) if @content.text.blank? @content.text = initial_page_content(@page) if @content.text.blank?
# don't keep previous comment # don't keep previous comment
@content.comments = nil @content.comments = nil
# To prevent StaleObjectError exception when reverting to a previous version # To prevent StaleObjectError exception when reverting to a previous version
@content.version = @page.content.version if @page.content @content.version = @page.content.version
@text = @content.text @text = @content.text
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@ -130,9 +132,10 @@ class WikiController < ApplicationController
def update def update
return render_403 unless editable? return render_403 unless editable?
was_new_page = @page.new_record? was_new_page = @page.new_record?
@page.content = WikiContent.new(:page => @page) if @page.new_record?
@page.safe_attributes = params[:wiki_page] @page.safe_attributes = params[:wiki_page]
@content = @page.content || WikiContent.new(:page => @page) @content = @page.content
content_params = params[:content] content_params = params[:content]
if content_params.nil? && params[:wiki_page].is_a?(Hash) if content_params.nil? && params[:wiki_page].is_a?(Hash)
content_params = params[:wiki_page].slice(:text, :comments, :version) content_params = params[:wiki_page].slice(:text, :comments, :version)
@ -144,23 +147,20 @@ class WikiController < ApplicationController
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@section = params[:section].to_i @section = params[:section].to_i
@section_hash = params[:section_hash] @section_hash = params[:section_hash]
@content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(@section, @text, @section_hash) @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
else else
@content.version = content_params[:version] if content_params[:version] @content.version = content_params[:version] if content_params[:version]
@content.text = @text @content.text = @text
end end
@content.author = User.current @content.author = User.current
if @page.save_with_content(@content) if @page.save_with_content
attachments = Attachment.attach_files(@page, params[:attachments]) attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page) render_attachment_warning_if_needed(@page)
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
respond_to do |format| respond_to do |format|
format.html { format.html { redirect_to project_wiki_page_path(@project, @page.title) }
anchor = @section ? "section-#{@section}" : nil
redirect_to project_wiki_page_path(@project, @page.title, :anchor => anchor)
}
format.api { format.api {
if was_new_page if was_new_page
render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title) render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title)
@ -277,19 +277,14 @@ class WikiController < ApplicationController
# Export wiki to a single pdf or html file # Export wiki to a single pdf or html file
def export def export
@pages = @wiki.pages. @pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}])
order('title').
includes([:content, {:attachments => :author}]).
all
respond_to do |format| respond_to do |format|
format.html { format.html {
export = render_to_string :action => 'export_multiple', :layout => false export = render_to_string :action => 'export_multiple', :layout => false
send_data(export, :type => 'text/html', :filename => "wiki.html") send_data(export, :type => 'text/html', :filename => "wiki.html")
} }
format.pdf { format.pdf {
send_data(wiki_pages_to_pdf(@pages, @project), send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
:type => 'application/pdf',
:filename => "#{@project.identifier}.pdf")
} }
end end
end end
@ -356,10 +351,6 @@ private
end end
def load_pages_for_index def load_pages_for_index
@pages = @wiki.pages.with_updated_on. @pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
reorder("#{WikiPage.table_name}.title").
includes(:wiki => :project).
includes(:parent).
all
end end
end end

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -18,30 +18,39 @@
class WorkflowsController < ApplicationController class WorkflowsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin, :find_roles, :find_trackers
def index def index
@workflow_counts = WorkflowTransition.count_by_tracker_and_role @workflow_counts = WorkflowTransition.count_by_tracker_and_role
end end
def edit def edit
find_trackers_roles_and_statuses_for_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]
if request.post? && @roles && @trackers && params[:transitions] if request.post?
transitions = params[:transitions].deep_dup WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
transitions.each do |old_status_id, transitions_by_new_status| (params[:issue_status] || []).each { |status_id, transitions|
transitions_by_new_status.each do |new_status_id, transition_by_rule| transitions.each { |new_status_id, options|
transition_by_rule.reject! {|rule, transition| transition == 'no_change'} author = options.is_a?(Array) && options.include?('author') && !options.include?('always')
end 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)
}
}
if @role.save
redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
return
end end
WorkflowTransition.replace_transitions(@trackers, @roles, transitions)
flash[:notice] = l(:notice_successful_update)
redirect_to_referer_or workflows_edit_path
return
end end
if @trackers && @roles && @statuses.any? @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
workflows = WorkflowTransition.where(:role_id => @roles.map(&:id), :tracker_id => @trackers.map(&:id)) if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
@statuses = @tracker.issue_statuses
end
@statuses ||= IssueStatus.sorted.all
if @tracker && @role && @statuses.any?
workflows = WorkflowTransition.where(:role_id => @role.id, :tracker_id => @tracker.id).all
@workflows = {} @workflows = {}
@workflows['always'] = workflows.select {|w| !w.author && !w.assignee} @workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
@workflows['author'] = workflows.select {|w| w.author} @workflows['author'] = workflows.select {|w| w.author}
@ -50,30 +59,35 @@ class WorkflowsController < ApplicationController
end end
def permissions def permissions
find_trackers_roles_and_statuses_for_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]
if request.post? && @roles && @trackers && params[:permissions] if request.post? && @role && @tracker
permissions = params[:permissions].deep_dup WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {})
permissions.each { |field, rule_by_status_id| redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
rule_by_status_id.reject! {|status_id, rule| rule == 'no_change'}
}
WorkflowPermission.replace_permissions(@trackers, @roles, permissions)
flash[:notice] = l(:notice_successful_update)
redirect_to_referer_or workflows_permissions_path
return return
end end
if @roles && @trackers @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
@fields = (Tracker::CORE_FIELDS_ALL - @trackers.map(&:disabled_core_fields).reduce(:&)).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]} if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
@custom_fields = @trackers.map(&:custom_fields).flatten.uniq.sort @statuses = @tracker.issue_statuses
@permissions = WorkflowPermission.rules_by_status_id(@trackers, @roles) 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] ||= {}} @statuses.each {|status| @permissions[status.id] ||= {}}
end end
end end
def copy def copy
@roles = Role.sorted
@trackers = Tracker.sorted
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any' if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
@source_tracker = nil @source_tracker = nil
@ -85,10 +99,10 @@ class WorkflowsController < ApplicationController
else else
@source_role = Role.find_by_id(params[:source_role_id].to_i) @source_role = Role.find_by_id(params[:source_role_id].to_i)
end end
@target_trackers = params[:target_tracker_ids].blank? ?
nil : Tracker.where(:id => params[:target_tracker_ids]).all @target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids])
@target_roles = params[:target_role_ids].blank? ? @target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids])
nil : Role.where(:id => params[:target_role_ids]).all
if request.post? if request.post?
if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) 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) flash.now[:error] = l(:error_workflow_copy_source)
@ -104,37 +118,11 @@ class WorkflowsController < ApplicationController
private private
def find_trackers_roles_and_statuses_for_edit
find_roles
find_trackers
find_statuses
end
def find_roles def find_roles
ids = Array.wrap(params[:role_id]) @roles = Role.sorted.all
if ids == ['all']
@roles = Role.sorted.all
elsif ids.present?
@roles = Role.where(:id => ids).all
end
@roles = nil if @roles.blank?
end end
def find_trackers def find_trackers
ids = Array.wrap(params[:tracker_id]) @trackers = Tracker.sorted.all
if ids == ['all']
@trackers = Tracker.sorted.all
elsif ids.present?
@trackers = Tracker.where(:id => ids).all
end
@trackers = nil if @trackers.blank?
end
def find_statuses
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
if @trackers && @used_statuses_only
@statuses = @trackers.map(&:issue_statuses).flatten.uniq.sort.presence
end
@statuses ||= IssueStatus.sorted.all
end end
end end

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -24,12 +24,4 @@ module AdminHelper
[l(:project_status_closed), '5'], [l(:project_status_closed), '5'],
[l(:project_status_archived), '9']], selected.to_s) [l(:project_status_archived), '9']], selected.to_s)
end end
def plugin_data_for_updates(plugins)
data = {"v" => Redmine::VERSION.to_s, "p" => {}}
plugins.each do |plugin|
data["p"].merge! plugin.id => {"v" => plugin.version, "n" => plugin.name, "a" => plugin.author}
end
data
end
end end

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -72,16 +72,14 @@ module ApplicationHelper
subject = nil subject = nil
text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}" text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
if options[:subject] == false if options[:subject] == false
title = issue.subject.truncate(60) title = truncate(issue.subject, :length => 60)
else else
subject = issue.subject subject = issue.subject
if truncate_length = options[:truncate] if options[:truncate]
subject = subject.truncate(truncate_length) subject = truncate(subject, :length => options[:truncate])
end end
end end
only_path = options[:only_path].nil? ? true : options[:only_path] s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title
s = link_to(text, issue_path(issue, :only_path => only_path),
:class => issue.css_classes, :title => title)
s << h(": #{subject}") if subject s << h(": #{subject}") if subject
s = h("#{issue.project} - ") + s if options[:project] s = h("#{issue.project} - ") + s if options[:project]
s s
@ -118,7 +116,7 @@ module ApplicationHelper
# Generates a link to a message # Generates a link to a message
def link_to_message(message, options={}, html_options = nil) def link_to_message(message, options={}, html_options = nil)
link_to( link_to(
message.subject.truncate(60), truncate(message.subject, :length => 60),
board_message_path(message.board_id, message.parent_id || message.id, { board_message_path(message.board_id, message.parent_id || message.id, {
:r => (message.parent_id && message.id), :r => (message.parent_id && message.id),
:anchor => (message.parent_id ? "message-#{message.id}" : nil) :anchor => (message.parent_id ? "message-#{message.id}" : nil)
@ -157,50 +155,6 @@ module ApplicationHelper
end end
end end
# Helper that formats object for html or text rendering
def format_object(object, html=true, &block)
if block_given?
object = yield object
end
case object.class.name
when 'Array'
object.map {|o| format_object(o, html)}.join(', ').html_safe
when 'Time'
format_time(object)
when 'Date'
format_date(object)
when 'Fixnum'
object.to_s
when 'Float'
sprintf "%.2f", object
when 'User'
html ? link_to_user(object) : object.to_s
when 'Project'
html ? link_to_project(object) : object.to_s
when 'Version'
html ? link_to(object.name, version_path(object)) : object.to_s
when 'TrueClass'
l(:general_text_Yes)
when 'FalseClass'
l(:general_text_No)
when 'Issue'
object.visible? && html ? link_to_issue(object) : "##{object.id}"
when 'CustomValue', 'CustomFieldValue'
if object.custom_field
f = object.custom_field.format.formatted_custom_value(self, object, html)
if f.nil? || f.is_a?(String)
f
else
format_object(f, html, &block)
end
else
object.value.to_s
end
else
html ? h(object) : object.to_s
end
end
def wiki_page_path(page, options={}) def wiki_page_path(page, options={})
url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options)) url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
end end
@ -227,7 +181,7 @@ module ApplicationHelper
end end
def format_activity_title(text) def format_activity_title(text)
h(truncate_single_line_raw(text, 100)) h(truncate_single_line(text, :length => 100))
end end
def format_activity_day(date) def format_activity_day(date)
@ -235,7 +189,7 @@ module ApplicationHelper
end end
def format_activity_description(text) def format_activity_description(text)
h(text.to_s.truncate(120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
).gsub(/[\r\n]+/, "<br />").html_safe ).gsub(/[\r\n]+/, "<br />").html_safe
end end
@ -312,13 +266,9 @@ module ApplicationHelper
end end
# Renders tabs and their content # Renders tabs and their content
def render_tabs(tabs, selected=params[:tab]) def render_tabs(tabs)
if tabs.any? if tabs.any?
unless tabs.detect {|tab| tab[:name] == selected} render :partial => 'common/tabs', :locals => {:tabs => tabs}
selected = nil
end
selected ||= tabs.first[:name]
render :partial => 'common/tabs', :locals => {:tabs => tabs, :selected_tab => selected}
else else
content_tag 'p', l(:label_no_data), :class => "nodata" content_tag 'p', l(:label_no_data), :class => "nodata"
end end
@ -380,7 +330,7 @@ module ApplicationHelper
end end
groups = '' groups = ''
collection.sort.each do |element| collection.sort.each do |element|
selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
(element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>) (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
end end
unless groups.empty? unless groups.empty?
@ -398,23 +348,11 @@ module ApplicationHelper
options options
end end
def option_tag(name, text, value, selected=nil, options={})
content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
end
# Truncates and returns the string as a single line # Truncates and returns the string as a single line
def truncate_single_line(string, *args) def truncate_single_line(string, *args)
ActiveSupport::Deprecation.warn(
"ApplicationHelper#truncate_single_line is deprecated and will be removed in Rails 4 poring")
# Rails 4 ActionView::Helpers::TextHelper#truncate escapes.
# So, result is broken.
truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
end end
def truncate_single_line_raw(string, length)
string.truncate(length).gsub(%r{[\r\n]+}m, ' ')
end
# Truncates at line break after 250 characters or options[:length] # Truncates at line break after 250 characters or options[:length]
def truncate_lines(string, options={}) def truncate_lines(string, options={})
length = options[:length] || 250 length = options[:length] || 250
@ -442,7 +380,7 @@ module ApplicationHelper
if @project if @project
link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
else else
content_tag('abbr', text, :title => format_time(time)) content_tag('acronym', text, :title => format_time(time))
end end
end end
@ -507,31 +445,12 @@ module ApplicationHelper
end end
end end
# Returns a h2 tag and sets the html title with the given arguments
def title(*args)
strings = args.map do |arg|
if arg.is_a?(Array) && arg.size >= 2
link_to(*arg)
else
h(arg.to_s)
end
end
html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
content_tag('h2', strings.join(' &#187; ').html_safe)
end
# Sets the html title
# Returns the html title when called without arguments
# Current project name and app_title and automatically appended
# Exemples:
# html_title 'Foo', 'Bar'
# html_title # => 'Foo - Bar - My Project - Redmine'
def html_title(*args) def html_title(*args)
if args.empty? if args.empty?
title = @html_title || [] title = @html_title || []
title << @project.name if @project title << @project.name if @project
title << Setting.app_title unless Setting.app_title == title.last title << Setting.app_title unless Setting.app_title == title.last
title.reject(&:blank?).join(' - ') title.select {|t| !t.blank? }.join(' - ')
else else
@html_title ||= [] @html_title ||= []
@html_title += args @html_title += args
@ -546,7 +465,6 @@ module ApplicationHelper
css << 'theme-' + theme.name css << 'theme-' + theme.name
end end
css << 'project-' + @project.identifier if @project && @project.identifier.present?
css << 'controller-' + controller_name css << 'controller-' + controller_name
css << 'action-' + action_name css << 'action-' + action_name
css.join(' ') css.join(' ')
@ -697,7 +615,7 @@ module ApplicationHelper
else else
wiki_page_id = page.present? ? Wiki.titleize(page) : nil wiki_page_id = page.present? ? Wiki.titleize(page) : nil
parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil 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, 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, :version => nil, :anchor => anchor, :parent => parent)
end end
end end
@ -738,9 +656,6 @@ module ApplicationHelper
# export:some/file -> Force the download of the file # export:some/file -> Force the download of the file
# Forum messages: # Forum messages:
# message#1218 -> Link to message with id 1218 # message#1218 -> Link to message with id 1218
# Projects:
# project:someproject -> Link to project named "someproject"
# project#3 -> Link to project with id 3
# #
# Links can refer other objects from other projects, using project identifier: # Links can refer other objects from other projects, using project identifier:
# identifier:r52 # identifier:r52
@ -765,30 +680,21 @@ module ApplicationHelper
repository = project.repository repository = project.repository
end end
# project.changesets.visible raises an SQL error because of a double join on repositories # project.changesets.visible raises an SQL error because of a double join on repositories
if repository && if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
(changeset = Changeset.visible. link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
find_by_repository_id_and_revision(repository.id, identifier)) :class => 'changeset',
link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), :title => truncate_single_line(changeset.comments, :length => 100))
{:only_path => only_path, :controller => 'repositories',
:action => 'revision', :id => project,
:repository_id => repository.identifier_param,
:rev => changeset.revision},
:class => 'changeset',
:title => truncate_single_line_raw(changeset.comments, 100))
end end
end end
elsif sep == '#' elsif sep == '#'
oid = identifier.to_i oid = identifier.to_i
case prefix case prefix
when nil when nil
if oid.to_s == identifier && if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
issue = Issue.visible.includes(:status).find_by_id(oid)
anchor = comment_id ? "note-#{comment_id}" : nil anchor = comment_id ? "note-#{comment_id}" : nil
link = link_to(h("##{oid}#{comment_suffix}"), link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
{:only_path => only_path, :controller => 'issues', :class => issue.css_classes,
:action => 'show', :id => oid, :anchor => anchor}, :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
:class => issue.css_classes,
:title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
end end
when 'document' when 'document'
if document = Document.visible.find_by_id(oid) if document = Document.visible.find_by_id(oid)
@ -801,7 +707,7 @@ module ApplicationHelper
:class => 'version' :class => 'version'
end end
when 'message' when 'message'
if message = Message.visible.includes(:parent).find_by_id(oid) if message = Message.visible.find_by_id(oid, :include => :parent)
link = link_to_message(message, {:only_path => only_path}, :class => 'message') link = link_to_message(message, {:only_path => only_path}, :class => 'message')
end end
when 'forum' when 'forum'
@ -822,7 +728,6 @@ module ApplicationHelper
elsif sep == ':' elsif sep == ':'
# removes the double quotes if any # removes the double quotes if any
name = identifier.gsub(%r{^"(.*)"$}, "\\1") name = identifier.gsub(%r{^"(.*)"$}, "\\1")
name = CGI.unescapeHTML(name)
case prefix case prefix
when 'document' when 'document'
if project && document = project.documents.visible.find_by_title(name) if project && document = project.documents.visible.find_by_title(name)
@ -857,7 +762,7 @@ module ApplicationHelper
if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first) if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
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}, link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
:class => 'changeset', :class => 'changeset',
:title => truncate_single_line_raw(changeset.comments, 100) :title => truncate_single_line(changeset.comments, :length => 100)
end end
else else
if repository && User.current.allowed_to?(:browse_repository, project) if repository && User.current.allowed_to?(:browse_repository, project)
@ -873,8 +778,7 @@ module ApplicationHelper
repo_prefix = nil repo_prefix = nil
end end
when 'attachment' when 'attachment'
attachments = options[:attachments] || [] attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
attachments += obj.attachments if obj.respond_to?(:attachments)
if attachments && attachment = Attachment.latest_attach(attachments, name) if attachments && attachment = Attachment.latest_attach(attachments, name)
link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment') link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
end end
@ -900,8 +804,7 @@ module ApplicationHelper
content_tag('div', content_tag('div',
link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
:class => 'contextual', :class => 'contextual',
:title => l(:button_edit_section), :title => l(:button_edit_section)) + heading.html_safe
:id => "section-#{@current_section}") + heading.html_safe
else else
heading heading
end end
@ -982,20 +885,19 @@ module ApplicationHelper
end end
end end
TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE) TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
# Renders the TOC with given headings # Renders the TOC with given headings
def replace_toc(text, headings) def replace_toc(text, headings)
text.gsub!(TOC_RE) do text.gsub!(TOC_RE) do
left_align, right_align = $2, $3
# Keep only the 4 first levels # Keep only the 4 first levels
headings = headings.select{|level, anchor, item| level <= 4} headings = headings.select{|level, anchor, item| level <= 4}
if headings.empty? if headings.empty?
'' ''
else else
div_class = 'toc' div_class = 'toc'
div_class << ' right' if right_align div_class << ' right' if $1 == '>'
div_class << ' left' if left_align div_class << ' left' if $1 == '<'
out = "<ul class=\"#{div_class}\"><li>" out = "<ul class=\"#{div_class}\"><li>"
root = headings.map(&:first).min root = headings.map(&:first).min
current = root current = root
@ -1073,7 +975,7 @@ module ApplicationHelper
html << "</ul></div>\n" html << "</ul></div>\n"
end end
html.html_safe html.html_safe
end end
def delete_link(url, options={}) def delete_link(url, options={})
options = { options = {
@ -1087,8 +989,8 @@ module ApplicationHelper
def preview_link(url, form, target='preview', options={}) def preview_link(url, form, target='preview', options={})
content_tag 'a', l(:label_preview), { content_tag 'a', l(:label_preview), {
:href => "#", :href => "#",
:onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
:accesskey => accesskey(:preview) :accesskey => accesskey(:preview)
}.merge(options) }.merge(options)
end end
@ -1133,7 +1035,7 @@ module ApplicationHelper
(pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
(pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) + (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) (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe + ), :class => 'progress', :style => "width: #{width};").html_safe +
content_tag('p', legend, :class => 'percent').html_safe content_tag('p', legend, :class => 'percent').html_safe
end end
@ -1166,7 +1068,6 @@ module ApplicationHelper
def include_calendar_headers_tags def include_calendar_headers_tags
unless @calendar_headers_tags_included unless @calendar_headers_tags_included
tags = javascript_include_tag("datepicker")
@calendar_headers_tags_included = true @calendar_headers_tags_included = true
content_for :header_tags do content_for :header_tags do
start_of_week = Setting.start_of_week start_of_week = Setting.start_of_week
@ -1174,16 +1075,15 @@ module ApplicationHelper
# Redmine uses 1..7 (monday..sunday) in settings and locales # Redmine uses 1..7 (monday..sunday) in settings and locales
# JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
start_of_week = start_of_week.to_i % 7 start_of_week = start_of_week.to_i % 7
tags << javascript_tag(
tags = javascript_tag(
"var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
"showOn: 'button', buttonImageOnly: true, buttonImage: '" + "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
path_to_image('/images/calendar.png') + path_to_image('/images/calendar.png') +
"', showButtonPanel: true, showWeek: true, showOtherMonths: true, " + "', showButtonPanel: true, showWeek: true, showOtherMonths: true, selectOtherMonths: true};")
"selectOtherMonths: true, changeMonth: true, changeYear: true, " +
"beforeShow: beforeShowDatePicker};")
jquery_locale = l('jquery.locale', :default => current_language.to_s) jquery_locale = l('jquery.locale', :default => current_language.to_s)
unless jquery_locale == 'en' unless jquery_locale == 'en'
tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js") tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
end end
tags tags
end end
@ -1243,13 +1143,18 @@ module ApplicationHelper
super sources, options super sources, options
end end
# TODO: remove this in 2.5.0 def content_for(name, content = nil, &block)
@has_content ||= {}
@has_content[name] = true
super(name, content, &block)
end
def has_content?(name) def has_content?(name)
content_for?(name) (@has_content && @has_content[name]) || false
end end
def sidebar_content? def sidebar_content?
content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present? has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
end end
def view_layouts_base_sidebar_hook_response def view_layouts_base_sidebar_hook_response
@ -1296,21 +1201,7 @@ module ApplicationHelper
end end
def favicon def favicon
"<link rel='shortcut icon' href='#{favicon_path}' />".html_safe "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
end
# Returns the path to the favicon
def favicon_path
icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
image_path(icon)
end
# Returns the full URL to the favicon
def favicon_url
# TODO: use #image_url introduced in Rails4
path = favicon_path
base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
end end
def robot_exclusion_tag def robot_exclusion_tag
@ -1332,7 +1223,7 @@ module ApplicationHelper
def api_meta(options) def api_meta(options)
if params[:nometa].present? || request.headers['X-Redmine-Nometa'] if params[:nometa].present? || request.headers['X-Redmine-Nometa']
# compatibility mode for activeresource clients that raise # compatibility mode for activeresource clients that raise
# an error when deserializing an array with attributes # an error when unserializing an array with attributes
nil nil
else else
options options

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -19,69 +19,55 @@
module CustomFieldsHelper module CustomFieldsHelper
CUSTOM_FIELDS_TABS = [ def custom_fields_tabs
{:name => 'IssueCustomField', :partial => 'custom_fields/index', CustomField::CUSTOM_FIELDS_TABS
: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}
]
def render_custom_fields_tabs(types)
tabs = CUSTOM_FIELDS_TABS.select {|h| types.include?(h[:name]) }
render_tabs tabs
end
def custom_field_type_options
CUSTOM_FIELDS_TABS.map {|h| [l(h[:label]), h[:name]]}
end
def render_custom_field_format_partial(form, custom_field)
partial = custom_field.format.form_partial
if partial
render :partial => custom_field.format.form_partial, :locals => {:f => form, :custom_field => custom_field}
end
end
def custom_field_tag_name(prefix, custom_field)
name = "#{prefix}[custom_field_values][#{custom_field.id}]"
name << "[]" if custom_field.multiple?
name
end
def custom_field_tag_id(prefix, custom_field)
"#{prefix}_custom_field_values_#{custom_field.id}"
end end
# Return custom field html tag corresponding to its format # Return custom field html tag corresponding to its format
def custom_field_tag(prefix, custom_value) def custom_field_tag(name, custom_value)
custom_value.custom_field.format.edit_tag self, custom_field = custom_value.custom_field
custom_field_tag_id(prefix, custom_value.custom_field), field_name = "#{name}[custom_field_values][#{custom_field.id}]"
custom_field_tag_name(prefix, custom_value.custom_field), field_name << "[]" if custom_field.multiple?
custom_value, field_id = "#{name}_custom_field_values_#{custom_field.id}"
:class => "#{custom_value.custom_field.field_format}_cf"
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)) +
calendar_for(field_id)
when "text"
text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3))
when "bool"
hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options)
when "list"
blank_option = ''.html_safe
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 => '')
end
else
blank_option = content_tag('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?))
if custom_field.multiple?
s << hidden_field_tag(field_name, '')
end
s
else
text_field_tag(field_name, custom_value.value, tag_options)
end
end end
# Return custom field label tag # Return custom field label tag
def custom_field_label_tag(name, custom_value, options={}) def custom_field_label_tag(name, custom_value, options={})
required = options[:required] || custom_value.custom_field.is_required? required = options[:required] || custom_value.custom_field.is_required?
title = custom_value.custom_field.description.presence
content = content_tag 'span', custom_value.custom_field.name, :title => title
content_tag "label", content + content_tag "label", h(custom_value.custom_field.name) +
(required ? " <span class=\"required\">*</span>".html_safe : ""), (required ? " <span class=\"required\">*</span>".html_safe : ""),
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}" :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
end end
@ -91,30 +77,53 @@ module CustomFieldsHelper
custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value) custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value)
end end
# Returns the custom field tag for when bulk editing objects def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil)
def custom_field_tag_for_bulk_edit(prefix, custom_field, objects=nil, value='') field_name = "#{name}[custom_field_values][#{custom_field.id}]"
custom_field.format.bulk_edit_tag self, field_name << "[]" if custom_field.multiple?
custom_field_tag_id(prefix, custom_field), field_id = "#{name}_custom_field_values_#{custom_field.id}"
custom_field_tag_name(prefix, custom_field),
custom_field, tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
objects,
value, field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
:class => "#{custom_field.field_format}_cf" case field_format.try(:edit_as)
when "date"
text_field_tag(field_name, '', tag_options.merge(:size => 10)) +
calendar_for(field_id)
when "text"
text_area_tag(field_name, '', tag_options.merge(:rows => 3))
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)
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?))
else
text_field_tag(field_name, '', tag_options)
end
end end
# Return a string used to display a custom value # Return a string used to display a custom value
def show_value(custom_value, html=true) def show_value(custom_value)
format_object(custom_value, html) return "" unless custom_value
format_value(custom_value.value, custom_value.custom_field.field_format)
end end
# Return a string used to display a custom value # Return a string used to display a custom value
def format_value(value, custom_field) def format_value(value, field_format)
format_object(custom_field.format.formatted_value(self, custom_field, value, false), false) if value.is_a?(Array)
value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ')
else
Redmine::CustomFieldFormat.format_value(value, field_format)
end
end end
# Return an array of custom field formats which can be used in select_tag # Return an array of custom field formats which can be used in select_tag
def custom_field_formats_for_select(custom_field) def custom_field_formats_for_select(custom_field)
Redmine::FieldFormat.as_select(custom_field.class.customized_class.name) Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name)
end end
# Renders the custom_values in api views # Renders the custom_values in api views
@ -137,8 +146,4 @@ module CustomFieldsHelper
end end
end unless custom_values.empty? end unless custom_values.empty?
end end
def edit_tag_style_tag(form)
form.select :edit_tag_style, [[l(:label_drop_down_list), ''], [l(:label_checkboxes), 'check_box']], :label => :label_display
end
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -94,20 +94,6 @@ module IssuesHelper
s.html_safe s.html_safe
end end
# Returns an array of error messages for bulk edited issues
def bulk_edit_error_messages(issues)
messages = {}
issues.each do |issue|
issue.errors.full_messages.each do |message|
messages[message] ||= []
messages[message] << issue
end
end
messages.map { |message, issues|
"#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
}
end
# Returns a link for adding a new subtask to the given issue # Returns a link for adding a new subtask to the given issue
def link_to_new_subtask(issue) def link_to_new_subtask(issue)
attrs = { attrs = {
@ -160,20 +146,18 @@ module IssuesHelper
end end
def render_custom_fields_rows(issue) def render_custom_fields_rows(issue)
values = issue.visible_custom_field_values return if issue.custom_field_values.empty?
return if values.empty?
ordered_values = [] ordered_values = []
half = (values.size / 2.0).ceil half = (issue.custom_field_values.size / 2.0).ceil
half.times do |i| half.times do |i|
ordered_values << values[i] ordered_values << issue.custom_field_values[i]
ordered_values << values[i + half] ordered_values << issue.custom_field_values[i + half]
end end
s = "<tr>\n" s = "<tr>\n"
n = 0 n = 0
ordered_values.compact.each do |value| ordered_values.compact.each do |value|
css = "cf_#{value.custom_field.id}"
s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0 s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0
s << "\t<th class=\"#{css}\">#{ h(value.custom_field.name) }:</th><td class=\"#{css}\">#{ h(show_value(value)) }</td>\n" s << "\t<th>#{ h(value.custom_field.name) }:</th><td>#{ simple_format_without_paragraph(h(show_value(value))) }</td>\n"
n += 1 n += 1
end end
s << "</tr>\n" s << "</tr>\n"
@ -200,53 +184,51 @@ module IssuesHelper
def sidebar_queries def sidebar_queries
unless @sidebar_queries unless @sidebar_queries
@sidebar_queries = IssueQuery.visible. @sidebar_queries = IssueQuery.visible.all(
order("#{Query.table_name}.name ASC"). :order => "#{Query.table_name}.name ASC",
# Project specific queries and global queries # Project specific queries and global queries
where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]). :conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
all )
end end
@sidebar_queries @sidebar_queries
end end
def query_links(title, queries) def query_links(title, queries)
return '' if queries.empty?
# links to #index on issues/show # links to #index on issues/show
url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
content_tag('h3', title) + "\n" + content_tag('h3', h(title)) +
content_tag('ul', queries.collect {|query|
queries.collect {|query| css = 'query'
css = 'query' css << ' selected' if query == @query
css << ' selected' if query == @query link_to(h(query.name), url_params.merge(:query_id => query), :class => css)
content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css)) }.join('<br />').html_safe
}.join("\n").html_safe,
:class => 'queries'
) + "\n"
end end
def render_sidebar_queries def render_sidebar_queries
out = ''.html_safe out = ''.html_safe
out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?)) queries = sidebar_queries.select {|q| !q.is_public?}
out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?)) out << query_links(l(:label_my_queries), queries) if queries.any?
queries = sidebar_queries.select {|q| q.is_public?}
out << query_links(l(:label_query_plural), queries) if queries.any?
out out
end end
def email_issue_attributes(issue, user) def email_issue_attributes(issue)
items = [] items = []
%w(author status priority assigned_to category fixed_version).each do |attribute| %w(author status priority assigned_to category fixed_version).each do |attribute|
unless issue.disabled_core_fields.include?(attribute+"_id") unless issue.disabled_core_fields.include?(attribute+"_id")
items << "#{l("field_#{attribute}")}: #{issue.send attribute}" items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
end end
end end
issue.visible_custom_field_values(user).each do |value| issue.custom_field_values.each do |value|
items << "#{value.custom_field.name}: #{show_value(value, false)}" items << "#{value.custom_field.name}: #{show_value(value)}"
end end
items items
end end
def render_email_issue_attributes(issue, user, html=false) def render_email_issue_attributes(issue, html=false)
items = email_issue_attributes(issue, user) items = email_issue_attributes(issue)
if html if html
content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe) content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe)
else else
@ -262,23 +244,23 @@ module IssuesHelper
values_by_field = {} values_by_field = {}
details.each do |detail| details.each do |detail|
if detail.property == 'cf' if detail.property == 'cf'
field = detail.custom_field field_id = detail.prop_key
field = CustomField.find_by_id(field_id)
if field && field.multiple? if field && field.multiple?
values_by_field[field] ||= {:added => [], :deleted => []} values_by_field[field_id] ||= {:added => [], :deleted => []}
if detail.old_value if detail.old_value
values_by_field[field][:deleted] << detail.old_value values_by_field[field_id][:deleted] << detail.old_value
end end
if detail.value if detail.value
values_by_field[field][:added] << detail.value values_by_field[field_id][:added] << detail.value
end end
next next
end end
end end
strings << show_detail(detail, no_html, options) strings << show_detail(detail, no_html, options)
end end
values_by_field.each do |field, changes| values_by_field.each do |field_id, changes|
detail = JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s) detail = JournalDetail.new(:property => 'cf', :prop_key => field_id)
detail.instance_variable_set "@custom_field", field
if changes[:added].any? if changes[:added].any?
detail.value = changes[:added] detail.value = changes[:added]
strings << show_detail(detail, no_html, options) strings << show_detail(detail, no_html, options)
@ -321,27 +303,15 @@ module IssuesHelper
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank? old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
end end
when 'cf' when 'cf'
custom_field = detail.custom_field custom_field = CustomField.find_by_id(detail.prop_key)
if custom_field if custom_field
multiple = custom_field.multiple? multiple = custom_field.multiple?
label = custom_field.name label = custom_field.name
value = format_value(detail.value, custom_field) if detail.value value = format_value(detail.value, custom_field.field_format) if detail.value
old_value = format_value(detail.old_value, custom_field) if detail.old_value old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
end end
when 'attachment' when 'attachment'
label = l(:label_attachment) label = l(:label_attachment)
when 'relation'
if detail.value && !detail.old_value
rel_issue = Issue.visible.find_by_id(detail.value)
value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" :
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
elsif detail.old_value && !detail.value
rel_issue = Issue.visible.find_by_id(detail.old_value)
old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" :
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
end
relation_type = IssueRelation::TYPES[detail.prop_key]
label = l(relation_type[:name]) if relation_type
end end
call_hook(:helper_issues_show_detail_after_setting, call_hook(:helper_issues_show_detail_after_setting,
{:detail => detail, :label => label, :value => value, :old_value => old_value }) {:detail => detail, :label => label, :value => value, :old_value => old_value })
@ -353,9 +323,7 @@ module IssuesHelper
unless no_html unless no_html
label = content_tag('strong', label) label = content_tag('strong', label)
old_value = content_tag("i", h(old_value)) if detail.old_value old_value = content_tag("i", h(old_value)) if detail.old_value
if detail.old_value && detail.value.blank? && detail.property != 'relation' old_value = content_tag("del", old_value) if detail.old_value and detail.value.blank?
old_value = content_tag("del", old_value)
end
if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key) if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key)
# Link to the attachment if it has not been removed # Link to the attachment if it has not been removed
value = link_to_attachment(atta, :download => true, :only_path => options[:only_path]) value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
@ -391,7 +359,7 @@ module IssuesHelper
else else
l(:text_journal_set_to, :label => label, :value => value).html_safe l(:text_journal_set_to, :label => label, :value => value).html_safe
end end
when 'attachment', 'relation' when 'attachment'
l(:text_journal_added, :label => label, :value => value).html_safe l(:text_journal_added, :label => label, :value => value).html_safe
end end
else else

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -20,7 +20,7 @@
module ProjectsHelper module ProjectsHelper
def link_to_version(version, options = {}) def link_to_version(version, options = {})
return '' unless version && version.is_a?(Version) return '' unless version && version.is_a?(Version)
link_to_if version.visible?, format_version_name(version), version_path(version), options link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, options
end end
def project_settings_tabs def project_settings_tabs
@ -46,26 +46,11 @@ module ProjectsHelper
end end
options = '' options = ''
options << "<option value=''>&nbsp;</option>" if project.allowed_parents.include?(nil) options << "<option value=''></option>" if project.allowed_parents.include?(nil)
options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected) options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id') content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id')
end end
def render_project_action_links
links = []
if User.current.allowed_to?(:add_project, nil, :global => true)
links << link_to(l(:label_project_new), new_project_path, :class => 'icon icon-add')
end
if User.current.allowed_to?(:view_issues, nil, :global => true)
links << link_to(l(:label_issue_view_all), issues_path)
end
if User.current.allowed_to?(:view_time_entries, nil, :global => true)
links << link_to(l(:label_overall_spent_time), time_entries_path)
end
links << link_to(l(:label_overall_activity), activity_path)
links.join(" | ").html_safe
end
# Renders the projects index # Renders the projects index
def render_project_hierarchy(projects) def render_project_hierarchy(projects)
render_project_nested_lists(projects) do |project| render_project_nested_lists(projects) do |project|
@ -84,11 +69,10 @@ module ProjectsHelper
grouped[version.project.name] << [version.name, version.id] grouped[version.project.name] << [version.name, version.id]
end end
selected = selected.is_a?(Version) ? selected.id : selected
if grouped.keys.size > 1 if grouped.keys.size > 1
grouped_options_for_select(grouped, selected) grouped_options_for_select(grouped, selected && selected.id)
else else
options_for_select((grouped.values.first || []), selected) options_for_select((grouped.values.first || []), selected && selected.id)
end end
end end

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -18,8 +18,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module QueriesHelper module QueriesHelper
include ApplicationHelper
def filters_options_for_select(query) def filters_options_for_select(query)
options_for_select(filters_options(query)) options_for_select(filters_options(query))
end end
@ -58,7 +56,7 @@ module QueriesHelper
def available_block_columns_tags(query) def available_block_columns_tags(query)
tags = ''.html_safe tags = ''.html_safe
query.available_block_columns.each do |column| query.available_block_columns.each do |column|
tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline') tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column)) + " #{column.caption}", :class => 'inline')
end end
tags tags
end end
@ -83,7 +81,7 @@ module QueriesHelper
end end
def column_content(column, issue) def column_content(column, issue)
value = column.value_object(issue) value = column.value(issue)
if value.is_a?(Array) 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.join(', ').html_safe
else else
@ -92,27 +90,53 @@ module QueriesHelper
end end
def column_value(column, issue, value) def column_value(column, issue, value)
case column.name case value.class.name
when :id when 'String'
link_to value, issue_path(issue) if column.name == :subject
when :subject link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
link_to value, issue_path(issue) elsif column.name == :description
when :description issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : '' else
when :done_ratio h(value)
progress_bar(value, :width => '80px') end
when :relations when 'Time'
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
progress_bar(value, :width => '80px')
else
value.to_s
end
when 'Float'
sprintf "%.2f", value
when 'User'
link_to_user value
when 'Project'
link_to_project value
when 'Version'
link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
when 'TrueClass'
l(:general_text_Yes)
when 'FalseClass'
l(:general_text_No)
when 'Issue'
value.visible? ? link_to_issue(value) : "##{value.id}"
when 'IssueRelation'
other = value.other_issue(issue) other = value.other_issue(issue)
content_tag('span', content_tag('span',
(l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe, (l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe,
:class => value.css_classes_for(issue)) :class => value.css_classes_for(issue))
else else
format_object(value) h(value)
end end
end end
def csv_content(column, issue) def csv_content(column, issue)
value = column.value_object(issue) value = column.value(issue)
if value.is_a?(Array) if value.is_a?(Array)
value.collect {|v| csv_value(column, issue, v)}.compact.join(', ') value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
else else
@ -121,16 +145,18 @@ module QueriesHelper
end end
def csv_value(column, issue, value) def csv_value(column, issue, value)
format_object(value, false) do |value| case value.class.name
case value.class.name when 'Time'
when 'Float' format_time(value)
sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator)) when 'Date'
when 'IssueRelation' format_date(value)
other = value.other_issue(issue) when 'Float'
l(value.label_for(issue)) + " ##{other.id}" sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
else when 'IssueRelation'
value other = value.other_issue(issue)
end l(value.label_for(issue)) + " ##{other.id}"
else
value.to_s
end end
end end
@ -159,7 +185,7 @@ module QueriesHelper
if !params[:query_id].blank? if !params[:query_id].blank?
cond = "project_id IS NULL" cond = "project_id IS NULL"
cond << " OR project_id = #{@project.id}" if @project cond << " OR project_id = #{@project.id}" if @project
@query = IssueQuery.where(cond).find(params[:query_id]) @query = IssueQuery.find(params[:query_id], :conditions => cond)
raise ::Unauthorized unless @query.visible? raise ::Unauthorized unless @query.visible?
@query.project = @project @query.project = @project
session[:query] = {:id => @query.id, :project_id => @query.project_id} session[:query] = {:id => @query.id, :project_id => @query.project_id}
@ -172,7 +198,6 @@ module QueriesHelper
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
else else
# retrieve from session # retrieve from session
@query = nil
@query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id] @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 ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
@query.project = @project @query.project = @project

View File

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

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -43,7 +43,7 @@ module RepositoriesHelper
end end
def render_changeset_changes def render_changeset_changes
changes = @changeset.filechanges.limit(1000).reorder('path').collect do |change| changes = @changeset.filechanges.limit(1000).reorder('path').all.collect do |change|
case change.action case change.action
when 'A' when 'A'
# Detects moved/copied files # Detects moved/copied files

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -79,29 +79,17 @@ module SettingsHelper
def setting_label(setting, options={}) def setting_label(setting, options={})
label = options.delete(:label) label = options.delete(:label)
label != false ? label_tag("settings_#{setting}", l(label || "setting_#{setting}"), options[:label_options]).html_safe : '' label != false ? label_tag("settings_#{setting}", l(label || "setting_#{setting}")).html_safe : ''
end end
# Renders a notification field for a Redmine::Notifiable option # Renders a notification field for a Redmine::Notifiable option
def notification_field(notifiable) def notification_field(notifiable)
tag_data = notifiable.parent.present? ? return content_tag(:label,
{:parent_notifiable => notifiable.parent} : check_box_tag('settings[notified_events][]',
{:disables => "input[data-parent-notifiable=#{notifiable.name}]"} notifiable.name,
Setting.notified_events.include?(notifiable.name), :id => nil).html_safe +
tag = check_box_tag('settings[notified_events][]', l_or_humanize(notifiable.name, :prefix => 'label_').html_safe,
notifiable.name, :class => notifiable.parent.present? ? "parent" : '').html_safe
Setting.notified_events.include?(notifiable.name),
:id => nil,
:data => tag_data)
text = l_or_humanize(notifiable.name, :prefix => 'label_')
options = {}
if notifiable.parent.present?
options[:class] = "parent"
end
content_tag(:label, tag + text, options)
end end
def cross_project_subtasks_options def cross_project_subtasks_options

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -96,10 +96,8 @@ module TimelogHelper
else else
obj obj
end end
elsif cf = criteria_options[:custom_field]
format_value(value, cf)
else else
value.to_s format_value(value, criteria_options[:format])
end end
end end

View File

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

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -19,7 +19,7 @@
module UsersHelper module UsersHelper
def users_status_options_for_select(selected) def users_status_options_for_select(selected)
user_count_by_status = User.group('status').count.to_hash user_count_by_status = User.count(:group => 'status').to_hash
options_for_select([[l(:label_all), ''], options_for_select([[l(:label_all), ''],
["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'], ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'],
["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'], ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'],

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -35,9 +35,12 @@ module VersionsHelper
h = Hash.new {|k,v| k[v] = [0, 0]} h = Hash.new {|k,v| k[v] = [0, 0]}
begin begin
# Total issue count # Total issue count
Issue.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][0] = s} Issue.count(:group => criteria,
:conditions => ["#{Issue.table_name}.fixed_version_id = ?", version.id]).each {|c,s| h[c][0] = s}
# Open issues count # Open issues count
Issue.open.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][1] = s} Issue.count(:group => criteria,
:include => :status,
:conditions => ["#{Issue.table_name}.fixed_version_id = ? AND #{IssueStatus.table_name}.is_closed = ?", version.id, false]).each {|c,s| h[c][1] = s}
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
# When grouping by an association, Rails throws this exception if there's no result (bug) # When grouping by an association, Rails throws this exception if there's no result (bug)
end end

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -27,9 +27,8 @@ module WatchersHelper
def watcher_link(objects, user) def watcher_link(objects, user)
return '' unless user && user.logged? return '' unless user && user.logged?
objects = Array.wrap(objects) objects = Array.wrap(objects)
return '' unless objects.any?
watched = Watcher.any_watched?(objects, user) watched = objects.any? {|object| object.watched_by?(user)}
css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ') css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ')
text = watched ? l(:button_unwatch) : l(:button_watch) text = watched ? l(:button_unwatch) : l(:button_watch)
url = watch_path( url = watch_path(

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
# #
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -18,78 +18,15 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module WorkflowsHelper module WorkflowsHelper
def options_for_workflow_select(name, objects, selected, options={})
option_tags = ''.html_safe
multiple = false
if selected
if selected.size == objects.size
selected = 'all'
else
selected = selected.map(&:id)
if selected.size > 1
multiple = true
end
end
else
selected = objects.first.try(:id)
end
all_tag_options = {:value => 'all', :selected => (selected == 'all')}
if multiple
all_tag_options.merge!(:style => "display:none;")
end
option_tags << content_tag('option', l(:label_all), all_tag_options)
option_tags << options_from_collection_for_select(objects, "id", "name", selected)
select_tag name, option_tags, {:multiple => multiple}.merge(options)
end
def field_required?(field) def field_required?(field)
field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field) field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
end end
def field_permission_tag(permissions, status, field, roles) def field_permission_tag(permissions, status, field)
name = field.is_a?(CustomField) ? field.id.to_s : field name = field.is_a?(CustomField) ? field.id.to_s : field
options = [["", ""], [l(:label_readonly), "readonly"]] options = [["", ""], [l(:label_readonly), "readonly"]]
options << [l(:label_required), "required"] unless field_required?(field) options << [l(:label_required), "required"] unless field_required?(field)
html_options = {}
if perm = permissions[status.id][name]
if perm.uniq.size > 1 || perm.size < @roles.size * @trackers.size
options << [l(:label_no_change_option), "no_change"]
selected = 'no_change'
else
selected = perm.first
end
end
hidden = field.is_a?(CustomField) && select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, permissions[status.id][name]))
!field.visible? &&
!roles.detect {|role| role.custom_fields.to_a.include?(field)}
if hidden
options[0][0] = l(:label_hidden)
selected = ''
html_options[:disabled] = true
end
select_tag("permissions[#{status.id}][#{name}]", options_for_select(options, selected), html_options)
end
def transition_tag(workflows, old_status, new_status, name)
w = workflows.select {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id}.size
tag_name = "transitions[#{ old_status.id }][#{new_status.id}][#{name}]"
if w == 0 || w == @roles.size * @trackers.size
hidden_field_tag(tag_name, "0") +
check_box_tag(tag_name, "1", w != 0,
:class => "old-status-#{old_status.id} new-status-#{new_status.id}")
else
select_tag tag_name,
options_for_select([
[l(:general_text_Yes), "1"],
[l(:general_text_No), "0"],
[l(:label_no_change_option), "no_change"]
], "no_change")
end
end end
end end

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -102,7 +102,7 @@ class Attachment < ActiveRecord::Base
if @temp_file && (@temp_file.size > 0) if @temp_file && (@temp_file.size > 0)
self.disk_directory = target_directory self.disk_directory = target_directory
self.disk_filename = Attachment.disk_filename(filename, disk_directory) self.disk_filename = Attachment.disk_filename(filename, disk_directory)
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") if logger logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
path = File.dirname(diskfile) path = File.dirname(diskfile)
unless File.directory?(path) unless File.directory?(path)
FileUtils.mkdir_p(path) FileUtils.mkdir_p(path)
@ -265,25 +265,14 @@ class Attachment < ActiveRecord::Base
# Moves an existing attachment to its target directory # Moves an existing attachment to its target directory
def move_to_target_directory! def move_to_target_directory!
return unless !new_record? & readable? if !new_record? & readable?
src = diskfile
src = diskfile self.disk_directory = target_directory
self.disk_directory = target_directory dest = diskfile
dest = diskfile if src != dest && FileUtils.mkdir_p(File.dirname(dest)) && FileUtils.mv(src, dest)
update_column :disk_directory, disk_directory
return if src == dest end
if !FileUtils.mkdir_p(File.dirname(dest))
logger.error "Could not create directory #{File.dirname(dest)}" if logger
return
end end
if !FileUtils.mv(src, dest)
logger.error "Could not move attachment from #{src} to #{dest}" if logger
return
end
update_column :disk_directory, disk_directory
end end
# Moves existing attachments that are stored at the root of the files # Moves existing attachments that are stored at the root of the files
@ -305,10 +294,10 @@ class Attachment < ActiveRecord::Base
def sanitize_filename(value) def sanitize_filename(value)
# get only the filename, not the whole path # get only the filename, not the whole path
just_filename = value.gsub(/\A.*(\\|\/)/m, '') just_filename = value.gsub(/^.*(\\|\/)/, '')
# Finally, replace invalid characters with underscore # Finally, replace invalid characters with underscore
@filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>\n\r]+/, '_') @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_')
end end
# Returns the subdirectory in which the attachment will be saved # Returns the subdirectory in which the attachment will be saved

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -77,7 +77,7 @@ class AuthSource < ActiveRecord::Base
# Try to authenticate a user not yet registered against available sources # Try to authenticate a user not yet registered against available sources
def self.authenticate(login, password) def self.authenticate(login, password)
AuthSource.where(:onthefly_register => true).each do |source| AuthSource.where(:onthefly_register => true).all.each do |source|
begin begin
logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug? logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
attrs = source.authenticate(login, password) attrs = source.authenticate(login, password)

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -60,10 +60,10 @@ class Board < ActiveRecord::Base
# Updates topics_count, messages_count and last_message_id attributes for +board_id+ # Updates topics_count, messages_count and last_message_id attributes for +board_id+
def self.reset_counters!(board_id) def self.reset_counters!(board_id)
board_id = board_id.to_i board_id = board_id.to_i
where(["id = ?", board_id]). update_all("topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id} AND parent_id IS NULL)," +
update_all("topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id} AND parent_id IS NULL)," +
" messages_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id})," + " messages_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id})," +
" last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})") " last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})",
["id = ?", board_id])
end end
def self.board_tree(boards, parent_id=nil, level=0) def self.board_tree(boards, parent_id=nil, level=0)

View File

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

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -118,25 +118,22 @@ class Changeset < ActiveRecord::Base
ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip) ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
ref_keywords_any = ref_keywords.delete('*') ref_keywords_any = ref_keywords.delete('*')
# keywords used to fix issues # keywords used to fix issues
fix_keywords = Setting.commit_update_keywords_array.map {|r| r['keywords']}.flatten.compact fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|") kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
referenced_issues = [] referenced_issues = []
comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match| comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
action, refs = match[2].to_s.downcase, match[3] action, refs = match[2], match[3]
next unless action.present? || ref_keywords_any next unless action.present? || ref_keywords_any
refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m| refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2] issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
if issue if issue
referenced_issues << issue referenced_issues << issue
# Don't update issues or log time when importing old commits fix_issue(issue) if fix_keywords.include?(action.to_s.downcase)
unless repository.created_on && committed_on && committed_on < repository.created_on log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
fix_issue(issue, action) if fix_keywords.include?(action)
log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
end
end end
end end
end end
@ -154,14 +151,13 @@ class Changeset < ActiveRecord::Base
end end
def text_tag(ref_project=nil) def text_tag(ref_project=nil)
repo = ""
if repository && repository.identifier.present?
repo = "#{repository.identifier}|"
end
tag = if scmid? tag = if scmid?
"commit:#{repo}#{scmid}" "commit:#{scmid}"
else else
"#{repo}r#{revision}" "r#{revision}"
end
if repository && repository.identifier.present?
tag = "#{repository.identifier}|#{tag}"
end end
if ref_project && project && ref_project != project if ref_project && project && ref_project != project
tag = "#{project.identifier}:#{tag}" tag = "#{project.identifier}:#{tag}"
@ -198,7 +194,7 @@ class Changeset < ActiveRecord::Base
# Finds an issue that can be referenced by the commit message # Finds an issue that can be referenced by the commit message
def find_referenced_issue_by_id(id) def find_referenced_issue_by_id(id)
return nil if id.blank? return nil if id.blank?
issue = Issue.includes(:project).where(:id => id.to_i).first issue = Issue.find_by_id(id.to_i, :include => :project)
if Setting.commit_cross_project_ref? if Setting.commit_cross_project_ref?
# all issues can be referenced/fixed # all issues can be referenced/fixed
elsif issue elsif issue
@ -214,26 +210,25 @@ class Changeset < ActiveRecord::Base
private private
# Updates the +issue+ according to +action+ def fix_issue(issue)
def fix_issue(issue, action) status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
if status.nil?
logger.warn("No status matches commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
return issue
end
# the issue may have been updated by the closure of another one (eg. duplicate) # the issue may have been updated by the closure of another one (eg. duplicate)
issue.reload issue.reload
# don't change the status is the issue is closed # don't change the status is the issue is closed
return if issue.status && issue.status.is_closed? return if issue.status && issue.status.is_closed?
journal = issue.init_journal(user || User.anonymous, journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project)))
ll(Setting.default_language, issue.status = status
:text_status_changed_by_changeset, unless Setting.commit_fix_done_ratio.blank?
text_tag(issue.project))) issue.done_ratio = Setting.commit_fix_done_ratio.to_i
rule = Setting.commit_update_keywords_array.detect do |rule|
rule['keywords'].include?(action) &&
(rule['if_tracker_id'].blank? || rule['if_tracker_id'] == issue.tracker_id.to_s)
end
if rule
issue.assign_attributes rule.slice(*Issue.attribute_names)
end end
Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update, Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
{ :changeset => self, :issue => issue, :action => action }) { :changeset => self, :issue => issue })
unless issue.save unless issue.save
logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
end end

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -22,16 +22,5 @@ class Comment < ActiveRecord::Base
validates_presence_of :commented, :author, :comments validates_presence_of :commented, :author, :comments
after_create :send_notification
safe_attributes 'comments' safe_attributes 'comments'
private
def send_notification
mailer_method = "#{commented.class.name.underscore}_comment_added"
if Setting.notified_events.include?(mailer_method)
Mailer.send(mailer_method, self).deliver
end
end
end end

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -15,27 +15,10 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require File.expand_path('../../../test_helper', __FILE__) class CommentObserver < ActiveRecord::Observer
def after_create(comment)
class Redmine::ApiTest::ApiTest < Redmine::ApiTest::Base if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added')
fixtures :users Mailer.news_comment_added(comment).deliver
def setup
Setting.rest_api_enabled = '1'
end
def test_api_should_work_with_protect_from_forgery
ActionController::Base.allow_forgery_protection = true
assert_difference('User.count') do
post '/users.xml', {
:user => {
:login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname',
:mail => 'foo@example.net', :password => 'secret123'}
},
credentials('admin')
assert_response 201
end end
ensure
ActionController::Base.allow_forgery_protection = false
end end
end end

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -19,71 +19,60 @@ class CustomField < ActiveRecord::Base
include Redmine::SubclassFactory include Redmine::SubclassFactory
has_many :custom_values, :dependent => :delete_all has_many :custom_values, :dependent => :delete_all
has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id"
acts_as_list :scope => 'type = \'#{self.class}\'' acts_as_list :scope => 'type = \'#{self.class}\''
serialize :possible_values serialize :possible_values
store :format_store
validates_presence_of :name, :field_format validates_presence_of :name, :field_format
validates_uniqueness_of :name, :scope => :type validates_uniqueness_of :name, :scope => :type
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_inclusion_of :field_format, :in => Proc.new { Redmine::FieldFormat.available_formats } validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
validate :validate_custom_field
validate :validate_custom_field
before_validation :set_searchable before_validation :set_searchable
before_save do |field|
field.format.before_custom_field_save(field)
end
after_save :handle_multiplicity_change after_save :handle_multiplicity_change
after_save do |field|
if field.visible_changed? && field.visible
field.roles.clear
end
end
scope :sorted, lambda { order("#{table_name}.position ASC") } scope :sorted, lambda { order("#{table_name}.position ASC") }
scope :visible, lambda {|*args|
user = args.shift || User.current
if user.admin?
# nop
elsif user.memberships.any?
where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" +
" INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
" INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
" WHERE m.user_id = ?)",
true, user.id)
else
where(:visible => true)
end
}
def visible_by?(project, user=User.current) CUSTOM_FIELDS_TABS = [
visible? || user.admin? {:name => 'IssueCustomField', :partial => 'custom_fields/index',
end :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}
]
def format CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]}
@format ||= Redmine::FieldFormat.find(field_format)
end
def field_format=(arg) def field_format=(arg)
# cannot change format of a saved custom field # cannot change format of a saved custom field
if new_record? super if new_record?
@format = nil
super
end
end end
def set_searchable def set_searchable
# make sure these fields are not searchable # make sure these fields are not searchable
self.searchable = false unless format.class.searchable_supported self.searchable = false if %w(int float date bool).include?(field_format)
# make sure only these fields can have multiple values # make sure only these fields can have multiple values
self.multiple = false unless format.class.multiple_supported self.multiple = false unless %w(list user version).include?(field_format)
true true
end end
def validate_custom_field def validate_custom_field
format.validate_custom_field(self).each do |attribute, message| if self.field_format == "list"
errors.add attribute, message errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
end end
if regexp.present? if regexp.present?
@ -94,49 +83,78 @@ class CustomField < ActiveRecord::Base
end end
end end
if default_value.present? if default_value.present? && !valid_field_value?(default_value)
validate_field_value(default_value).each do |message| errors.add(:default_value, :invalid)
errors.add :default_value, message
end
end end
end end
def possible_custom_value_options(custom_value) def possible_values_options(obj=nil)
format.possible_custom_value_options(custom_value) case field_format
end when 'user', 'version'
if obj.respond_to?(:project) && obj.project
def possible_values_options(object=nil) case field_format
if object.is_a?(Array) when 'user'
object.map {|o| format.possible_values_options(self, o)}.reduce(:&) || [] obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
when 'version'
obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
end
elsif obj.is_a?(Array)
obj.collect {|o| possible_values_options(o)}.reduce(:&)
else
[]
end
when 'bool'
[[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
else else
format.possible_values_options(self, object) || [] possible_values || []
end end
end end
def possible_values def possible_values(obj=nil)
values = read_attribute(:possible_values) case field_format
if values.is_a?(Array) when 'user', 'version'
values.each do |value| possible_values_options(obj).collect(&:last)
value.force_encoding('UTF-8') if value.respond_to?(:force_encoding) when 'bool'
end ['1', '0']
values
else 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 || []
end end
end end
# Makes possible_values accept a multiline string # Makes possible_values accept a multiline string
def possible_values=(arg) def possible_values=(arg)
if arg.is_a?(Array) if arg.is_a?(Array)
values = arg.compact.collect(&:strip).select {|v| !v.blank?} super(arg.compact.collect(&:strip).select {|v| !v.blank?})
write_attribute(:possible_values, values)
else else
self.possible_values = arg.to_s.split(/[\n\r]+/) self.possible_values = arg.to_s.split(/[\n\r]+/)
end end
end end
def cast_value(value) def cast_value(value)
format.cast_value(self, value) casted = nil
unless value.blank?
case field_format
when 'string', 'text', 'list'
casted = value
when 'date'
casted = begin; value.to_date; rescue; nil end
when 'bool'
casted = (value == '1' ? true : false)
when 'int'
casted = value.to_i
when 'float'
casted = value.to_f
when 'user', 'version'
casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
end
end
casted
end end
def value_from_keyword(keyword, customized) def value_from_keyword(keyword, customized)
@ -160,46 +178,80 @@ class CustomField < ActiveRecord::Base
# Returns nil if the custom field can not be used for sorting. # Returns nil if the custom field can not be used for sorting.
def order_statement def order_statement
return nil if multiple? return nil if multiple?
format.order_statement(self) 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, '')"
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)
else
nil
end
end end
# Returns a GROUP BY clause that can used to group by custom value # 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. # Returns nil if the custom field can not be used for grouping.
def group_statement def group_statement
return nil if multiple? return nil if multiple?
format.group_statement(self) case field_format
when 'list', 'date', 'bool', 'int'
order_statement
when 'user', 'version'
"COALESCE(#{join_alias}.value, '')"
else
nil
end
end end
def join_for_order_statement def join_for_order_statement
format.join_for_order_statement(self) case field_format
end when 'user', 'version'
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil) " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
if visible? || user.admin? " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
"1=1" " AND #{join_alias}.custom_field_id = #{id}" +
elsif user.anonymous? " AND #{join_alias}.value <> ''" +
"1=0" " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
else " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
project_key ||= "#{self.class.customized_class.table_name}.project_id" " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
id_column ||= id " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
"#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" + " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
" INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" + " ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id"
" INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" + when 'int', 'float'
" WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id_column})" "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
end end
def self.visibility_condition def join_alias
if user.admin? "cf_#{id}"
"1=1" end
elsif user.anonymous?
"#{table_name}.visible" def value_join_alias
else join_alias + "_" + field_format
"#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
" INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
" INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
" WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
end
end end
def <=>(field) def <=>(field)
@ -208,12 +260,17 @@ class CustomField < ActiveRecord::Base
# Returns the class that values represent # Returns the class that values represent
def value_class def value_class
format.target_class if format.respond_to?(:target_class) case field_format
when 'user', 'version'
field_format.classify.constantize
else
nil
end
end end
def self.customized_class def self.customized_class
self.name =~ /^(.+)CustomField$/ self.name =~ /^(.+)CustomField$/
$1.constantize rescue nil begin; $1.constantize; rescue nil; end
end end
# to move in project_custom_field # to move in project_custom_field
@ -227,8 +284,7 @@ class CustomField < ActiveRecord::Base
# Returns the error messages for the given value # Returns the error messages for the given value
# or an empty array if value is a valid value for the custom field # or an empty array if value is a valid value for the custom field
def validate_custom_value(custom_value) def validate_field_value(value)
value = custom_value.value
errs = [] errs = []
if value.is_a?(Array) if value.is_a?(Array)
if !multiple? if !multiple?
@ -237,20 +293,16 @@ class CustomField < ActiveRecord::Base
if is_required? && value.detect(&:present?).nil? if is_required? && value.detect(&:present?).nil?
errs << ::I18n.t('activerecord.errors.messages.blank') errs << ::I18n.t('activerecord.errors.messages.blank')
end end
value.each {|v| errs += validate_field_value_format(v)}
else else
if is_required? && value.blank? if is_required? && value.blank?
errs << ::I18n.t('activerecord.errors.messages.blank') errs << ::I18n.t('activerecord.errors.messages.blank')
end end
errs += validate_field_value_format(value)
end end
errs += format.validate_custom_value(custom_value)
errs errs
end end
# Returns the error messages for the default custom field value
def validate_field_value(value)
validate_custom_value(CustomValue.new(:custom_field => self, :value => value))
end
# Returns true if value is a valid value for the custom field # Returns true if value is a valid value for the custom field
def valid_field_value?(value) def valid_field_value?(value)
validate_field_value(value).empty? validate_field_value(value).empty?
@ -262,6 +314,29 @@ class CustomField < ActiveRecord::Base
protected protected
# Returns the error message for the given value regarding its format
def validate_field_value_format(value)
errs = []
if value.present?
errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
# Format specific validations
case field_format
when 'int'
errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
when 'float'
begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
when 'date'
errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
when 'list'
errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
end
end
errs
end
# Removes multiple values for the custom field after setting the multiple attribute to false # 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 # We kepp the value with the highest id for each customized object
def handle_multiplicity_change def handle_multiplicity_change
@ -278,5 +353,3 @@ class CustomField < ActiveRecord::Base
end end
end end
end end
require_dependency 'redmine/field_format'

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