Compare commits

..
135 Commits
Author SHA1 Message Date
Toshi MARUYAMA bc79904e4e Merged r4806 from trunk.
scm: change gunzip to tar -z option for scm repository setup in lib/tasks/testing.rake.

Pipe does not work on Mingw Ruby.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@6271 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 00:48:46 +00:00
Toshi MARUYAMA ffc111bd23 Merged r6127 from trunk.
Escape AuthSources in the list.

Contributed by MAEDA, Go

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@6129 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-27 23:21:45 +00:00
Toshi MARUYAMA 2a2e705e9e Merged r5743 from trunk.
Russian "default_issue_status_rejected" and "default_tracker_feature" changed.

Contributed by Dmitry Babenko.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5744 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-11 23:03:23 +00:00
Toshi MARUYAMA 31cd20f2b6 Merged r5740 from trunk.
Hungarian translation for 1.1.3 updated by Gábor Takács.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5742 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-11 22:31:54 +00:00
Toshi MARUYAMA 4fc2e757f6 Merged r5644 from trunk.
scm: git: add comments of revision order in fetch_changesets().

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5646 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-04 23:43:40 +00:00
Toshi MARUYAMA 240254b1a3 Merged r5643 from trunk.
scm: git: rearrange fetch_changesets() comment.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5645 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-04 23:42:58 +00:00
Toshi MARUYAMA 95f3c336d5 Merged r5621 from trunk.
Fix a typo in Czech localization.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5623 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-04 00:43:17 +00:00
Toshi MARUYAMA d63e3a781f Merged r5620 from trunk.
Simplified Chinese translation updated by Sam Qiu.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5622 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-04 00:42:54 +00:00
Toshi MARUYAMA 0aab60943c Merged r5614 from trunk.
Fix a typo in public/help/wiki_syntax_detailed.html.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5616 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-02 23:17:02 +00:00
Toshi MARUYAMA 935306af80 Merged r5611 from trunk.
Fix potential Execution After Redirect bugs.

Execution After Redirect (EAR) happens when redirect in a controller is
triggered but there still is code that is executed in the action.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5613 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-01 23:20:46 +00:00
Toshi MARUYAMA 6fce2170c4 Merged r5610 from trunk.
Add missing fixture when running tests from scratch in functional RolesControllerTest.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5612 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-01 23:20:04 +00:00
Jean-Philippe Lang 3f302b66ce Updates for 1.1.3 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5592 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 09:23:34 +00:00
Jean-Philippe Lang 32022267af Merged r5285 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5591 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 09:07:40 +00:00
Jean-Philippe Lang f078f7127c Merged r5283 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5590 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 09:06:32 +00:00
Jean-Philippe Lang c2cd4b7f48 Merged r5176 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5589 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 09:02:29 +00:00
Jean-Philippe Lang 0b5b8bebd1 Merged r5171 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5588 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 09:01:37 +00:00
Jean-Philippe Lang 9f36f18b39 Merged r4735 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5587 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 08:59:30 +00:00
Jean-Philippe Lang fab0774c4b Fixes Prototypejs Form.serialize() for multiple selects (#7954).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5586 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:38:24 +00:00
Jean-Philippe Lang 93c0b120de Merged r5469 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5585 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:37:03 +00:00
Jean-Philippe Lang e20191e666 Backported r5581 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5584 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:34:09 +00:00
Jean-Philippe Lang 8d3b32644b Merged r5330 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5583 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:31:21 +00:00
Jean-Philippe Lang 6153d5ab83 Merged r5265 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5582 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:29:23 +00:00
Jean-Philippe Lang f49904569d Merged r5215 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5580 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:16:39 +00:00
Jean-Philippe Lang f48460da4f Merged r5300 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5579 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:15:46 +00:00
Jean-Philippe Lang bd55d7f815 Merged r5232 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5578 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:14:52 +00:00
Jean-Philippe Lang 4402e7e232 Merged r5214 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5577 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:13:59 +00:00
Jean-Philippe Lang b43ebcbdc4 Merged r5186 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5576 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:12:52 +00:00
Jean-Philippe Lang 55fd2f5562 Merged r5185 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5575 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:11:56 +00:00
Jean-Philippe Lang 8a734f9997 Merged r5157 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5574 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:10:27 +00:00
Jean-Philippe Lang d60949ca87 Merged r5181 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5573 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:09:19 +00:00
Jean-Philippe Lang aef8228b0e Merged r5100 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5572 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:08:39 +00:00
Jean-Philippe Lang 20e6652109 Merged r5236 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5571 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:07:28 +00:00
Jean-Philippe Lang d2bc5a9473 Merged r5105 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5570 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:05:41 +00:00
Jean-Philippe Lang 23e75d87d3 Merged r5097 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5569 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:04:23 +00:00
Jean-Philippe Lang 8a13595c64 Merged r5230 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5568 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:02:26 +00:00
Jean-Philippe Lang 7845843596 Merged r5225 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5567 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-29 07:00:16 +00:00
Toshi MARUYAMA f26a504045 Merged r5557 from trunk.
Delete doc/git.rdoc.

http://www.redmine.org/projects/redmine/wiki/Contribute?version=27 has the github mirror link.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5558 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-27 22:43:42 +00:00
Toshi MARUYAMA f274193893 Merged r5555 from trunk.
Remove obsolete github descriptions from doc/git.rdoc.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5556 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-27 14:08:01 +00:00
Toshi MARUYAMA 0ba400834c Merged r5553 from trunk.
Fix notice_failed_to_save_issues format in es, gl and ca locales.

Contributed by Jose M. Prieto.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5554 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-27 10:51:10 +00:00
Toshi MARUYAMA e6148cfdd4 Merged r5502 from trunk.
Simplified Chinese translation updated by Peng Wang.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5503 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-18 04:29:28 +00:00
Toshi MARUYAMA 99b1b95222 Merged r5500 from trunk.
Czech translation updated by Lubor Nosek.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5501 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-17 23:42:31 +00:00
Toshi MARUYAMA a9a5870d25 Merged r4901 from trunk.
Updated basque and czech translations. Contributed by Ales Zabala Alava and Michal Gebauer.

#7390 depends on this commit.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5497 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-17 16:26:53 +00:00
Toshi MARUYAMA f09f2e8e63 scm: git: backout r5336 (#8081, #8083).
Git on Redmine CI Server does not support "--no-decorate" option of "git log".

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5347 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-06 06:59:50 +00:00
Toshi MARUYAMA 2a1203bfef scm: git: backout r5340 (#8081, #8083).
Git on Redmine CI Server does not support "--no-decorate" option of "git log".

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5346 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-06 06:58:47 +00:00
Toshi MARUYAMA f6918d0242 Merged r5338 from trunk.
scm: git: add "decorate = short" in config log section of test repository.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5340 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-06 02:52:59 +00:00
Toshi MARUYAMA e338f522dd Merged r5334 from trunk.
scm: git: add "--no-decorate" option in "git log".

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5336 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-05 23:18:29 +00:00
Jean-Philippe Lang ab10e187a6 Merged r5322 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5323 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-05 12:17:43 +00:00
Toshi MARUYAMA 12801275a8 Merged r5281 from trunk.
r5256 in trunk and r5271 in 1.1-stable fixed #7794 completely.
r5253 (r5183) in trunk and r5184 effect the width of the ASCII character of Japanese PDF.

Contributed by Jun NAITOH.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5282 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-01 15:54:05 +00:00
Toshi MARUYAMA e35e04de8e PDF: fix the width of the ASCII character of Japanese PDF (#7794).
r5256 in trunk has this change.
So, there is no need to commit in trunk.

Contributed by Jun NAITOH.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5271 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-04-01 01:26:38 +00:00
Toshi MARUYAMA 3ca48e1ecf Merged r5233 from trunk.
i18n: fix typo general_pdf_encoding "UFT-8" in sl.yml.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5234 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-28 01:14:15 +00:00
Toshi MARUYAMA f49bbf48e7 Merged r5183 from trunk.
Fix an internal server error on formatting an issue as a PDF in Japanese.

Contributed by Yuki Sonoda.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5184 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-21 06:17:06 +00:00
Toshi MARUYAMA 891ed84fe3 Merged r5091 from trunk.
scm: mercurial: add :order => 'id DESC' explicitly for MySQL test fails.

Because :order => 'id DESC' is defined at 'has_many',
there is no need to set 'order'.
But, MySQL test fails.
Sqlite3 and PostgreSQL pass.
Is this MySQL bug?

MySQL svn trunk test on Redmine CI server fails.
But, svn 1.1-stable passes.
If this is MySQL bug, this effects 1.1-stable, too.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5092 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-11 07:46:23 +00:00
Jean-Philippe Lang 78a4a995e6 Merged r5030 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5031 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-07 19:27:14 +00:00
Jean-Philippe Lang 465534a298 Merged r5021 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5022 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-06 16:12:27 +00:00
Jean-Philippe Lang d5f1bd07b2 Updates for 1.1.2 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5020 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-06 13:19:48 +00:00
Jean-Philippe Lang 50863117b8 Translations updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5018 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-06 13:12:50 +00:00
Jean-Philippe Lang c6693fc78b Merged r4937 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5017 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-06 13:08:02 +00:00
Jean-Philippe Lang 1e01711e3d Merged r4946 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5015 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-06 12:57:03 +00:00
Jean-Philippe Lang 1ab7f6f930 Merged r4820 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5014 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-06 12:54:59 +00:00
Jean-Philippe Lang f6f7467cdd Merged r4913, r4914, r4916 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5013 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-06 12:51:10 +00:00
Jean-Philippe Lang 4d0a955d3c Merged r5004 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5012 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-06 12:48:40 +00:00
Jean-Philippe Lang c45044f13c Merged r4888 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5011 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-06 12:47:05 +00:00
Jean-Philippe Lang 4761e55691 Merged r4951 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@5010 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-03-06 12:45:56 +00:00
Jean-Philippe Lang af7fb657f4 Merged r4889 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4973 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-28 20:36:06 +00:00
Jean-Philippe Lang 0805ab943e Merged r4890 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4972 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-28 20:34:31 +00:00
Jean-Philippe Lang 9589c0bcad Merged r4811 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4971 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-28 20:33:31 +00:00
Jean-Philippe Lang 91295ea6cd Merged r4935 and r4947 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4970 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-28 20:32:25 +00:00
Jean-Philippe Lang d0f4b5aa50 Merged r4968 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4969 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-28 20:27:44 +00:00
Jean-Philippe Lang 008e8d4fbf Merged r4965 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4966 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-28 16:45:02 +00:00
Toshi MARUYAMA 1e50fea55a Merged r4860 from trunk.
scm: fix diff revision param validation.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4877 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-18 13:48:29 +00:00
Toshi MARUYAMA 008d38d6b4 Merged r4816 from trunk.
scm: fix non ASCII filename downloaded from repo is broken on Internet Explorer.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4819 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-12 09:51:43 +00:00
Toshi MARUYAMA c61c9e6471 Merged r4815 from trunk.
scm: cvs: fix most binary files become corrupted on Windows.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4818 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-12 09:51:21 +00:00
Jean-Baptiste Barth e5b5b61d6e Merged r4813 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4814 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-12 09:03:36 +00:00
Jean-Baptiste Barth 6a8cdf54b3 Removed .project file and added it to svn:ignore'd files. #7497
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4812 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-12 08:56:19 +00:00
Jean-Baptiste Barth e43d98a6f5 Merged r4799 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4800 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-02-06 00:34:21 +00:00
Jean-Philippe Lang 11b774d39d Merged r4784 from trunk (update for 1.1.1 release).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4785 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 14:38:25 +00:00
Jean-Philippe Lang 77c4667dbc Merged r4782 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4783 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 09:13:36 +00:00
Jean-Philippe Lang f05fdd5cfa Merged r4780 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4781 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:54:51 +00:00
Jean-Philippe Lang a0bb70ed2d Merged r4765 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4779 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:45:15 +00:00
Jean-Philippe Lang 1d5c3f7fba Translations updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4778 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:43:27 +00:00
Jean-Philippe Lang 8b83aa1470 Merged r4775 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4776 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:32:10 +00:00
Jean-Philippe Lang 3a92721af4 Merged r4739 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4774 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:22:38 +00:00
Jean-Philippe Lang b3218ba4d4 Merged r4700 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4773 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:16:06 +00:00
Jean-Philippe Lang 975ee2b522 Merged r4701 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4772 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:11:51 +00:00
Jean-Philippe Lang 27a319e66d Merged r4727 and r4730 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4771 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:10:36 +00:00
Jean-Philippe Lang 7be5bf6e4d Merged r4741 and r4764 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4770 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:08:01 +00:00
Jean-Philippe Lang c73d4042d1 Merged r4677 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4769 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:05:54 +00:00
Jean-Philippe Lang 8270ad1e64 Merged r4736 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4768 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:04:40 +00:00
Jean-Philippe Lang 26016fbf43 Merged r4761 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4767 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:02:16 +00:00
Jean-Philippe Lang 307e4ceaa2 Merged r4708 and r4709 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4766 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-30 06:00:56 +00:00
Jean-Philippe Lang 03085e85f9 Merged r4720, r4724, r4738 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4763 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-27 17:51:01 +00:00
Jean-Philippe Lang 1f4e0dc10c Merged r4719 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4762 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-27 17:44:40 +00:00
Toshi MARUYAMA f69c95306d Merged r4749 from trunk.
scm: darcs: fix Darcs adapter recognizes new files as modified files above Darcs 2.4.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4751 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-23 04:21:57 +00:00
Toshi MARUYAMA 634ede3e8b Merged r4748 from trunk.
scm: darcs: add compatible test of Darcs 2.3 and 2.4.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4750 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-23 04:21:34 +00:00
Toshi MARUYAMA c2a2979189 Merged r4744 from trunk.
scm: darcs: switch '.' or @url at entries() in darcs version.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4747 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-23 04:03:04 +00:00
Toshi MARUYAMA 9800469943 Merged r4743 from trunk.
scm: darcs: change io.gets to io.read and add darcs version unit test.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4746 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-23 04:02:41 +00:00
Toshi MARUYAMA e47f1d5595 Merged r4742 from trunk.
scm: darcs: add unit lib test.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4745 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-23 04:02:15 +00:00
Jean-Philippe Lang 135fe04d02 Merged r4681 from trunk (missing fixtures breaking tests).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4732 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-16 16:39:19 +00:00
Jean-Philippe Lang 794d7c0959 Merged r4680 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4728 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-16 14:44:38 +00:00
Toshi MARUYAMA b8f365f2a1 Merged r4713 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4717 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-14 23:37:50 +00:00
Toshi MARUYAMA 6188b9eddb Merged r4710 and r4714 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4716 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-14 23:37:30 +00:00
Toshi MARUYAMA 96c4dc3f1e Merged r4712 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4715 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-14 23:37:10 +00:00
Toshi MARUYAMA b877215261 Merged r4703 from trunk (scm: fix error on revision page for empty revision).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4707 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-13 12:10:52 +00:00
Toshi MARUYAMA 788b143596 Merged r4665 from trunk.
scm: add compatible functional test fof changing diff revisions label at SCM adapter level.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4706 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-13 12:10:30 +00:00
Toshi MARUYAMA 6a261eb5a0 Merged r4691 from trunk (fix assert_equal parameter order in subversion and git unit test).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4693 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-11 15:28:23 +00:00
Toshi MARUYAMA 007fbc00cf Merged r4687 from trunk (fix setup mercurial test repository).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4689 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-11 08:46:27 +00:00
Toshi MARUYAMA 9b8b4b3bfc Merged r4683 from trunk (test:scm:setup:mercurial task can be simpler).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4685 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-11 04:22:52 +00:00
Toshi MARUYAMA 6734f91a72 Merged r4676 from trunk (change mercurial test repository from tar.gz to bundle).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4684 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-11 04:22:31 +00:00
Jean-Philippe Lang 5266346315 Set the version to stable (#7259).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4678 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-10 18:13:09 +00:00
Toshi MARUYAMA d426e4452b Merged r4644 and r4674 from trunk (mercurial unit app test).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4675 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-09 23:39:46 +00:00
Jean-Philippe Lang f8af1bebd7 Merged r4670 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4671 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-09 15:58:15 +00:00
Jean-Philippe Lang 6e695a4d1a Merged r4645 to r4651 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4660 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-08 10:14:00 +00:00
Jean-Philippe Lang e5b7c94cb4 Merged r4643 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4659 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-08 10:09:04 +00:00
Jean-Philippe Lang 9028e664c4 Merged r4656 and r4657 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4658 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-08 10:04:38 +00:00
Toshi MARUYAMA 6318affd31 Merged r4652 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4653 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-08 00:24:43 +00:00
Jean-Philippe Lang 8794cd3344 Merged r4610 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4649 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-06 21:04:24 +00:00
Toshi MARUYAMA 2fcd4e5271 Merged r4636 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4642 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:12:08 +00:00
Toshi MARUYAMA ea60705ca7 Merged r4635 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4641 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:11:47 +00:00
Toshi MARUYAMA 90bce4e366 Merged r4634 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4640 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:11:21 +00:00
Toshi MARUYAMA 9f7cc355ad Merged r4633 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4639 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:10:58 +00:00
Toshi MARUYAMA 6ee4c0bac7 Merged r4632 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4638 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:10:38 +00:00
Toshi MARUYAMA b0f0bd1848 Merged r4631 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4637 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:10:17 +00:00
Toshi MARUYAMA fe563a8802 Merged r4629 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4630 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-04 16:39:45 +00:00
Toshi MARUYAMA 8ebab00767 Merged r4625 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4628 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-03 10:55:07 +00:00
Toshi MARUYAMA d97297e45d Merged r4624 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4627 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-03 10:54:29 +00:00
Toshi MARUYAMA f06500dcce Merged r4623 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4626 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-03 10:53:47 +00:00
Jean-Philippe Lang b098e2b63f Merged r4621 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4622 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-02 14:37:55 +00:00
Jean-Philippe Lang 151a49b319 Merged r4618 and r4619 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4620 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-02 12:17:39 +00:00
Jean-Philippe Lang ded234794e Merged r4615 and r4616 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4617 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-02 11:44:19 +00:00
Toshi MARUYAMA 119732c3ee Merged r4613 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4614 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-02 09:52:29 +00:00
Toshi MARUYAMA 730fcef844 Merged r4611 from trunk (Mercurial sorting).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4612 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-02 06:14:59 +00:00
Toshi MARUYAMA 1cb33f3a95 Merged r4608 from trunk (repository: switch darcs cat test if cat supports).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4609 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-01 22:05:48 +00:00
Toshi MARUYAMA 69edd3c53f Merged r4606 from trunk (.hgignore).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4607 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-01 21:20:25 +00:00
Jean-Philippe Lang ad784e2146 Merged locales updates (r4592 to 4595) from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4596 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-12-30 15:54:47 +00:00
Jean-Philippe Lang c783ae4b3d 1.1-stable branch added
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4585 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-12-30 15:09:18 +00:00
1640 changed files with 62362 additions and 87159 deletions
-10
View File
@@ -1,18 +1,14 @@
/.project /.project
/.loadpath /.loadpath
/config/additional_environment.rb /config/additional_environment.rb
/config/configuration.yml
/config/database.yml /config/database.yml
/config/email.yml /config/email.yml
/config/initializers/session_store.rb /config/initializers/session_store.rb
/config/initializers/secret_token.rb
/coverage /coverage
/db/*.db /db/*.db
/db/*.sqlite3 /db/*.sqlite3
/db/schema.rb /db/schema.rb
/files/* /files/*
/lib/redmine/scm/adapters/mercurial/redminehelper.pyc
/lib/redmine/scm/adapters/mercurial/redminehelper.pyo
/log/*.log* /log/*.log*
/log/mongrel_debug /log/mongrel_debug
/public/dispatch.* /public/dispatch.*
@@ -22,11 +18,5 @@
/tmp/sessions/* /tmp/sessions/*
/tmp/sockets/* /tmp/sockets/*
/tmp/test/* /tmp/test/*
/vendor/cache
/vendor/rails /vendor/rails
*.rbc *.rbc
/.bundle
/Gemfile.lock
/Gemfile.local
-13
View File
@@ -3,18 +3,14 @@ syntax: glob
.project .project
.loadpath .loadpath
config/additional_environment.rb config/additional_environment.rb
config/configuration.yml
config/database.yml config/database.yml
config/email.yml config/email.yml
config/initializers/session_store.rb config/initializers/session_store.rb
config/initializers/secret_token.rb
coverage coverage
db/*.db db/*.db
db/*.sqlite3 db/*.sqlite3
db/schema.rb db/schema.rb
files/* files/*
lib/redmine/scm/adapters/mercurial/redminehelper.pyc
lib/redmine/scm/adapters/mercurial/redminehelper.pyo
log/*.log* log/*.log*
log/mongrel_debug log/mongrel_debug
public/dispatch.* public/dispatch.*
@@ -24,14 +20,5 @@ tmp/cache/*
tmp/sessions/* tmp/sessions/*
tmp/sockets/* tmp/sockets/*
tmp/test/* tmp/test/*
vendor/cache
vendor/rails vendor/rails
*.rbc *.rbc
.svn/
.git/
.bundle
Gemfile.lock
Gemfile.local
-90
View File
@@ -1,90 +0,0 @@
source 'http://rubygems.org'
gem 'rails', '3.2.5'
gem 'prototype-rails', '3.2.1'
gem "i18n", "~> 0.6.0"
gem "coderay", "~> 1.0.6"
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
gem "builder"
# Optional gem for LDAP authentication
group :ldap do
gem "net-ldap", "~> 0.3.1"
end
# Optional gem for OpenID authentication
group :openid do
gem "ruby-openid", "~> 2.1.4", :require => "openid"
gem "rack-openid"
end
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
platforms :mri, :mingw do
group :rmagick do
# RMagick 2 supports ruby 1.9
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support
# different requirements for the same gem on different platforms
gem "rmagick", ">= 2.0.0"
end
end
# Database gems
platforms :mri, :mingw do
group :postgresql do
gem "pg", ">= 0.11.0"
end
group :sqlite do
gem "sqlite3"
end
end
platforms :mri_18, :mingw_18 do
group :mysql do
gem "mysql"
end
end
platforms :mri_19, :mingw_19 do
group :mysql do
gem "mysql2", "~> 0.3.11"
end
end
platforms :jruby do
gem "jruby-openssl"
group :mysql do
gem "activerecord-jdbcmysql-adapter"
end
group :postgresql do
gem "activerecord-jdbcpostgresql-adapter"
end
group :sqlite do
gem "activerecord-jdbcsqlite3-adapter"
end
end
group :development do
gem "rdoc", ">= 2.4.2"
gem "yard"
end
group :test do
gem "shoulda", "~> 2.11"
gem "mocha"
end
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
if File.exists?(local_gemfile)
puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v`
instance_eval File.read(local_gemfile)
end
# Load plugins' Gemfiles
Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file|
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
instance_eval File.read(file)
end
+1 -1
View File
@@ -2,4 +2,4 @@
Redmine is a flexible project management web application written using Ruby on Rails framework. Redmine is a flexible project management web application written using Ruby on Rails framework.
More details can be found in the doc directory or on the official website http://www.redmine.org More details can be found at in the doc directory or on the official website http://www.redmine.org
+7 -4
View File
@@ -1,7 +1,10 @@
#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake, # Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. # for example lib/tasks/switchtower.rake, and they will automatically be available to Rake.
require File.expand_path('../config/application', __FILE__) require(File.join(File.dirname(__FILE__), 'config', 'boot'))
RedmineApp::Application.load_tasks require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'tasks/rails'
+32 -39
View File
@@ -1,24 +1,24 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2009 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class AccountController < ApplicationController class AccountController < ApplicationController
helper :custom_fields helper :custom_fields
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 skip_before_filter :check_if_login_required
@@ -29,9 +29,6 @@ class AccountController < ApplicationController
else else
authenticate_user authenticate_user
end end
rescue AuthSourceException => e
logger.error "An error occured when authenticating #{params[:username]}: #{e.message}"
render_error :message => e.message
end end
# Log out current user and redirect to welcome page # Log out current user and redirect to welcome page
@@ -39,7 +36,7 @@ class AccountController < ApplicationController
logout_user logout_user
redirect_to home_url redirect_to home_url
end end
# Enable user to choose a new password # Enable user to choose a new password
def lost_password def lost_password
redirect_to(home_url) && return unless Setting.lost_password? redirect_to(home_url) && return unless Setting.lost_password?
@@ -54,7 +51,7 @@ class AccountController < ApplicationController
flash[:notice] = l(:notice_account_password_updated) flash[:notice] = l(:notice_account_password_updated)
redirect_to :action => 'login' redirect_to :action => 'login'
return return
end end
end end
render :template => "account/password_recovery" render :template => "account/password_recovery"
return return
@@ -68,7 +65,7 @@ class AccountController < ApplicationController
# create a new token for password recovery # create a new token for password recovery
token = Token.new(:user => user, :action => "recovery") token = Token.new(:user => user, :action => "recovery")
if token.save if token.save
Mailer.lost_password(token).deliver Mailer.deliver_lost_password(token)
flash[:notice] = l(:notice_account_lost_email_sent) flash[:notice] = l(:notice_account_lost_email_sent)
redirect_to :action => 'login' redirect_to :action => 'login'
return return
@@ -76,7 +73,7 @@ class AccountController < ApplicationController
end end
end end
end end
# User self-registration # User self-registration
def register def register
redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration] redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
@@ -84,8 +81,7 @@ class AccountController < ApplicationController
session[:auth_source_registration] = nil session[:auth_source_registration] = nil
@user = User.new(:language => Setting.default_language) @user = User.new(:language => Setting.default_language)
else else
@user = User.new @user = User.new(params[:user])
@user.safe_attributes = params[:user]
@user.admin = false @user.admin = false
@user.register @user.register
if session[:auth_source_registration] if session[:auth_source_registration]
@@ -100,7 +96,7 @@ class AccountController < ApplicationController
end end
else else
@user.login = params[:user][:login] @user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
case Setting.self_registration case Setting.self_registration
when '1' when '1'
@@ -113,7 +109,7 @@ class AccountController < ApplicationController
end end
end end
end end
# Token based account activation # Token based account activation
def activate def activate
redirect_to(home_url) && return unless Setting.self_registration? && params[:token] redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
@@ -128,9 +124,17 @@ class AccountController < ApplicationController
end end
redirect_to :action => 'login' redirect_to :action => 'login'
end end
private private
def logout_user
if User.current.logged?
cookies.delete :autologin
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
self.logged_user = nil
end
end
def authenticate_user def authenticate_user
if Setting.openid? && using_open_id? if Setting.openid? && using_open_id?
open_id_authenticate(params[:openid_url]) open_id_authenticate(params[:openid_url])
@@ -152,8 +156,9 @@ class AccountController < ApplicationController
end end
end end
def open_id_authenticate(openid_url) def open_id_authenticate(openid_url)
authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url, :method => :post) do |result, identity_url, registration| authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url) do |result, identity_url, registration|
if result.successful? if result.successful?
user = User.find_or_initialize_by_identity_url(identity_url) user = User.find_or_initialize_by_identity_url(identity_url)
if user.new_record? if user.new_record?
@@ -180,7 +185,7 @@ class AccountController < ApplicationController
register_manually_by_administrator(user) do register_manually_by_administrator(user) do
onthefly_creation_failed(user) onthefly_creation_failed(user)
end end
end end
else else
# Existing record # Existing record
if user.active? if user.active?
@@ -192,31 +197,19 @@ class AccountController < ApplicationController
end end
end end
end end
def successful_authentication(user) def successful_authentication(user)
# Valid user # Valid user
self.logged_user = user self.logged_user = user
# generate a key and set cookie if autologin # generate a key and set cookie if autologin
if params[:autologin] && Setting.autologin? if params[:autologin] && Setting.autologin?
set_autologin_cookie(user) token = Token.create(:user => user, :action => 'autologin')
cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now }
end end
call_hook(:controller_account_success_authentication_after, {:user => user }) call_hook(:controller_account_success_authentication_after, {:user => user })
redirect_back_or_default :controller => 'my', :action => 'page' redirect_back_or_default :controller => 'my', :action => 'page'
end end
def set_autologin_cookie(user)
token = Token.create(:user => user, :action => 'autologin')
cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin'
cookie_options = {
:value => token.value,
:expires => 1.year.from_now,
:path => (Redmine::Configuration['autologin_cookie_path'] || '/'),
:secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
:httponly => true
}
cookies[cookie_name] = cookie_options
end
# Onthefly creation failed, display the registration form to fill/fix attributes # Onthefly creation failed, display the registration form to fill/fix attributes
def onthefly_creation_failed(user, auth_source_options = { }) def onthefly_creation_failed(user, auth_source_options = { })
@user = user @user = user
@@ -235,14 +228,14 @@ class AccountController < ApplicationController
def register_by_email_activation(user, &block) def register_by_email_activation(user, &block)
token = Token.new(:user => user, :action => "register") token = Token.new(:user => user, :action => "register")
if user.save and token.save if user.save and token.save
Mailer.register(token).deliver Mailer.deliver_register(token)
flash[:notice] = l(:notice_account_register_done) flash[:notice] = l(:notice_account_register_done)
redirect_to :action => 'login' redirect_to :action => 'login'
else else
yield if block_given? yield if block_given?
end end
end end
# Automatically register a user # Automatically register a user
# #
# Pass a block for behavior when a user fails to save # Pass a block for behavior when a user fails to save
@@ -258,14 +251,14 @@ class AccountController < ApplicationController
yield if block_given? yield if block_given?
end end
end end
# Manual activation by the administrator # Manual activation by the administrator
# #
# Pass a block for behavior when a user fails to save # Pass a block for behavior when a user fails to save
def register_manually_by_administrator(user, &block) def register_manually_by_administrator(user, &block)
if user.save if user.save
# Sends an email to the administrators # Sends an email to the administrators
Mailer.account_activation_request(user).deliver Mailer.deliver_account_activation_request(user)
account_pending account_pending
else else
yield if block_given? yield if block_given?
+9 -25
View File
@@ -1,28 +1,11 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ActivitiesController < ApplicationController class ActivitiesController < ApplicationController
menu_item :activity menu_item :activity
before_filter :find_optional_project before_filter :find_optional_project
accept_rss_auth :index accept_key_auth :index
def index def index
@days = Setting.activity_days_default.to_i @days = Setting.activity_days_default.to_i
if params[:from] if params[:from]
begin; @date_to = params[:from].to_date + 1; rescue; end begin; @date_to = params[:from].to_date + 1; rescue; end
end end
@@ -31,19 +14,19 @@ class ActivitiesController < ApplicationController
@date_from = @date_to - @days @date_from = @date_to - @days
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
@author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id])) @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
@activity = Redmine::Activity::Fetcher.new(User.current, :project => @project, @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
:with_subprojects => @with_subprojects, :with_subprojects => @with_subprojects,
:author => @author) :author => @author)
@activity.scope_select {|t| !params["show_#{t}"].nil?} @activity.scope_select {|t| !params["show_#{t}"].nil?}
@activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty? @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
events = @activity.events(@date_from, @date_to) events = @activity.events(@date_from, @date_to)
if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, User.current, current_language]) if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, User.current, current_language])
respond_to do |format| respond_to do |format|
format.html { format.html {
@events_by_day = events.group_by {|event| User.current.time_to_date(event.event_datetime)} @events_by_day = events.group_by(&:event_date)
render :layout => false if request.xhr? render :layout => false if request.xhr?
} }
format.atom { format.atom {
@@ -57,7 +40,7 @@ class ActivitiesController < ApplicationController
} }
end end
end end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
@@ -72,4 +55,5 @@ class ActivitiesController < ApplicationController
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
end end
+25 -23
View File
@@ -1,27 +1,25 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class AdminController < ApplicationController class AdminController < ApplicationController
layout 'admin' layout 'admin'
menu_item :projects, :only => :projects
menu_item :plugins, :only => :plugins
menu_item :info, :only => :info
before_filter :require_admin before_filter :require_admin
helper :sort helper :sort
include SortHelper include SortHelper
@@ -30,20 +28,24 @@ class AdminController < ApplicationController
end end
def projects def projects
@status = params[:status] || 1 @status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
scope = Project.status(@status)
scope = scope.like(params[:name]) if params[:name].present? unless params[:name].blank?
name = "%#{params[:name].strip.downcase}%"
@projects = scope.all(:order => 'lft') c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name]
end
@projects = Project.find :all, :order => 'lft',
:conditions => c.conditions
render :action => "projects", :layout => false if request.xhr? render :action => "projects", :layout => false if request.xhr?
end end
def plugins def plugins
@plugins = Redmine::Plugin.all @plugins = Redmine::Plugin.all
end end
# Loads the default configuration # Loads the default configuration
# (roles, trackers, statuses, workflow, enumerations) # (roles, trackers, statuses, workflow, enumerations)
def default_configuration def default_configuration
@@ -57,13 +59,13 @@ class AdminController < ApplicationController
end end
redirect_to :action => 'index' redirect_to :action => 'index'
end end
def test_email def test_email
raise_delivery_errors = ActionMailer::Base.raise_delivery_errors raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
# Force ActionMailer to raise delivery errors so we can catch it # Force ActionMailer to raise delivery errors so we can catch it
ActionMailer::Base.raise_delivery_errors = true ActionMailer::Base.raise_delivery_errors = true
begin begin
@test = Mailer.test_email(User.current).deliver @test = Mailer.deliver_test(User.current)
flash[:notice] = l(:notice_email_sent, User.current.mail) flash[:notice] = l(:notice_email_sent, User.current.mail)
rescue Exception => e rescue Exception => e
flash[:error] = l(:notice_email_error, e.message) flash[:error] = l(:notice_email_error, e.message)
@@ -71,14 +73,14 @@ class AdminController < ApplicationController
ActionMailer::Base.raise_delivery_errors = raise_delivery_errors ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications' redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications'
end end
def info def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name @db_adapter_name = ActiveRecord::Base.connection.adapter_name
@checklist = [ @checklist = [
[:text_default_administrator_account_changed, User.default_admin_account_changed?], [:text_default_administrator_account_changed, User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
[:text_file_repository_writable, File.writable?(Attachment.storage_path)], [:text_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)], [:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
[:text_rmagick_available, Object.const_defined?(:Magick)] [:text_rmagick_available, Object.const_defined?(:Magick)]
] ]
end end
end end
+108 -122
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -18,31 +18,37 @@
require 'uri' require 'uri'
require 'cgi' require 'cgi'
class Unauthorized < Exception; end
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Redmine::I18n include Redmine::I18n
class_attribute :accept_api_auth_actions
class_attribute :accept_rss_auth_actions
class_attribute :model_object
layout 'base' layout 'base'
exempt_from_layout 'builder', 'rsb'
protect_from_forgery
def handle_unverified_request # Remove broken cookie after upgrade from 0.8.x (#4292)
super # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
cookies.delete(:autologin) # TODO: remove it when Rails is fixed
before_filter :delete_broken_cookies
def delete_broken_cookies
if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
cookies.delete '_redmine_session'
redirect_to home_path
return false
end
end end
before_filter :user_setup, :check_if_login_required, :set_localization before_filter :user_setup, :check_if_login_required, :set_localization
filter_parameter_logging :password
protect_from_forgery
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
rescue_from ::Unauthorized, :with => :deny_access
include Redmine::Search::Controller include Redmine::Search::Controller
include Redmine::MenuManager::MenuController include Redmine::MenuManager::MenuController
helper Redmine::MenuManager::MenuHelper helper Redmine::MenuManager::MenuHelper
Redmine::Scm::Base.all.each do |scm|
require_dependency "repository/#{scm.underscore}"
end
def user_setup def user_setup
# Check the settings cache for each request # Check the settings cache for each request
@@ -50,7 +56,7 @@ class ApplicationController < ActionController::Base
# Find the current user # Find the current user
User.current = find_current_user User.current = find_current_user
end end
# Returns the current user or nil if no user is logged in # Returns the current user or nil if no user is logged in
# and starts a session if needed # and starts a session if needed
def find_current_user def find_current_user
@@ -62,11 +68,11 @@ class ApplicationController < ActionController::Base
user = User.try_to_autologin(cookies[:autologin]) user = User.try_to_autologin(cookies[:autologin])
session[:user_id] = user.id if user session[:user_id] = user.id if user
user user
elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth? elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
# RSS key authentication does not start a session # RSS key authentication does not start a session
User.find_by_rss_key(params[:key]) User.find_by_rss_key(params[:key])
elsif Setting.rest_api_enabled? && accept_api_auth? elsif Setting.rest_api_enabled? && api_request?
if (key = api_key_from_request) if (key = api_key_from_request) && accept_key_auth_actions.include?(params[:action])
# Use API key # Use API key
User.find_by_api_key(key) User.find_by_api_key(key)
else else
@@ -88,23 +94,14 @@ class ApplicationController < ActionController::Base
User.current = User.anonymous User.current = User.anonymous
end end
end end
# Logs out current user
def logout_user
if User.current.logged?
cookies.delete :autologin
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
self.logged_user = nil
end
end
# check if login is globally required to access the application # check if login is globally required to access the application
def check_if_login_required def check_if_login_required
# no check needed if user is already logged in # no check needed if user is already logged in
return true if User.current.logged? return true if User.current.logged?
require_login if Setting.login_required? require_login if Setting.login_required?
end end
def set_localization def set_localization
lang = nil lang = nil
if User.current.logged? if User.current.logged?
@@ -120,7 +117,7 @@ class ApplicationController < ActionController::Base
lang ||= Setting.default_language lang ||= Setting.default_language
set_language_if_valid(lang) set_language_if_valid(lang)
end end
def require_login def require_login
if !User.current.logged? if !User.current.logged?
# Extract only the basic url parameters on non-GET requests # Extract only the basic url parameters on non-GET requests
@@ -149,7 +146,7 @@ class ApplicationController < ActionController::Base
end end
true true
end end
def deny_access def deny_access
User.current.logged? ? render_403 : require_login User.current.logged? ? render_403 : require_login
end end
@@ -200,12 +197,14 @@ class ApplicationController < ActionController::Base
# Finds and sets @project based on @object.project # Finds and sets @project based on @object.project
def find_project_from_association def find_project_from_association
render_404 unless @object.present? render_404 unless @object.present?
@project = @object.project @project = @object.project
rescue ActiveRecord::RecordNotFound
render_404
end end
def find_model_object def find_model_object
model = self.class.model_object model = self.class.read_inheritable_attribute('model_object')
if model if model
@object = model.find(params[:id]) @object = model.find(params[:id])
self.instance_variable_set('@' + controller_name.singularize, @object) if @object self.instance_variable_set('@' + controller_name.singularize, @object) if @object
@@ -215,31 +214,36 @@ class ApplicationController < ActionController::Base
end end
def self.model_object(model) def self.model_object(model)
self.model_object = model write_inheritable_attribute('model_object', model)
end end
# Filter for bulk issue operations # Filter for bulk issue operations
def find_issues def find_issues
@issues = Issue.find_all_by_id(params[:id] || params[:ids]) @issues = Issue.find_all_by_id(params[:id] || params[:ids])
raise ActiveRecord::RecordNotFound if @issues.empty? raise ActiveRecord::RecordNotFound if @issues.empty?
if @issues.detect {|issue| !issue.visible?}
deny_access
return
end
@projects = @issues.collect(&:project).compact.uniq @projects = @issues.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1 @project = @projects.first if @projects.size == 1
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
# Check if project is unique before bulk operations
def check_project_uniqueness
unless @project
# TODO: let users bulk edit/move/destroy issues from different projects
render_error 'Can not bulk edit/move/destroy issues from different projects'
return false
end
end
# make sure that the user is a member of the project (or admin) if project is private # make sure that the user is a member of the project (or admin) if project is private
# used as a before_filter for actions that do not require any particular permission on the project # used as a before_filter for actions that do not require any particular permission on the project
def check_project_privacy def check_project_privacy
if @project && @project.active? if @project && @project.active?
if @project.visible? if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
true true
else else
deny_access User.current.logged? ? render_403 : require_login
end end
else else
@project = nil @project = nil
@@ -269,39 +273,26 @@ class ApplicationController < ActionController::Base
redirect_to default redirect_to default
false false
end end
# Redirects to the request referer if present, redirects to args or call block otherwise.
def redirect_to_referer_or(*args, &block)
redirect_to :back
rescue ::ActionController::RedirectBackError
if args.any?
redirect_to *args
elsif block_given?
block.call
else
raise "#redirect_to_referer_or takes arguments or a block"
end
end
def render_403(options={}) def render_403(options={})
@project = nil @project = nil
render_error({:message => :notice_not_authorized, :status => 403}.merge(options)) render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
return false return false
end end
def render_404(options={}) def render_404(options={})
render_error({:message => :notice_file_not_found, :status => 404}.merge(options)) render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
return false return false
end end
# Renders an error response # Renders an error response
def render_error(arg) def render_error(arg)
arg = {:message => arg} unless arg.is_a?(Hash) arg = {:message => arg} unless arg.is_a?(Hash)
@message = arg[:message] @message = arg[:message]
@message = l(@message) if @message.is_a?(Symbol) @message = l(@message) if @message.is_a?(Symbol)
@status = arg[:status] || 500 @status = arg[:status] || 500
respond_to do |format| respond_to do |format|
format.html { format.html {
render :template => 'common/error', :layout => use_layout, :status => @status render :template => 'common/error', :layout => use_layout, :status => @status
@@ -312,19 +303,6 @@ class ApplicationController < ActionController::Base
format.json { head @status } format.json { head @status }
end end
end end
# Filter for actions that provide an API response
# but have no HTML representation for non admin users
def require_admin_or_api_request
return true if api_request?
if User.current.admin?
true
elsif User.current.logged?
render_error(:status => 406)
else
deny_access
end
end
# Picks which layout to use based on the request # Picks which layout to use based on the request
# #
@@ -332,47 +310,31 @@ class ApplicationController < ActionController::Base
def use_layout def use_layout
request.xhr? ? false : 'base' request.xhr? ? false : 'base'
end end
def invalid_authenticity_token def invalid_authenticity_token
if api_request? 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)." 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 end
render_error "Invalid form authenticity token." render_error "Invalid form authenticity token."
end 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 }
@items = @items.slice(0, Setting.feeds_limit.to_i) @items = @items.slice(0, Setting.feeds_limit.to_i)
@title = options[:title] || Setting.app_title @title = options[:title] || Setting.app_title
render :template => "common/feed.atom", :layout => false, render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
:content_type => 'application/atom+xml'
end end
def self.accept_rss_auth(*actions) def self.accept_key_auth(*actions)
if actions.any? actions = actions.flatten.map(&:to_s)
self.accept_rss_auth_actions = actions write_inheritable_attribute('accept_key_auth_actions', actions)
else
self.accept_rss_auth_actions || []
end
end end
def accept_rss_auth?(action=action_name) def accept_key_auth_actions
self.class.accept_rss_auth.include?(action.to_sym) self.class.read_inheritable_attribute('accept_key_auth_actions') || []
end end
def self.accept_api_auth(*actions)
if actions.any?
self.accept_api_auth_actions = actions
else
self.accept_api_auth_actions || []
end
end
def accept_api_auth?(action=action_name)
self.class.accept_api_auth.include?(action.to_sym)
end
# Returns the number of objects that should be displayed # Returns the number of objects that should be displayed
# on the paginated list # on the paginated list
def per_page_option def per_page_option
@@ -408,10 +370,10 @@ class ApplicationController < ActionController::Base
offset = 0 if offset < 0 offset = 0 if offset < 0
end end
offset ||= 0 offset ||= 0
[offset, limit] [offset, limit]
end end
# qvalues http header parser # qvalues http header parser
# code taken from webrick # code taken from webrick
def parse_qvalues(value) def parse_qvalues(value)
@@ -432,16 +394,16 @@ class ApplicationController < ActionController::Base
rescue rescue
nil nil
end end
# 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} ? 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?
%w(xml json).include? params[:format] %w(xml json).include? params[:format]
end end
# Returns the API key present in the request # Returns the API key present in the request
def api_key_from_request def api_key_from_request
if params[:key].present? if params[:key].present?
@@ -479,19 +441,43 @@ class ApplicationController < ActionController::Base
render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator." render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
end end
# Renders API response on validation failure # Converts the errors on an ActiveRecord object into a common JSON format
def render_validation_errors(objects) def object_errors_to_json(object)
if objects.is_a?(Array) object.errors.collect do |attribute, error|
@error_messages = objects.map {|object| object.errors.full_messages}.flatten { attribute => error }
else end.to_json
@error_messages = objects.errors.full_messages
end
render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil
end end
# Overrides #_include_layout? so that #render with no arguments # Renders API response on validation failure
def render_validation_errors(object)
options = { :status => :unprocessable_entity, :layout => false }
options.merge!(case params[:format]
when 'xml'; { :xml => object.errors }
when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance
else
raise "Unknown format #{params[:format]} in #render_validation_errors"
end
)
render options
end
# Overrides #default_template so that the api template
# is used automatically if it exists
def default_template(action_name = self.action_name)
if api_request?
begin
return self.view_paths.find_template(default_template_name(action_name), 'api')
rescue ::ActionView::MissingTemplate
# the api template was not found
# fallback to the default behaviour
end
end
super
end
# Overrides #pick_layout so that #render with no arguments
# doesn't use the layout for api requests # doesn't use the layout for api requests
def _include_layout?(*args) def pick_layout(*args)
api_request? ? false : super api_request? ? nil : super
end end
end end
+29 -66
View File
@@ -1,96 +1,59 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2008 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class AttachmentsController < ApplicationController class AttachmentsController < ApplicationController
before_filter :find_project, :except => :upload before_filter :find_project
before_filter :file_readable, :read_authorize, :only => [:show, :download] before_filter :file_readable, :read_authorize, :except => :destroy
before_filter :delete_authorize, :only => :destroy before_filter :delete_authorize, :only => :destroy
before_filter :authorize_global, :only => :upload
verify :method => :post, :only => :destroy
accept_api_auth :show, :download, :upload
def show def show
respond_to do |format| if @attachment.is_diff?
format.html { @diff = File.new(@attachment.diskfile, "rb").read
if @attachment.is_diff? render :action => 'diff'
@diff = File.new(@attachment.diskfile, "rb").read elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' @content = File.new(@attachment.diskfile, "rb").read
@diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) render :action => 'file'
# Save diff type as user preference else
if User.current.logged? && @diff_type != User.current.pref[:diff_type] download
User.current.pref[:diff_type] = @diff_type
User.current.preference.save
end
render :action => 'diff'
elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
@content = File.new(@attachment.diskfile, "rb").read
render :action => 'file'
else
download
end
}
format.api
end end
end end
def download def download
if @attachment.container.is_a?(Version) || @attachment.container.is_a?(Project) if @attachment.container.is_a?(Version) || @attachment.container.is_a?(Project)
@attachment.increment_download @attachment.increment_download
end end
# images are sent inline # images are sent inline
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename), send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
:type => detect_content_type(@attachment), :type => detect_content_type(@attachment),
:disposition => (@attachment.image? ? 'inline' : 'attachment') :disposition => (@attachment.image? ? 'inline' : 'attachment')
end end
def upload
# Make sure that API users get used to set this content type
# as it won't trigger Rails' automatic parsing of the request body for parameters
unless request.content_type == 'application/octet-stream'
render :nothing => true, :status => 406
return
end
@attachment = Attachment.new(:file => request.raw_post)
@attachment.author = User.current
@attachment.filename = Redmine::Utils.random_hex(16)
if @attachment.save
respond_to do |format|
format.api { render :action => 'upload', :status => :created }
end
else
respond_to do |format|
format.api { render_validation_errors(@attachment) }
end
end
end
def destroy def destroy
if @attachment.container.respond_to?(:init_journal)
@attachment.container.init_journal(User.current)
end
# Make sure association callbacks are called # Make sure association callbacks are called
@attachment.container.attachments.delete(@attachment) @attachment.container.attachments.delete(@attachment)
redirect_to_referer_or project_path(@project) redirect_to :back
rescue ::ActionController::RedirectBackError
redirect_to :controller => 'projects', :action => 'show', :id => @project
end end
private private
def find_project def find_project
@attachment = Attachment.find(params[:id]) @attachment = Attachment.find(params[:id])
@@ -100,20 +63,20 @@ private
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
# Checks that the file exists and is readable # Checks that the file exists and is readable
def file_readable def file_readable
@attachment.readable? ? true : render_404 @attachment.readable? ? true : render_404
end end
def read_authorize def read_authorize
@attachment.visible? ? true : deny_access @attachment.visible? ? true : deny_access
end end
def delete_authorize def delete_authorize
@attachment.deletable? ? true : deny_access @attachment.deletable? ? true : deny_access
end end
def detect_content_type(attachment) def detect_content_type(attachment)
content_type = attachment.content_type content_type = attachment.content_type
if content_type.blank? if content_type.blank?
+28 -17
View File
@@ -1,47 +1,52 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class AuthSourcesController < ApplicationController class AuthSourcesController < ApplicationController
layout 'admin' layout 'admin'
menu_item :ldap_authentication
before_filter :require_admin before_filter :require_admin
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :template => :index }
def index def index
@auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 10 @auth_source_pages, @auth_sources = paginate auth_source_class.name.tableize, :per_page => 10
render "auth_sources/index"
end end
def new def new
klass_name = params[:type] || 'AuthSourceLdap' @auth_source = auth_source_class.new
@auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source]) render 'auth_sources/new'
end end
def create def create
@auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source]) @auth_source = auth_source_class.new(params[:auth_source])
if @auth_source.save if @auth_source.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index' redirect_to :action => 'index'
else else
render :action => 'new' render 'auth_sources/new'
end end
end end
def edit def edit
@auth_source = AuthSource.find(params[:id]) @auth_source = AuthSource.find(params[:id])
render 'auth_sources/edit'
end end
def update def update
@@ -50,17 +55,17 @@ class AuthSourcesController < ApplicationController
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index' redirect_to :action => 'index'
else else
render :action => 'edit' render 'auth_sources/edit'
end end
end end
def test_connection def test_connection
@auth_source = AuthSource.find(params[:id]) @auth_method = AuthSource.find(params[:id])
begin begin
@auth_source.test_connection @auth_method.test_connection
flash[:notice] = l(:notice_successful_connection) flash[:notice] = l(:notice_successful_connection)
rescue Exception => e rescue => text
flash[:error] = l(:error_unable_to_connect, e.message) flash[:error] = l(:error_unable_to_connect, text.message)
end end
redirect_to :action => 'index' redirect_to :action => 'index'
end end
@@ -73,4 +78,10 @@ class AuthSourcesController < ApplicationController
end end
redirect_to :action => 'index' redirect_to :action => 'index'
end end
protected
def auth_source_class
AuthSource
end
end end
+1 -18
View File
@@ -1,23 +1,6 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AutoCompletesController < ApplicationController class AutoCompletesController < ApplicationController
before_filter :find_project before_filter :find_project
def issues def issues
@issues = [] @issues = []
q = params[:q].to_s q = params[:q].to_s
+25 -27
View File
@@ -1,29 +1,32 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class BoardsController < ApplicationController class BoardsController < ApplicationController
default_search_scope :messages default_search_scope :messages
before_filter :find_project_by_project_id, :find_board_if_available, :authorize before_filter :find_project, :find_board_if_available, :authorize
accept_rss_auth :index, :show accept_key_auth :index, :show
helper :messages
include MessagesHelper
helper :sort helper :sort
include SortHelper include SortHelper
helper :watchers helper :watchers
include WatchersHelper
def index def index
@boards = @project.boards @boards = @project.boards
# show the board if there is only one # show the board if there is only one
@@ -40,14 +43,14 @@ class BoardsController < ApplicationController
sort_update 'created_on' => "#{Message.table_name}.created_on", sort_update 'created_on' => "#{Message.table_name}.created_on",
'replies' => "#{Message.table_name}.replies_count", 'replies' => "#{Message.table_name}.replies_count",
'updated_on' => "#{Message.table_name}.updated_on" 'updated_on' => "#{Message.table_name}.updated_on"
@topic_count = @board.topics.count @topic_count = @board.topics.count
@topic_pages = Paginator.new self, @topic_count, per_page_option, params['page'] @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
@topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '), @topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '),
:include => [:author, {:last_reply => :author}], :include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page, :limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset :offset => @topic_pages.current.offset
@message = Message.new(:board => @board) @message = Message.new
render :action => 'show', :layout => !request.xhr? render :action => 'show', :layout => !request.xhr?
} }
format.atom { format.atom {
@@ -58,32 +61,21 @@ class BoardsController < ApplicationController
} }
end end
end end
verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
def new def new
@board = @project.boards.build @board = Board.new(params[:board])
@board.safe_attributes = params[:board] @board.project = @project
end if request.post? && @board.save
def create
@board = @project.boards.build
@board.safe_attributes = params[:board]
if @board.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to_settings_in_projects redirect_to_settings_in_projects
else
render :action => 'new'
end end
end end
def edit def edit
end if request.post? && @board.update_attributes(params[:board])
def update
@board.safe_attributes = params[:board]
if @board.save
redirect_to_settings_in_projects redirect_to_settings_in_projects
else
render :action => 'edit'
end end
end end
@@ -91,12 +83,18 @@ class BoardsController < ApplicationController
@board.destroy @board.destroy
redirect_to_settings_in_projects redirect_to_settings_in_projects
end end
private private
def redirect_to_settings_in_projects def redirect_to_settings_in_projects
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards' redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end end
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
def find_board_if_available def find_board_if_available
@board = @project.boards.find(params[:id]) if params[:id] @board = @project.boards.find(params[:id]) if params[:id]
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
+9 -21
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CalendarsController < ApplicationController class CalendarsController < ApplicationController
menu_item :calendar menu_item :calendar
before_filter :find_optional_project before_filter :find_optional_project
@@ -33,11 +16,11 @@ class CalendarsController < ApplicationController
@year = params[:year].to_i @year = params[:year].to_i
if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
@month = params[:month].to_i @month = params[:month].to_i
end end
end end
@year ||= Date.today.year @year ||= Date.today.year
@month ||= Date.today.month @month ||= Date.today.month
@calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month) @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
retrieve_query retrieve_query
@query.group_by = nil @query.group_by = nil
@@ -47,10 +30,15 @@ class CalendarsController < ApplicationController
:conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt] :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
) )
events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt]) events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
@calendar.events = events @calendar.events = events
end end
render :action => 'show', :layout => false if request.xhr? render :action => 'show', :layout => false if request.xhr?
end end
def update
show
end
end end
+5 -22
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CommentsController < ApplicationController class CommentsController < ApplicationController
default_search_scope :news default_search_scope :news
model_object News model_object News
@@ -22,19 +5,18 @@ class CommentsController < ApplicationController
before_filter :find_project_from_association before_filter :find_project_from_association
before_filter :authorize before_filter :authorize
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
raise Unauthorized unless @news.commentable? @comment = Comment.new(params[:comment])
@comment = Comment.new
@comment.safe_attributes = params[:comment]
@comment.author = User.current @comment.author = User.current
if @news.comments << @comment if @news.comments << @comment
flash[:notice] = l(:label_comment_added) flash[:notice] = l(:label_comment_added)
end end
redirect_to :controller => 'news', :action => 'show', :id => @news redirect_to :controller => 'news', :action => 'show', :id => @news
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
@news.comments.find(params[:comment_id]).destroy @news.comments.find(params[:comment_id]).destroy
redirect_to :controller => 'news', :action => 'show', :id => @news redirect_to :controller => 'news', :action => 'show', :id => @news
@@ -50,4 +32,5 @@ class CommentsController < ApplicationController
@comment = nil @comment = nil
@news @news
end end
end end
+18 -57
View File
@@ -1,31 +1,19 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ContextMenusController < ApplicationController class ContextMenusController < ApplicationController
helper :watchers helper :watchers
helper :issues
def issues def issues
@issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project) @issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project)
if (@issues.size == 1) if (@issues.size == 1)
@issue = @issues.first @issue = @issues.first
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
else
@allowed_statuses = @issues.map do |i|
i.new_statuses_allowed_to(User.current)
end.inject do |memo,s|
memo & s
end
end end
@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
@projects = @issues.collect(&:project).compact.uniq @projects = @issues.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1 @project = @projects.first if @projects.size == 1
@@ -37,47 +25,20 @@ class ContextMenusController < ApplicationController
:delete => User.current.allowed_to?(:delete_issues, @projects) :delete => User.current.allowed_to?(:delete_issues, @projects)
} }
if @project if @project
if @issue @assignables = @project.assignable_users
@assignables = @issue.assignable_users @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
else
@assignables = @project.assignable_users
end
@trackers = @project.trackers @trackers = @project.trackers
else else
#when multiple projects, we only keep the intersection of each set #when multiple projects, we only keep the intersection of each set
@assignables = @projects.map(&:assignable_users).reduce(:&) @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
@trackers = @projects.map(&:trackers).reduce(:&) @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
end end
@priorities = IssuePriority.active.reverse @priorities = IssuePriority.all.reverse
@back = back_url @statuses = IssueStatus.find(:all, :order => 'position')
@options_by_custom_field = {}
if @can[:edit]
custom_fields = @issues.map(&:available_custom_fields).reduce(:&).select do |f|
%w(bool list user version).include?(f.field_format) && !f.multiple?
end
custom_fields.each do |field|
values = field.possible_values_options(@projects)
if values.any?
@options_by_custom_field[field] = values
end
end
end
render :layout => false
end
def time_entries
@time_entries = TimeEntry.all(
:conditions => {:id => params[:ids]}, :include => :project)
@projects = @time_entries.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1
@activities = TimeEntryActivity.shared.active
@can = {:edit => User.current.allowed_to?(:edit_time_entries, @projects),
:delete => User.current.allowed_to?(:edit_time_entries, @projects)
}
@back = back_url @back = back_url
render :layout => false render :layout => false
end end
end end
+19 -33
View File
@@ -1,78 +1,64 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2009 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class CustomFieldsController < ApplicationController class CustomFieldsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin
before_filter :build_new_custom_field, :only => [:new, :create]
before_filter :find_custom_field, :only => [:edit, :update, :destroy]
def index def index
@custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name } @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
@tab = params[:tab] || 'IssueCustomField' @tab = params[:tab] || 'IssueCustomField'
end end
def new def new
end @custom_field = begin
if params[:type].to_s.match(/.+CustomField$/)
def create params[:type].to_s.constantize.new(params[:custom_field])
end
rescue
end
(redirect_to(:action => 'index'); return) unless @custom_field.is_a?(CustomField)
if request.post? and @custom_field.save if request.post? and @custom_field.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field) call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
redirect_to :action => 'index', :tab => @custom_field.class.name redirect_to :action => 'index', :tab => @custom_field.class.name
else else
render :action => 'new' @trackers = Tracker.find(:all, :order => 'position')
end end
end end
def edit def edit
end @custom_field = CustomField.find(params[:id])
if request.post? and @custom_field.update_attributes(params[:custom_field])
def update
if request.put? and @custom_field.update_attributes(params[:custom_field])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field) call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
redirect_to :action => 'index', :tab => @custom_field.class.name redirect_to :action => 'index', :tab => @custom_field.class.name
else else
render :action => 'edit' @trackers = Tracker.find(:all, :order => 'position')
end end
end end
def destroy def destroy
@custom_field.destroy @custom_field = CustomField.find(params[:id]).destroy
redirect_to :action => 'index', :tab => @custom_field.class.name redirect_to :action => 'index', :tab => @custom_field.class.name
rescue rescue
flash[:error] = l(:error_can_not_delete_custom_field) flash[:error] = l(:error_can_not_delete_custom_field)
redirect_to :action => 'index' redirect_to :action => 'index'
end end
private
def build_new_custom_field
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
if @custom_field.nil?
render_404
end
end
def find_custom_field
@custom_field = CustomField.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+27 -35
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -18,13 +18,13 @@
class DocumentsController < ApplicationController class DocumentsController < ApplicationController
default_search_scope :documents default_search_scope :documents
model_object Document model_object Document
before_filter :find_project_by_project_id, :only => [:index, :new, :create] before_filter :find_project, :only => [:index, :new]
before_filter :find_model_object, :except => [:index, :new, :create] before_filter :find_model_object, :except => [:index, :new]
before_filter :find_project_from_association, :except => [:index, :new, :create] before_filter :find_project_from_association, :except => [:index, :new]
before_filter :authorize before_filter :authorize
helper :attachments helper :attachments
def index def index
@sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category' @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
documents = @project.documents.find :all, :include => [:attachments, :category] documents = @project.documents.find :all, :include => [:attachments, :category]
@@ -41,54 +41,46 @@ class DocumentsController < ApplicationController
@document = @project.documents.build @document = @project.documents.build
render :layout => false if request.xhr? render :layout => false if request.xhr?
end end
def show def show
@attachments = @document.attachments.find(:all, :order => "created_on DESC") @attachments = @document.attachments.find(:all, :order => "created_on DESC")
end end
def new def new
@document = @project.documents.build @document = @project.documents.build(params[:document])
@document.safe_attributes = params[:document] if request.post? and @document.save
end attachments = Attachment.attach_files(@document, params[:attachments])
def create
@document = @project.documents.build
@document.safe_attributes = params[:document]
@document.save_attachments(params[:attachments])
if @document.save
render_attachment_warning_if_needed(@document) render_attachment_warning_if_needed(@document)
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index', :project_id => @project redirect_to :action => 'index', :project_id => @project
else
render :action => 'new'
end end
end end
def edit def edit
end @categories = DocumentCategory.all
if request.post? and @document.update_attributes(params[:document])
def update
@document.safe_attributes = params[:document]
if request.put? and @document.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @document redirect_to :action => 'show', :id => @document
else
render :action => 'edit'
end end
end end
def destroy def destroy
@document.destroy if request.delete? @document.destroy
redirect_to :controller => 'documents', :action => 'index', :project_id => @project redirect_to :controller => 'documents', :action => 'index', :project_id => @project
end end
def add_attachment def add_attachment
attachments = Attachment.attach_files(@document, params[:attachments]) attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document) render_attachment_warning_if_needed(@document)
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added') Mailer.deliver_attachments_added(attachments[:files]) if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
Mailer.attachments_added(attachments[:files]).deliver
end
redirect_to :action => 'show', :id => @document redirect_to :action => 'show', :id => @document
end end
private
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+36 -30
View File
@@ -1,57 +1,76 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class EnumerationsController < ApplicationController class EnumerationsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin
before_filter :build_new_enumeration, :only => [:new, :create]
before_filter :find_enumeration, :only => [:edit, :update, :destroy]
helper :custom_fields helper :custom_fields
include CustomFieldsHelper
def index def index
list
render :action => 'list'
end
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :action => :list }
def list
end end
def new def new
begin
@enumeration = params[:type].constantize.new
rescue NameError
@enumeration = Enumeration.new
end
end end
def create def create
if request.post? && @enumeration.save @enumeration = Enumeration.new(params[:enumeration])
@enumeration.type = params[:enumeration][:type]
if @enumeration.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index', :type => @enumeration.type redirect_to :action => 'list', :type => @enumeration.type
else else
render :action => 'new' render :action => 'new'
end end
end end
def edit def edit
@enumeration = Enumeration.find(params[:id])
end end
def update def update
if request.put? && @enumeration.update_attributes(params[:enumeration]) @enumeration = Enumeration.find(params[:id])
@enumeration.type = params[:enumeration][:type] if params[:enumeration][:type]
if @enumeration.update_attributes(params[:enumeration])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index', :type => @enumeration.type redirect_to :action => 'list', :type => @enumeration.type
else else
render :action => 'edit' render :action => 'edit'
end end
end end
def destroy def destroy
@enumeration = Enumeration.find(params[:id])
if !@enumeration.in_use? if !@enumeration.in_use?
# No associated objects # No associated objects
@enumeration.destroy @enumeration.destroy
@@ -64,22 +83,9 @@ class EnumerationsController < ApplicationController
return return
end end
end end
@enumerations = @enumeration.class.all - [@enumeration] @enumerations = @enumeration.class.find(:all) - [@enumeration]
end #rescue
# flash[:error] = 'Unable to delete enumeration'
private # redirect_to :action => 'index'
def build_new_enumeration
class_name = params[:enumeration] && params[:enumeration][:type] || params[:type]
@enumeration = Enumeration.new_subclass_instance(class_name, params[:enumeration])
if @enumeration.nil?
render_404
end
end
def find_enumeration
@enumeration = Enumeration.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end end
end end
+2 -19
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class FilesController < ApplicationController class FilesController < ApplicationController
menu_item :files menu_item :files
@@ -30,7 +13,7 @@ class FilesController < ApplicationController
'created_on' => "#{Attachment.table_name}.created_on", 'created_on' => "#{Attachment.table_name}.created_on",
'size' => "#{Attachment.table_name}.filesize", 'size' => "#{Attachment.table_name}.filesize",
'downloads' => "#{Attachment.table_name}.downloads" 'downloads' => "#{Attachment.table_name}.downloads"
@containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)] @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
@containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
render :layout => !request.xhr? render :layout => !request.xhr?
@@ -46,7 +29,7 @@ class FilesController < ApplicationController
render_attachment_warning_if_needed(container) render_attachment_warning_if_needed(container)
if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added') if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
Mailer.attachments_added(attachments[:files]).deliver Mailer.deliver_attachments_added(attachments[:files])
end end
redirect_to project_files_path(@project) redirect_to project_files_path(@project)
end end
+8 -20
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class GanttsController < ApplicationController class GanttsController < ApplicationController
menu_item :gantt menu_item :gantt
before_filter :find_optional_project before_filter :find_optional_project
@@ -29,20 +12,25 @@ class GanttsController < ApplicationController
helper :sort helper :sort
include SortHelper include SortHelper
include Redmine::Export::PDF include Redmine::Export::PDF
def show def show
@gantt = Redmine::Helpers::Gantt.new(params) @gantt = Redmine::Helpers::Gantt.new(params)
@gantt.project = @project @gantt.project = @project
retrieve_query retrieve_query
@query.group_by = nil @query.group_by = nil
@gantt.query = @query if @query.valid? @gantt.query = @query if @query.valid?
basename = (@project ? "#{@project.identifier}-" : '') + 'gantt' basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
respond_to do |format| respond_to do |format|
format.html { render :action => "show", :layout => !request.xhr? } format.html { render :action => "show", :layout => !request.xhr? }
format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image') format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") } format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") }
end end
end end
def update
show
end
end end
+18 -20
View File
@@ -1,27 +1,27 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2009 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class GroupsController < ApplicationController class GroupsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin
helper :custom_fields helper :custom_fields
# GET /groups # GET /groups
# GET /groups.xml # GET /groups.xml
def index def index
@@ -48,7 +48,7 @@ class GroupsController < ApplicationController
# GET /groups/new.xml # GET /groups/new.xml
def new def new
@group = Group.new @group = Group.new
respond_to do |format| respond_to do |format|
format.html # new.html.erb format.html # new.html.erb
format.xml { render :xml => @group } format.xml { render :xml => @group }
@@ -67,10 +67,8 @@ class GroupsController < ApplicationController
respond_to do |format| respond_to do |format|
if @group.save if @group.save
format.html { flash[:notice] = l(:notice_successful_create)
flash[:notice] = l(:notice_successful_create) format.html { redirect_to(groups_path) }
redirect_to(params[:continue] ? new_group_path : groups_path)
}
format.xml { render :xml => @group, :status => :created, :location => @group } format.xml { render :xml => @group, :status => :created, :location => @group }
else else
format.html { render :action => "new" } format.html { render :action => "new" }
@@ -107,37 +105,37 @@ class GroupsController < ApplicationController
format.xml { head :ok } format.xml { head :ok }
end end
end end
def add_users def add_users
@group = Group.find(params[:id]) @group = Group.find(params[:id])
users = User.find_all_by_id(params[:user_ids]) users = User.find_all_by_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 :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js { format.js {
render(:update) {|page| render(:update) {|page|
page.replace_html "tab-content-users", :partial => 'groups/users' page.replace_html "tab-content-users", :partial => 'groups/users'
users.each {|user| page.visual_effect(:highlight, "user-#{user.id}") } users.each {|user| page.visual_effect(:highlight, "user-#{user.id}") }
} }
} }
end end
end end
def remove_user def remove_user
@group = Group.find(params[:id]) @group = Group.find(params[:id])
@group.users.delete(User.find(params[:user_id])) if request.delete? @group.users.delete(User.find(params[:user_id])) if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js { render(:update) {|page| page.replace_html "tab-content-users", :partial => 'groups/users'} } format.js { render(:update) {|page| page.replace_html "tab-content-users", :partial => 'groups/users'} }
end end
end end
def autocomplete_for_user def autocomplete_for_user
@group = Group.find(params[:id]) @group = Group.find(params[:id])
@users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100) @users = User.active.like(params[:q]).find(:all, :limit => 100) - @group.users
render :layout => false render :layout => false
end end
def edit_membership def edit_membership
@group = Group.find(params[:id]) @group = Group.find(params[:id])
@membership = Member.edit_membership(params[:membership_id], params[:membership], @group) @membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
@@ -160,7 +158,7 @@ class GroupsController < ApplicationController
end end
end end
end end
def destroy_membership def destroy_membership
@group = Group.find(params[:id]) @group = Group.find(params[:id])
Member.find(params[:membership_id]).destroy if request.post? Member.find(params[:membership_id]).destroy if request.post?
+46 -74
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -18,92 +18,58 @@
class IssueCategoriesController < ApplicationController class IssueCategoriesController < ApplicationController
menu_item :settings menu_item :settings
model_object IssueCategory model_object IssueCategory
before_filter :find_model_object, :except => [:index, :new, :create] before_filter :find_model_object, :except => :new
before_filter :find_project_from_association, :except => [:index, :new, :create] before_filter :find_project_from_association, :except => :new
before_filter :find_project_by_project_id, :only => [:index, :new, :create] before_filter :find_project, :only => :new
before_filter :authorize before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
def index verify :method => :post, :only => :destroy
respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project }
format.api { @categories = @project.issue_categories.all }
end
end
def show
respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project }
format.api
end
end
def new def new
@category = @project.issue_categories.build @category = @project.issue_categories.build(params[:category])
@category.safe_attributes = params[:issue_category] if request.post?
end if @category.save
respond_to do |format|
def create format.html do
@category = @project.issue_categories.build flash[:notice] = l(:notice_successful_create)
@category.safe_attributes = params[:issue_category] redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
if @category.save end
respond_to do |format| format.js do
format.html do # IE doesn't support the replace_html rjs method for select box options
flash[:notice] = l(:notice_successful_create) render(:update) {|page| page.replace "issue_category_id",
redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
}
end
end end
format.js do else
# IE doesn't support the replace_html rjs method for select box options respond_to do |format|
render(:update) {|page| page.replace "issue_category_id", format.html
content_tag('select', content_tag('option') + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]') format.js do
} render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
end
end end
format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) }
end
else
respond_to do |format|
format.html { render :action => 'new'}
format.js do
render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
end
format.api { render_validation_errors(@category) }
end end
end end
end end
def edit def edit
end if request.post? and @category.update_attributes(params[:category])
flash[:notice] = l(:notice_successful_update)
def update redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
@category.safe_attributes = params[:issue_category]
if @category.save
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
}
format.api { head :ok }
end
else
respond_to do |format|
format.html { render :action => 'edit' }
format.api { render_validation_errors(@category) }
end
end end
end end
def destroy def destroy
@issue_count = @category.issues.size @issue_count = @category.issues.size
if @issue_count == 0 || params[:todo] || api_request? if @issue_count == 0
reassign_to = nil # No issue assigned to this category
if params[:reassign_to_id] && (params[:todo] == 'reassign' || params[:todo].blank?) @category.destroy
reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
end return
elsif params[:todo]
reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) if params[:todo] == 'reassign'
@category.destroy(reassign_to) @category.destroy(reassign_to)
respond_to do |format| redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
format.html { redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' }
format.api { head :ok }
end
return return
end end
@categories = @project.issue_categories - [@category] @categories = @project.issue_categories - [@category]
@@ -115,5 +81,11 @@ private
def find_model_object def find_model_object
super super
@category = @object @category = @object
end
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end end
end end
+68
View File
@@ -0,0 +1,68 @@
class IssueMovesController < ApplicationController
default_search_scope :issues
before_filter :find_issues, :check_project_uniqueness
before_filter :authorize
def new
prepare_for_issue_move
render :layout => false if request.xhr?
end
def create
prepare_for_issue_move
if request.post?
new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
unsaved_issue_ids = []
moved_issues = []
@issues.each do |issue|
issue.reload
issue.init_journal(User.current)
issue.current_journal.notes = @notes if @notes.present?
call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy })
if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params)})
moved_issues << r
else
unsaved_issue_ids << issue.id
end
end
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
if params[:follow]
if @issues.size == 1 && moved_issues.size == 1
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
else
redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
end
else
redirect_to :controller => 'issues', :action => 'index', :project_id => @project
end
return
end
end
private
def prepare_for_issue_move
@issues.sort!
@copy = params[:copy_options] && params[:copy_options][:copy]
@allowed_projects = Issue.allowed_target_projects_on_move
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
@target_project ||= @project
@trackers = @target_project.trackers
@available_statuses = Workflow.available_statuses(@project)
@notes = params[:notes]
@notes ||= ''
end
def extract_changed_attributes_for_move(params)
changed_attributes = {}
[:assigned_to_id, :status_id, :start_date, :due_date, :priority_id].each do |valid_attribute|
unless params[valid_attribute].blank?
changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
end
end
changed_attributes
end
end
+24 -55
View File
@@ -1,52 +1,30 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class IssueRelationsController < ApplicationController class IssueRelationsController < ApplicationController
before_filter :find_issue, :find_project_from_association, :authorize, :only => [:index, :create] before_filter :find_issue, :find_project_from_association, :authorize
before_filter :find_relation, :except => [:index, :create]
def new
accept_api_auth :index, :show, :create, :destroy
def index
@relations = @issue.relations
respond_to do |format|
format.html { render :nothing => true }
format.api
end
end
def show
raise Unauthorized unless @relation.visible?
respond_to do |format|
format.html { render :nothing => true }
format.api
end
end
def create
@relation = IssueRelation.new(params[:relation]) @relation = IssueRelation.new(params[:relation])
@relation.issue_from = @issue @relation.issue_from = @issue
if params[:relation] && m = params[:relation][:issue_to_id].to_s.strip.match(/^#?(\d+)$/) if params[:relation] && m = params[:relation][:issue_to_id].to_s.match(/^#?(\d+)$/)
@relation.issue_to = Issue.visible.find_by_id(m[1].to_i) @relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
end end
saved = @relation.save @relation.save if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js do format.js do
@@ -59,37 +37,28 @@ class IssueRelationsController < ApplicationController
end end
end end
end end
format.api { end
if saved end
render :action => 'show', :status => :created, :location => relation_url(@relation)
else def destroy
render_validation_errors(@relation) relation = IssueRelation.find(params[:id])
end if request.post? && @issue.relations.include?(relation)
relation.destroy
@issue.reload
end
respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js {
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'}
} }
end end
end end
def destroy
raise Unauthorized unless @relation.deletable?
@relation.destroy
respond_to do |format|
format.html { redirect_to issue_path } # TODO : does this really work since @issue is always nil? What is it useful to?
format.js { render(:update) {|page| page.remove "relation-#{@relation.id}"} }
format.api { head :ok }
end
end
private private
def find_issue def find_issue
@issue = @object = Issue.find(params[:issue_id]) @issue = @object = Issue.find(params[:issue_id])
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def find_relation
@relation = IssueRelation.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+15 -21
View File
@@ -1,37 +1,31 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class IssueStatusesController < ApplicationController class IssueStatusesController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin
before_filter :require_admin, :except => :index verify :method => :post, :only => [ :destroy, :create, :update, :move, :update_issue_done_ratio ],
before_filter :require_admin_or_api_request, :only => :index :redirect_to => { :action => :index }
accept_api_auth :index
def index def index
respond_to do |format| @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
format.html { render :action => "index", :layout => false if request.xhr?
@issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
render :action => "index", :layout => false if request.xhr?
}
format.api {
@issue_statuses = IssueStatus.all(:order => 'position')
}
end
end end
def new def new
@@ -40,7 +34,7 @@ class IssueStatusesController < ApplicationController
def create def create
@issue_status = IssueStatus.new(params[:issue_status]) @issue_status = IssueStatus.new(params[:issue_status])
if request.post? && @issue_status.save if @issue_status.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index' redirect_to :action => 'index'
else else
@@ -54,7 +48,7 @@ class IssueStatusesController < ApplicationController
def update def update
@issue_status = IssueStatus.find(params[:id]) @issue_status = IssueStatus.find(params[:id])
if request.put? && @issue_status.update_attributes(params[:issue_status]) if @issue_status.update_attributes(params[:issue_status])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index' redirect_to :action => 'index'
else else
@@ -69,9 +63,9 @@ class IssueStatusesController < ApplicationController
flash[:error] = l(:error_unable_delete_issue_status) flash[:error] = l(:error_unable_delete_issue_status)
redirect_to :action => 'index' redirect_to :action => 'index'
end end
def update_issue_done_ratio def update_issue_done_ratio
if request.post? && IssueStatus.update_issue_done_ratios if IssueStatus.update_issue_done_ratios
flash[:notice] = l(:notice_issue_done_ratios_updated) flash[:notice] = l(:notice_issue_done_ratios_updated)
else else
flash[:error] = l(:error_issue_done_ratios_not_updated) flash[:error] = l(:error_issue_done_ratios_not_updated)
+74 -191
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2008 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -18,22 +18,22 @@
class IssuesController < ApplicationController class IssuesController < ApplicationController
menu_item :new_issue, :only => [:new, :create] menu_item :new_issue, :only => [:new, :create]
default_search_scope :issues default_search_scope :issues
before_filter :find_issue, :only => [:show, :edit, :update] before_filter :find_issue, :only => [:show, :edit, :update]
before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy] before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
before_filter :check_project_uniqueness, :only => [:move, :perform_move]
before_filter :find_project, :only => [:new, :create] before_filter :find_project, :only => [:new, :create]
before_filter :authorize, :except => [:index] before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => [:index] before_filter :find_optional_project, :only => [:index]
before_filter :check_for_default_issue_status, :only => [:new, :create] before_filter :check_for_default_issue_status, :only => [:new, :create]
before_filter :build_new_issue_from_params, :only => [:new, :create] before_filter :build_new_issue_from_params, :only => [:new, :create]
accept_rss_auth :index, :show accept_key_auth :index, :show, :create, :update, :destroy
accept_api_auth :index, :show, :create, :update, :destroy
rescue_from Query::StatementInvalid, :with => :query_statement_invalid rescue_from Query::StatementInvalid, :with => :query_statement_invalid
helper :journals helper :journals
helper :projects helper :projects
include ProjectsHelper include ProjectsHelper
helper :custom_fields helper :custom_fields
include CustomFieldsHelper include CustomFieldsHelper
helper :issue_relations helper :issue_relations
@@ -53,11 +53,19 @@ class IssuesController < ApplicationController
helper :gantt helper :gantt
include Redmine::Export::PDF include Redmine::Export::PDF
verify :method => [:post, :delete],
:only => :destroy,
:render => { :nothing => true, :status => :method_not_allowed }
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def index def index
retrieve_query retrieve_query
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns) sort_update(@query.sortable_columns)
if @query.valid? if @query.valid?
case params[:format] case params[:format]
when 'csv', 'pdf' when 'csv', 'pdf'
@@ -69,54 +77,44 @@ class IssuesController < ApplicationController
else else
@limit = per_page_option @limit = per_page_option
end end
@issue_count = @query.issue_count @issue_count = @query.issue_count
@issue_pages = Paginator.new self, @issue_count, @limit, params['page'] @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
@offset ||= @issue_pages.current.offset @offset ||= @issue_pages.current.offset
@issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version], @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
:order => sort_clause, :order => sort_clause,
:offset => @offset, :offset => @offset,
:limit => @limit) :limit => @limit)
@issue_count_by_group = @query.issue_count_by_group @issue_count_by_group = @query.issue_count_by_group
respond_to do |format| respond_to do |format|
format.html { render :template => 'issues/index', :layout => !request.xhr? } format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
format.api { format.api
Issue.load_relations(@issues) if include_in_api_response?('relations')
}
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') } format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') } format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
end end
else else
respond_to do |format| # Send html if the query is not valid
format.html { render(:template => 'issues/index', :layout => !request.xhr?) } render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
format.any(:atom, :csv, :pdf) { render(:nothing => true) }
format.api { render_validation_errors(@query) }
end
end end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def show def show
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
@journals.each_with_index {|j,i| j.indice = i+1} @journals.each_with_index {|j,i| j.indice = i+1}
@journals.reverse! if User.current.wants_comments_in_reverse_order? @journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.all @changesets = @issue.changesets.visible.all
@changesets.reverse! if User.current.wants_comments_in_reverse_order? @changesets.reverse! if User.current.wants_comments_in_reverse_order?
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
@allowed_statuses = @issue.new_statuses_allowed_to(User.current) @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@priorities = IssuePriority.active @priorities = IssuePriority.all
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry = TimeEntry.new
respond_to do |format| respond_to do |format|
format.html { format.html { render :template => 'issues/show.rhtml' }
retrieve_previous_and_next_issue_ids
render :template => 'issues/show'
}
format.api format.api
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' } format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
@@ -128,30 +126,20 @@ class IssuesController < ApplicationController
def new def new
respond_to do |format| respond_to do |format|
format.html { render :action => 'new', :layout => !request.xhr? } format.html { render :action => 'new', :layout => !request.xhr? }
format.js { format.js { render :partial => 'attributes' }
render(:update) { |page|
if params[:project_change]
page.replace_html 'all_attributes', :partial => 'form'
else
page.replace_html 'attributes', :partial => 'attributes'
end
m = User.current.allowed_to?(:log_time, @issue.project) ? 'show' : 'hide'
page << "if ($('log_time')) {Element.#{m}('log_time');}"
}
}
end end
end end
def create def create
call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue }) call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
if @issue.save if @issue.save
attachments = Attachment.attach_files(@issue, params[:attachments])
render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_create)
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue}) call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
respond_to do |format| respond_to do |format|
format.html { format.html {
render_attachment_warning_if_needed(@issue) redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
redirect_to(params[:continue] ? { :action => 'new', :project_id => @issue.project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
{ :action => 'show', :id => @issue }) { :action => 'show', :id => @issue })
} }
format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) } format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
@@ -164,9 +152,11 @@ class IssuesController < ApplicationController
end end
end end
end end
def edit def edit
return unless update_issue_from_params update_issue_from_params
@journal = @issue.current_journal
respond_to do |format| respond_to do |format|
format.html { } format.html { }
@@ -175,24 +165,9 @@ class IssuesController < ApplicationController
end end
def update def update
return unless update_issue_from_params update_issue_from_params
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
saved = false
begin
saved = @issue.save_issue_with_child_records(params, @time_entry)
rescue ActiveRecord::StaleObjectError
@conflict = true
if params[:last_journal_id]
if params[:last_journal_id].present?
last_journal_id = params[:last_journal_id].to_i
@conflict_journals = @issue.journals.all(:conditions => ["#{Journal.table_name}.id > ?", last_journal_id])
else
@conflict_journals = @issue.journals.all
end
end
end
if saved if @issue.save_issue_with_child_records(params, @time_entry)
render_attachment_warning_if_needed(@issue) render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
@@ -201,6 +176,10 @@ class IssuesController < ApplicationController
format.api { head :ok } format.api { head :ok }
end end
else else
render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
@journal = @issue.current_journal
respond_to do |format| respond_to do |format|
format.html { render :action => 'edit' } format.html { render :action => 'edit' }
format.api { render_validation_errors(@issue) } format.api { render_validation_errors(@issue) }
@@ -208,76 +187,34 @@ class IssuesController < ApplicationController
end end
end end
# Bulk edit/copy a set of issues # Bulk edit a set of issues
def bulk_edit def bulk_edit
@issues.sort! @issues.sort!
@copy = params[:copy].present? @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
@notes = params[:notes] @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
@assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
if User.current.allowed_to?(:move_issues, @projects) @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
@allowed_projects = Issue.allowed_target_projects_on_move
if params[:issue]
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
if @target_project
target_projects = [@target_project]
end
end
end
target_projects ||= @projects
if @copy
@available_statuses = [IssueStatus.default]
else
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
end
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
@assignables = target_projects.map(&:assignable_users).reduce(:&)
@trackers = target_projects.map(&:trackers).reduce(:&)
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
@categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
if @copy
@attachments_present = @issues.detect {|i| i.attachments.any?}.present?
end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
render :layout => false if request.xhr?
end end
def bulk_update def bulk_update
@issues.sort! @issues.sort!
@copy = params[:copy].present?
attributes = parse_params_for_bulk_issue_attributes(params) attributes = parse_params_for_bulk_issue_attributes(params)
unsaved_issue_ids = [] unsaved_issue_ids = []
moved_issues = []
@issues.each do |issue| @issues.each do |issue|
issue.reload issue.reload
if @copy
issue = issue.copy({}, :attachments => params[:copy_attachments].present?)
end
journal = issue.init_journal(User.current, params[:notes]) journal = issue.init_journal(User.current, params[:notes])
issue.safe_attributes = attributes issue.safe_attributes = attributes
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
if issue.save unless issue.save
moved_issues << issue
else
# Keep unsaved issue ids to display them in flash error # Keep unsaved issue ids to display them in flash error
unsaved_issue_ids << issue.id unsaved_issue_ids << issue.id
end end
end end
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
if params[:follow]
if @issues.size == 1 && moved_issues.size == 1
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
elsif moved_issues.map(&:project).uniq.size == 1
redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first
end
else
redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
end
end end
def destroy def destroy
@hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
if @hours > 0 if @hours > 0
@@ -314,71 +251,32 @@ class IssuesController < ApplicationController
private private
def find_issue def find_issue
# Issue.visible.find(...) can not be used to redirect user to the login form
# if the issue actually exists but requires authentication
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
unless @issue.visible?
deny_access
return
end
@project = @issue.project @project = @issue.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def find_project def find_project
project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id]) project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
@project = Project.find(project_id) @project = Project.find(project_id)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def retrieve_previous_and_next_issue_ids
retrieve_query_from_session
if @query
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns, 'issues_index_sort')
limit = 500
issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
if (idx = issue_ids.index(@issue.id)) && idx < limit
if issue_ids.size < 500
@issue_position = idx + 1
@issue_count = issue_ids.size
end
@prev_issue_id = issue_ids[idx - 1] if idx > 0
@next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
end
end
end
# Used by #edit and #update to set some common instance variables # Used by #edit and #update to set some common instance variables
# from the params # from the params
# TODO: Refactor, not everything in here is needed by #edit # TODO: Refactor, not everything in here is needed by #edit
def update_issue_from_params def update_issue_from_params
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@priorities = IssuePriority.all
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry = TimeEntry.new
@time_entry.attributes = params[:time_entry] @time_entry.attributes = params[:time_entry]
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil) @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
@issue.init_journal(User.current, @notes) @issue.init_journal(User.current, @notes)
@issue.safe_attributes = params[:issue]
issue_attributes = params[:issue]
if issue_attributes && params[:conflict_resolution]
case params[:conflict_resolution]
when 'overwrite'
issue_attributes = issue_attributes.dup
issue_attributes.delete(:lock_version)
when 'add_notes'
issue_attributes = {}
when 'cancel'
redirect_to issue_path(@issue)
return false
end
end
@issue.safe_attributes = issue_attributes
@priorities = IssuePriority.active
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
true
end end
# TODO: Refactor, lots of extra code in here # TODO: Refactor, lots of extra code in here
@@ -386,35 +284,29 @@ private
def build_new_issue_from_params def build_new_issue_from_params
if params[:id].blank? if params[:id].blank?
@issue = Issue.new @issue = Issue.new
if params[:copy_from] @issue.copy_from(params[:copy_from]) if params[:copy_from]
begin
@copy_from = Issue.visible.find(params[:copy_from])
@copy_attachments = params[:copy_attachments].present? || request.get?
@issue.copy_from(@copy_from, :attachments => @copy_attachments)
rescue ActiveRecord::RecordNotFound
render_404
return
end
end
@issue.project = @project @issue.project = @project
else else
@issue = @project.issues.visible.find(params[:id]) @issue = @project.issues.visible.find(params[:id])
end end
@issue.project = @project @issue.project = @project
@issue.author = User.current
# Tracker must be set before custom field values # Tracker must be set before custom field values
@issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first) @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
if @issue.tracker.nil? if @issue.tracker.nil?
render_error l(:error_no_tracker_in_project) render_error l(:error_no_tracker_in_project)
return false return false
end end
@issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date? @issue.start_date ||= Date.today
@issue.safe_attributes = params[:issue] if params[:issue].is_a?(Hash)
@issue.safe_attributes = params[:issue]
@priorities = IssuePriority.active if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
@issue.watcher_user_ids = params[:issue]['watcher_user_ids']
end
end
@issue.author = User.current
@priorities = IssuePriority.all
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, true) @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
@available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
end end
def check_for_default_issue_status def check_for_default_issue_status
@@ -427,16 +319,7 @@ private
def parse_params_for_bulk_issue_attributes(params) def parse_params_for_bulk_issue_attributes(params)
attributes = (params[:issue] || {}).reject {|k,v| v.blank?} attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
if custom = attributes[:custom_field_values] attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
custom.reject! {|k,v| v.blank?}
custom.keys.each do |k|
if custom[k].is_a?(Array)
custom[k] << '' if custom[k].delete('__none__')
else
custom[k] = '' if custom[k] == '__none__'
end
end
end
attributes attributes
end end
end end
+16 -29
View File
@@ -1,27 +1,26 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2008 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class JournalsController < ApplicationController class JournalsController < ApplicationController
before_filter :find_journal, :only => [:edit, :diff] before_filter :find_journal, :only => [:edit]
before_filter :find_issue, :only => [:new] before_filter :find_issue, :only => [:new]
before_filter :find_optional_project, :only => [:index] before_filter :find_optional_project, :only => [:index]
before_filter :authorize, :only => [:new, :edit, :diff] before_filter :authorize, :only => [:new, :edit]
accept_rss_auth :index accept_key_auth :index
menu_item :issues
helper :issues helper :issues
helper :custom_fields helper :custom_fields
@@ -34,9 +33,9 @@ class JournalsController < ApplicationController
retrieve_query retrieve_query
sort_init 'id', 'desc' sort_init 'id', 'desc'
sort_update(@query.sortable_columns) sort_update(@query.sortable_columns)
if @query.valid? if @query.valid?
@journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC", @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
:limit => 25) :limit => 25)
end end
@title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name) @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
@@ -44,18 +43,7 @@ class JournalsController < ApplicationController
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def diff
@issue = @journal.issue
if params[:detail_id].present?
@detail = @journal.details.find_by_id(params[:detail_id])
else
@detail = @journal.details.detect {|d| d.prop_key == 'description'}
end
(render_404; return false) unless @issue && @detail
@diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value)
end
def new def new
journal = Journal.find(params[:journal_id]) if params[:journal_id] journal = Journal.find(params[:journal_id]) if params[:journal_id]
if journal if journal
@@ -69,7 +57,7 @@ class JournalsController < ApplicationController
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]') text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> " content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
render(:update) { |page| render(:update) { |page|
page.<< "$('notes').value = \"#{escape_javascript content}\";" page.<< "$('notes').value = \"#{escape_javascript content}\";"
page.show 'update' page.show 'update'
@@ -78,9 +66,8 @@ class JournalsController < ApplicationController
page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;" page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
} }
end end
def edit def edit
(render_403; return false) unless @journal.editable_by?(User.current)
if request.post? if request.post?
@journal.update_attributes(:notes => params[:notes]) if params[:notes] @journal.update_attributes(:notes => params[:notes]) if params[:notes]
@journal.destroy if @journal.details.empty? && @journal.notes.blank? @journal.destroy if @journal.details.empty? && @journal.notes.blank?
@@ -93,17 +80,17 @@ class JournalsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { format.html {
# TODO: implement non-JS journal update # TODO: implement non-JS journal update
render :nothing => true render :nothing => true
} }
format.js format.js
end end
end end
end end
private private
def find_journal def find_journal
@journal = Journal.find(params[:id]) @journal = Journal.find(params[:id])
(render_403; return false) unless @journal.editable_by?(User.current)
@project = @journal.journalized.project @project = @journal.journalized.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
@@ -1,24 +1,25 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class CommentObserver < ActiveRecord::Observer class LdapAuthSourcesController < AuthSourcesController
def after_create(comment)
if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added') protected
Mailer.news_comment_added(comment).deliver
end def auth_source_class
AuthSourceLdap
end end
end end
+11 -7
View File
@@ -1,23 +1,27 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2008 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class MailHandlerController < ActionController::Base class MailHandlerController < ActionController::Base
before_filter :check_credential before_filter :check_credential
verify :method => :post,
:only => :index,
:render => { :nothing => true, :status => 405 }
# Submits an incoming email to MailHandler # Submits an incoming email to MailHandler
def index def index
options = params.dup options = params.dup
@@ -28,9 +32,9 @@ class MailHandlerController < ActionController::Base
render :nothing => true, :status => :unprocessable_entity render :nothing => true, :status => :unprocessable_entity
end end
end end
private private
def check_credential def check_credential
User.current = nil User.current = nil
unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key
+38 -82
View File
@@ -1,82 +1,54 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class MembersController < ApplicationController class MembersController < ApplicationController
model_object Member model_object Member
before_filter :find_model_object, :except => [:index, :create, :autocomplete] before_filter :find_model_object, :except => [:new, :autocomplete_for_member]
before_filter :find_project_from_association, :except => [:index, :create, :autocomplete] before_filter :find_project_from_association, :except => [:new, :autocomplete_for_member]
before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete] before_filter :find_project, :only => [:new, :autocomplete_for_member]
before_filter :authorize before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
def index def new
@offset, @limit = api_offset_and_limit
@member_count = @project.member_principals.count
@member_pages = Paginator.new self, @member_count, @limit, params['page']
@offset ||= @member_pages.current.offset
@members = @project.member_principals.all(
:order => "#{Member.table_name}.id",
:limit => @limit,
:offset => @offset
)
respond_to do |format|
format.html { head 406 }
format.api
end
end
def show
respond_to do |format|
format.html { head 406 }
format.api
end
end
def create
members = [] members = []
if params[:membership] if params[:member] && request.post?
if params[:membership][:user_ids] attrs = params[:member].dup
attrs = params[:membership].dup if (user_ids = attrs.delete(:user_ids))
user_ids = attrs.delete(:user_ids)
user_ids.each do |user_id| user_ids.each do |user_id|
members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id) members << Member.new(attrs.merge(:user_id => user_id))
end end
else else
members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id]) members << Member.new(attrs)
end end
@project.members << members @project.members << members
end end
respond_to do |format| respond_to do |format|
if members.present? && members.all? {|m| m.valid? } if members.present? && members.all? {|m| m.valid? }
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project } format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js {
render(:update) {|page| format.js {
render(:update) {|page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members' page.replace_html "tab-content-members", :partial => 'projects/settings/members'
page << 'hideOnLoad()' page << 'hideOnLoad()'
members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") } members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
} }
} }
format.api {
@member = members.first
render :action => 'show', :status => :created, :location => membership_url(@member)
}
else else
format.js { format.js {
render(:update) {|page| render(:update) {|page|
errors = members.collect {|m| errors = members.collect {|m|
@@ -86,37 +58,28 @@ class MembersController < ApplicationController
page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', '))) page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', ')))
} }
} }
format.api { render_validation_errors(members.first) }
end
end
end
def edit
if request.post? and @member.update_attributes(params[:member])
respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js {
render(:update) {|page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
page << 'hideOnLoad()'
page.visual_effect(:highlight, "member-#{@member.id}")
}
}
end end
end end
end end
def update
if params[:membership]
@member.role_ids = params[:membership][:role_ids]
end
saved = @member.save
respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js {
render(:update) {|page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
page << 'hideOnLoad()'
page.visual_effect(:highlight, "member-#{@member.id}")
}
}
format.api {
if saved
head :ok
else
render_validation_errors(@member)
end
}
end
end
def destroy def destroy
if request.delete? && @member.deletable? if request.post? && @member.deletable?
@member.destroy @member.destroy
end end
respond_to do |format| respond_to do |format|
@@ -126,18 +89,11 @@ class MembersController < ApplicationController
page << 'hideOnLoad()' page << 'hideOnLoad()'
} }
} }
format.api {
if @member.destroyed?
head :ok
else
head :unprocessable_entity
end
}
end end
end end
def autocomplete def autocomplete_for_member
@principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100) @principals = Principal.active.like(params[:q]).find(:all, :limit => 100) - @project.principals
render :layout => false render :layout => false
end end
+35 -30
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -22,12 +22,15 @@ class MessagesController < ApplicationController
before_filter :find_message, :except => [:new, :preview] before_filter :find_message, :except => [:new, :preview]
before_filter :authorize, :except => [:preview, :edit, :destroy] before_filter :authorize, :except => [:preview, :edit, :destroy]
verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
verify :xhr => true, :only => :quote
helper :watchers helper :watchers
helper :attachments helper :attachments
include AttachmentsHelper include AttachmentsHelper
REPLIES_PER_PAGE = 25 unless const_defined?(:REPLIES_PER_PAGE) REPLIES_PER_PAGE = 25 unless const_defined?(:REPLIES_PER_PAGE)
# Show a topic and its replies # Show a topic and its replies
def show def show
page = params[:page] page = params[:page]
@@ -36,40 +39,40 @@ class MessagesController < ApplicationController
offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i]) 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
@reply_count = @topic.children.count @reply_count = @topic.children.count
@reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page @reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page
@replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}], @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}],
:order => "#{Message.table_name}.created_on ASC", :order => "#{Message.table_name}.created_on ASC",
:limit => @reply_pages.items_per_page, :limit => @reply_pages.items_per_page,
:offset => @reply_pages.current.offset) :offset => @reply_pages.current.offset)
@reply = Message.new(:subject => "RE: #{@message.subject}") @reply = Message.new(:subject => "RE: #{@message.subject}")
render :action => "show", :layout => false if request.xhr? render :action => "show", :layout => false if request.xhr?
end end
# Create a new topic # Create a new topic
def new def new
@message = Message.new @message = Message.new(params[:message])
@message.author = User.current @message.author = User.current
@message.board = @board @message.board = @board
@message.safe_attributes = params[:message] if params[:message] && User.current.allowed_to?(:edit_messages, @project)
if request.post? @message.locked = params[:message]['locked']
@message.save_attachments(params[:attachments]) @message.sticky = params[:message]['sticky']
if @message.save end
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message}) if request.post? && @message.save
render_attachment_warning_if_needed(@message) call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
redirect_to :action => 'show', :id => @message attachments = Attachment.attach_files(@message, params[:attachments])
end render_attachment_warning_if_needed(@message)
redirect_to :action => 'show', :id => @message
end end
end end
# Reply to a topic # Reply to a topic
def reply def reply
@reply = Message.new @reply = Message.new(params[:reply])
@reply.author = User.current @reply.author = User.current
@reply.board = @board @reply.board = @board
@reply.safe_attributes = params[:reply]
@topic.children << @reply @topic.children << @reply
if !@reply.new_record? if !@reply.new_record?
call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply}) call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
@@ -82,8 +85,11 @@ class MessagesController < ApplicationController
# Edit a message # Edit a message
def edit def edit
(render_403; return false) unless @message.editable_by?(User.current) (render_403; return false) unless @message.editable_by?(User.current)
@message.safe_attributes = params[:message] if params[:message]
if request.post? && @message.save @message.locked = params[:message]['locked']
@message.sticky = params[:message]['sticky']
end
if request.post? && @message.update_attributes(params[:message])
attachments = Attachment.attach_files(@message, params[:attachments]) attachments = Attachment.attach_files(@message, params[:attachments])
render_attachment_warning_if_needed(@message) render_attachment_warning_if_needed(@message)
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
@@ -91,17 +97,16 @@ class MessagesController < ApplicationController
redirect_to :action => 'show', :board_id => @message.board, :id => @message.root, :r => (@message.parent_id && @message.id) redirect_to :action => 'show', :board_id => @message.board, :id => @message.root, :r => (@message.parent_id && @message.id)
end end
end end
# Delete a messages # Delete a messages
def destroy def destroy
(render_403; return false) unless @message.destroyable_by?(User.current) (render_403; return false) unless @message.destroyable_by?(User.current)
r = @message.to_param
@message.destroy @message.destroy
redirect_to @message.parent.nil? ? redirect_to @message.parent.nil? ?
{ :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } : { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
{ :action => 'show', :id => @message.parent, :r => r } { :action => 'show', :id => @message.parent, :r => @message }
end end
def quote def quote
user = @message.author user = @message.author
text = @message.content text = @message.content
@@ -110,7 +115,7 @@ class MessagesController < ApplicationController
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> " content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n" content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
render(:update) { |page| render(:update) { |page|
page << "$('message_subject').value = \"#{subject}\";" page << "$('reply_subject').value = \"#{subject}\";"
page.<< "$('message_content').value = \"#{content}\";" page.<< "$('message_content').value = \"#{content}\";"
page.show 'reply' page.show 'reply'
page << "Form.Element.focus('message_content');" page << "Form.Element.focus('message_content');"
@@ -118,14 +123,14 @@ class MessagesController < ApplicationController
page << "$('message_content').scrollTop = $('message_content').scrollHeight - $('message_content').clientHeight;" page << "$('message_content').scrollTop = $('message_content').scrollHeight - $('message_content').clientHeight;"
} }
end end
def preview def preview
message = @board.messages.find_by_id(params[:id]) message = @board.messages.find_by_id(params[:id])
@attachements = message.attachments if message @attachements = message.attachments if message
@text = (params[:message] || params[:reply])[:content] @text = (params[:message] || params[:reply])[:content]
render :partial => 'common/preview' render :partial => 'common/preview'
end end
private private
def find_message def find_message
find_board find_board
@@ -134,7 +139,7 @@ private
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def find_board def find_board
@board = Board.find(params[:board_id], :include => :project) @board = Board.find(params[:board_id], :include => :project)
@project = @board.project @project = @board.project
+14 -29
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2009 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -31,10 +31,13 @@ class MyController < ApplicationController
'timelog' => :label_spent_time 'timelog' => :label_spent_time
}.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'], DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
'right' => ['issuesreportedbyme'] 'right' => ['issuesreportedbyme']
}.freeze }.freeze
verify :xhr => true,
:only => [:add_block, :remove_block, :order_blocks]
def index def index
page page
render :action => 'page' render :action => 'page'
@@ -65,24 +68,6 @@ class MyController < ApplicationController
end end
end end
# Destroys user's account
def destroy
@user = User.current
unless @user.own_account_deletable?
redirect_to :action => 'account'
return
end
if request.post? && params[:confirm]
@user.destroy
if @user.destroyed?
logout_user
flash[:notice] = l(:notice_account_deleted)
end
redirect_to home_path
end
end
# Manage user's password # Manage user's password
def password def password
@user = User.current @user = User.current
@@ -103,7 +88,7 @@ class MyController < ApplicationController
end end
end end
end end
# Create a new feeds key # Create a new feeds key
def reset_rss_key def reset_rss_key
if request.post? if request.post?
@@ -137,7 +122,7 @@ class MyController < ApplicationController
@block_options = [] @block_options = []
BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]} BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
end end
# Add a block to user's page # Add a block to user's page
# The block is added on top of the page # The block is added on top of the page
# params[:block] : id of the block to add # params[:block] : id of the block to add
@@ -151,10 +136,10 @@ class MyController < ApplicationController
# add it on top # add it on top
layout['top'].unshift block layout['top'].unshift block
@user.pref[:my_page_layout] = layout @user.pref[:my_page_layout] = layout
@user.pref.save @user.pref.save
render :partial => "block", :locals => {:user => @user, :block_name => block} render :partial => "block", :locals => {:user => @user, :block_name => block}
end end
# Remove a block to user's page # Remove a block to user's page
# params[:block] : id of the block to remove # params[:block] : id of the block to remove
def remove_block def remove_block
@@ -164,7 +149,7 @@ class MyController < ApplicationController
layout = @user.pref[:my_page_layout] || {} layout = @user.pref[:my_page_layout] || {}
%w(top left right).each {|f| (layout[f] ||= []).delete block } %w(top left right).each {|f| (layout[f] ||= []).delete block }
@user.pref[:my_page_layout] = layout @user.pref[:my_page_layout] = layout
@user.pref.save @user.pref.save
render :nothing => true render :nothing => true
end end
@@ -184,7 +169,7 @@ class MyController < ApplicationController
} }
layout[group] = group_items layout[group] = group_items
@user.pref[:my_page_layout] = layout @user.pref[:my_page_layout] = layout
@user.pref.save @user.pref.save
end end
end end
render :nothing => true render :nothing => true
+30 -35
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -20,15 +20,11 @@ class NewsController < ApplicationController
model_object News model_object News
before_filter :find_model_object, :except => [:new, :create, :index] before_filter :find_model_object, :except => [:new, :create, :index]
before_filter :find_project_from_association, :except => [:new, :create, :index] before_filter :find_project_from_association, :except => [:new, :create, :index]
before_filter :find_project_by_project_id, :only => [:new, :create] before_filter :find_project, :only => [:new, :create]
before_filter :authorize, :except => [:index] before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => :index before_filter :find_optional_project, :only => :index
accept_rss_auth :index accept_key_auth :index
accept_api_auth :index
helper :watchers
helper :attachments
def index def index
case params[:format] case params[:format]
when 'xml', 'json' when 'xml', 'json'
@@ -36,9 +32,9 @@ class NewsController < ApplicationController
else else
@limit = 10 @limit = 10
end end
scope = @project ? @project.news.visible : News.visible scope = @project ? @project.news.visible : News.visible
@news_count = scope.count @news_count = scope.count
@news_pages = Paginator.new self, @news_count, @limit, params['page'] @news_pages = Paginator.new self, @news_count, @limit, params['page']
@offset ||= @news_pages.current.offset @offset ||= @news_pages.current.offset
@@ -46,17 +42,14 @@ class NewsController < ApplicationController
:order => "#{News.table_name}.created_on DESC", :order => "#{News.table_name}.created_on DESC",
:offset => @offset, :offset => @offset,
:limit => @limit) :limit => @limit)
respond_to do |format| respond_to do |format|
format.html { format.html { render :layout => false if request.xhr? }
@news = News.new # for adding news inline
render :layout => false if request.xhr?
}
format.api format.api
format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") } format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
end end
end end
def show def show
@comments = @news.comments @comments = @news.comments
@comments.reverse! if User.current.wants_comments_in_reverse_order? @comments.reverse! if User.current.wants_comments_in_reverse_order?
@@ -68,25 +61,22 @@ class NewsController < ApplicationController
def create def create
@news = News.new(:project => @project, :author => User.current) @news = News.new(:project => @project, :author => User.current)
@news.safe_attributes = params[:news] if request.post?
@news.save_attachments(params[:attachments]) @news.attributes = params[:news]
if @news.save if @news.save
render_attachment_warning_if_needed(@news) flash[:notice] = l(:notice_successful_create)
flash[:notice] = l(:notice_successful_create) redirect_to :controller => 'news', :action => 'index', :project_id => @project
redirect_to :controller => 'news', :action => 'index', :project_id => @project else
else render :action => 'new'
render :action => 'new' end
end end
end end
def edit def edit
end end
def update def update
@news.safe_attributes = params[:news] if request.put? and @news.update_attributes(params[:news])
@news.save_attachments(params[:attachments])
if @news.save
render_attachment_warning_if_needed(@news)
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @news redirect_to :action => 'show', :id => @news
else else
@@ -98,9 +88,14 @@ class NewsController < ApplicationController
@news.destroy @news.destroy
redirect_to :action => 'index', :project_id => @project redirect_to :action => 'index', :project_id => @project
end end
private private
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
def find_optional_project def find_optional_project
return true unless params[:project_id] return true unless params[:project_id]
@project = Project.find(params[:project_id]) @project = Project.find(params[:project_id])
+2 -19
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class PreviewsController < ApplicationController class PreviewsController < ApplicationController
before_filter :find_project before_filter :find_project
@@ -39,12 +22,12 @@ class PreviewsController < ApplicationController
end end
private private
def find_project def find_project
project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id] project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
@project = Project.find(project_id) @project = Project.find(project_id)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
end end
@@ -1,24 +1,7 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ProjectEnumerationsController < ApplicationController class ProjectEnumerationsController < ApplicationController
before_filter :find_project_by_project_id before_filter :find_project_by_project_id
before_filter :authorize before_filter :authorize
def update def update
if request.put? && params[:enumerations] if request.put? && params[:enumerations]
Project.transaction do Project.transaction do
@@ -28,7 +11,7 @@ class ProjectEnumerationsController < ApplicationController
end end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
end end
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end end
+54 -42
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2009 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -19,36 +19,35 @@ class ProjectsController < ApplicationController
menu_item :overview menu_item :overview
menu_item :roadmap, :only => :roadmap menu_item :roadmap, :only => :roadmap
menu_item :settings, :only => :settings menu_item :settings, :only => :settings
before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ] before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy] before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
before_filter :authorize_global, :only => [:new, :create] before_filter :authorize_global, :only => [:new, :create]
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
accept_rss_auth :index accept_key_auth :index, :show, :create, :update, :destroy
accept_api_auth :index, :show, :create, :update, :destroy
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller| after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
if controller.request.post? if controller.request.post?
controller.send :expire_action, :controller => 'welcome', :action => 'robots' controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
end end
end end
helper :sort helper :sort
include SortHelper include SortHelper
helper :custom_fields helper :custom_fields
include CustomFieldsHelper include CustomFieldsHelper
helper :issues helper :issues
helper :queries helper :queries
include QueriesHelper include QueriesHelper
helper :repositories helper :repositories
include RepositoriesHelper include RepositoriesHelper
include ProjectsHelper include ProjectsHelper
# Lists visible projects # Lists visible projects
def index def index
respond_to do |format| respond_to do |format|
format.html { format.html {
@projects = Project.visible.find(:all, :order => 'lft') @projects = Project.visible.find(:all, :order => 'lft')
} }
format.api { format.api {
@offset, @limit = api_offset_and_limit @offset, @limit = api_offset_and_limit
@@ -62,14 +61,14 @@ class ProjectsController < ApplicationController
} }
end end
end end
def new def new
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all @trackers = Tracker.all
@project = Project.new @project = Project.new(params[:project])
@project.safe_attributes = params[:project]
end end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all @trackers = Tracker.all
@@ -85,12 +84,9 @@ class ProjectsController < ApplicationController
@project.members << m @project.members << m
end end
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to(params[:continue] ? redirect_to :controller => 'projects', :action => 'settings', :id => @project
{:controller => 'projects', :action => 'new', :project => {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}} :
{:controller => 'projects', :action => 'settings', :id => @project}
)
} }
format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
end end
@@ -100,9 +96,9 @@ class ProjectsController < ApplicationController
format.api { render_validation_errors(@project) } format.api { render_validation_errors(@project) }
end end
end end
end end
def copy def copy
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all @trackers = Tracker.all
@@ -116,7 +112,7 @@ class ProjectsController < ApplicationController
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers? @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
else else
redirect_to :controller => 'admin', :action => 'projects' redirect_to :controller => 'admin', :action => 'projects'
end end
else else
Mailer.with_deliveries(params[:notifications] == '1') do Mailer.with_deliveries(params[:notifications] == '1') do
@project = Project.new @project = Project.new
@@ -144,27 +140,28 @@ class ProjectsController < ApplicationController
# try to redirect to the requested menu item # try to redirect to the requested menu item
redirect_to_project_menu_item(@project, params[:jump]) && return redirect_to_project_menu_item(@project, params[:jump]) && return
end end
@users_by_role = @project.users_by_role @users_by_role = @project.users_by_role
@subprojects = @project.children.visible.all @subprojects = @project.children.visible
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@trackers = @project.rolled_up_trackers @trackers = @project.rolled_up_trackers
cond = @project.project_condition(Setting.display_subprojects_issues?) cond = @project.project_condition(Setting.display_subprojects_issues?)
@open_issues_by_tracker = Issue.visible.count(:group => :tracker, @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
:include => [:project, :status, :tracker], :include => [:project, :status, :tracker],
:conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false]) :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
@total_issues_by_tracker = Issue.visible.count(:group => :tracker, @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
:include => [:project, :status, :tracker], :include => [:project, :status, :tracker],
:conditions => cond) :conditions => cond)
if User.current.allowed_to?(:view_time_entries, @project) TimeEntry.visible_by(User.current) do
@total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f @total_hours = TimeEntry.sum(:hours,
:include => :project,
:conditions => cond).to_f
end end
@key = User.current.rss_key @key = User.current.rss_key
respond_to do |format| respond_to do |format|
format.html format.html
format.api format.api
@@ -176,18 +173,21 @@ class ProjectsController < ApplicationController
@issue_category ||= IssueCategory.new @issue_category ||= IssueCategory.new
@member ||= @project.members.new @member ||= @project.members.new
@trackers = Tracker.all @trackers = Tracker.all
@repository ||= @project.repository
@wiki ||= @project.wiki @wiki ||= @project.wiki
end end
def edit def edit
end end
# TODO: convert to PUT only
verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update def update
@project.safe_attributes = params[:project] @project.safe_attributes = params[:project]
if validate_parent_id && @project.save if validate_parent_id && @project.save
@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')
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project redirect_to :action => 'settings', :id => @project
} }
@@ -195,7 +195,7 @@ class ProjectsController < ApplicationController
end end
else else
respond_to do |format| respond_to do |format|
format.html { format.html {
settings settings
render :action => 'settings' render :action => 'settings'
} }
@@ -204,6 +204,7 @@ class ProjectsController < ApplicationController
end end
end end
verify :method => :post, :only => :modules, :render => {:nothing => true, :status => :method_not_allowed }
def modules def modules
@project.enabled_module_names = params[:enabled_module_names] @project.enabled_module_names = params[:enabled_module_names]
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
@@ -218,27 +219,38 @@ class ProjectsController < ApplicationController
end end
redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status])) redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end end
def unarchive def unarchive
@project.unarchive if request.post? && !@project.active? @project.unarchive if request.post? && !@project.active?
redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status])) redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end end
# Delete @project # Delete @project
def destroy def destroy
@project_to_destroy = @project @project_to_destroy = @project
if api_request? || params[:confirm] if request.get?
@project_to_destroy.destroy # display confirmation view
respond_to do |format| else
format.html { redirect_to :controller => 'admin', :action => 'projects' } if api_request? || params[:confirm]
format.api { head :ok } @project_to_destroy.destroy
respond_to do |format|
format.html { redirect_to :controller => 'admin', :action => 'projects' }
format.api { head :ok }
end
end end
end end
# hide project in layout # hide project in layout
@project = nil @project = nil
end end
private private
def find_optional_project
return true unless params[:id]
@project = Project.find(params[:id])
authorize
rescue ActiveRecord::RecordNotFound
render_404
end
# Validates parent_id param according to user's permissions # Validates parent_id param according to user's permissions
# TODO: move it to Project model in a validation that depends on User.current # TODO: move it to Project model in a validation that depends on User.current
+31 -61
View File
@@ -1,94 +1,64 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class QueriesController < ApplicationController class QueriesController < ApplicationController
menu_item :issues menu_item :issues
before_filter :find_query, :except => [:new, :create, :index] before_filter :find_query, :except => :new
before_filter :find_optional_project, :only => [:new, :create] before_filter :find_optional_project, :only => :new
accept_api_auth :index
include QueriesHelper
def index
case params[:format]
when 'xml', 'json'
@offset, @limit = api_offset_and_limit
else
@limit = per_page_option
end
@query_count = Query.visible.count
@query_pages = Paginator.new self, @query_count, @limit, params['page']
@queries = Query.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
respond_to do |format|
format.html { render :nothing => true }
format.api
end
end
def new def new
@query = Query.new
@query.user = User.current
@query.project = @project
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
build_query_from_params
end
def create
@query = Query.new(params[:query]) @query = Query.new(params[:query])
@query.user = User.current
@query.project = params[:query_is_for_all] ? nil : @project @query.project = params[:query_is_for_all] ? nil : @project
@query.user = User.current
@query.is_public = false 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?
build_query_from_params
@query.column_names = nil if params[:default_columns] @query.column_names = nil if params[:default_columns]
if @query.save @query.add_filters(params[:fields], params[:operators], params[:values]) if params[:fields]
@query.group_by ||= params[:group_by]
if request.post? && params[:confirm] && @query.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
else return
render :action => 'new', :layout => !request.xhr?
end end
render :layout => false if request.xhr?
end end
def edit def edit
end if request.post?
@query.filters = {}
def update @query.add_filters(params[:fields], params[:operators], params[:values]) if params[:fields]
@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.is_public = false 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?
build_query_from_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 :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query end
else
render :action => 'edit'
end end
end end
def destroy def destroy
@query.destroy @query.destroy if request.post?
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
end end
private private
def find_query def find_query
@query = Query.find(params[:id]) @query = Query.find(params[:id])
@@ -97,7 +67,7 @@ private
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def find_optional_project def find_optional_project
@project = Project.find(params[:project_id]) if params[:project_id] @project = Project.find(params[:project_id]) if params[:project_id]
render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true) render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true)
+9 -9
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -24,8 +24,8 @@ class ReportsController < ApplicationController
@versions = @project.shared_versions.sort @versions = @project.shared_versions.sort
@priorities = IssuePriority.all @priorities = IssuePriority.all
@categories = @project.issue_categories @categories = @project.issue_categories
@assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort @assignees = @project.members.collect { |m| m.user }.sort
@authors = @project.users.sort @authors = @project.members.collect { |m| m.user }.sort
@subprojects = @project.descendants.visible @subprojects = @project.descendants.visible
@issues_by_tracker = Issue.by_tracker(@project) @issues_by_tracker = Issue.by_tracker(@project)
@@ -37,7 +37,7 @@ class ReportsController < ApplicationController
@issues_by_subproject = Issue.by_subproject(@project) || [] @issues_by_subproject = Issue.by_subproject(@project) || []
render :template => "reports/issue_report" render :template => "reports/issue_report"
end end
def issue_report_details def issue_report_details
case params[:detail] case params[:detail]
@@ -63,12 +63,12 @@ class ReportsController < ApplicationController
@report_title = l(:field_category) @report_title = l(:field_category)
when "assigned_to" when "assigned_to"
@field = "assigned_to_id" @field = "assigned_to_id"
@rows = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort @rows = @project.members.collect { |m| m.user }.sort
@data = Issue.by_assigned_to(@project) @data = Issue.by_assigned_to(@project)
@report_title = l(:field_assigned_to) @report_title = l(:field_assigned_to)
when "author" when "author"
@field = "author_id" @field = "author_id"
@rows = @project.users.sort @rows = @project.members.collect { |m| m.user }.sort
@data = Issue.by_author(@project) @data = Issue.by_author(@project)
@report_title = l(:field_author) @report_title = l(:field_author)
when "subproject" when "subproject"
+100 -222
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2009 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -18,78 +18,41 @@
require 'SVG/Graph/Bar' require 'SVG/Graph/Bar'
require 'SVG/Graph/BarHorizontal' require 'SVG/Graph/BarHorizontal'
require 'digest/sha1' require 'digest/sha1'
require 'redmine/scm/adapters/abstract_adapter'
class ChangesetNotFound < Exception; end class ChangesetNotFound < Exception; end
class InvalidRevisionParam < Exception; end class InvalidRevisionParam < Exception; end
class RepositoriesController < ApplicationController class RepositoriesController < ApplicationController
menu_item :repository menu_item :repository
menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers] menu_item :settings, :only => :edit
default_search_scope :changesets default_search_scope :changesets
before_filter :find_project_by_project_id, :only => [:new, :create] before_filter :find_repository, :except => :edit
before_filter :find_repository, :only => [:edit, :update, :destroy, :committers] before_filter :find_project, :only => :edit
before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
before_filter :authorize before_filter :authorize
accept_rss_auth :revisions accept_key_auth :revisions
rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
def new
scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
@repository = Repository.factory(scm)
@repository.is_default = @project.repository.nil?
@repository.project = @project
render :layout => !request.xhr?
end
def create
attrs = pickup_extra_info
@repository = Repository.factory(params[:repository_scm], attrs[:attrs])
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end
@repository.project = @project
if request.post? && @repository.save
redirect_to settings_project_path(@project, :tab => 'repositories')
else
render :action => 'new'
end
end
def edit def edit
end @repository = @project.repository
if !@repository
def update @repository = Repository.factory(params[:repository_scm])
attrs = pickup_extra_info @repository.project = @project if @repository
@repository.attributes = attrs[:attrs]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end end
@repository.project = @project if request.post? && @repository
if request.put? && @repository.save @repository.attributes = params[:repository]
redirect_to settings_project_path(@project, :tab => 'repositories') @repository.save
else
render :action => 'edit'
end end
end render(:update) do |page|
page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'
def pickup_extra_info if @repository && !@project.repository
p = {} @project.reload #needed to reload association
p_extra = {} page.replace_html "main-menu", render_main_menu(@project)
params[:repository].each do |k, v|
if k =~ /^extra_/
p_extra[k] = v
else
p[k] = v
end end
end end
{:attrs => p, :attrs_extra => p_extra}
end end
private :pickup_extra_info
def committers def committers
@committers = @repository.committers @committers = @repository.committers
@users = @project.users @users = @project.users
@@ -101,66 +64,55 @@ class RepositoriesController < ApplicationController
# Build a hash with repository usernames as keys and corresponding user ids as values # Build a hash with repository usernames as keys and corresponding user ids as values
@repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h} @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project, :tab => 'repositories') redirect_to :action => 'committers', :id => @project
end end
end end
def destroy def destroy
@repository.destroy if request.delete? @repository.destroy
redirect_to settings_project_path(@project, :tab => 'repositories') redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
end end
def show def show
@repository.fetch_changesets if 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)
if request.xhr? if request.xhr?
@entries ? render(:partial => 'dir_list_content') : render(:nothing => true) @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
else else
(show_error_not_found; return) unless @entries (show_error_not_found; return) unless @entries
@changesets = @repository.latest_changesets(@path, @rev) @changesets = @repository.latest_changesets(@path, @rev)
@properties = @repository.properties(@path, @rev) @properties = @repository.properties(@path, @rev)
@repositories = @project.repositories
render :action => 'show' render :action => 'show'
end end
end end
alias_method :browse, :show alias_method :browse, :show
def changes def changes
@entry = @repository.entry(@path, @rev) @entry = @repository.entry(@path, @rev)
(show_error_not_found; return) unless @entry (show_error_not_found; return) unless @entry
@changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i) @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
@properties = @repository.properties(@path, @rev) @properties = @repository.properties(@path, @rev)
@changeset = @repository.find_changeset_by_name(@rev)
end end
def revisions def revisions
@changeset_count = @repository.changesets.count @changeset_count = @repository.changesets.count
@changeset_pages = Paginator.new self, @changeset_count, @changeset_pages = Paginator.new self, @changeset_count,
per_page_option, per_page_option,
params['page'] params['page']
@changesets = @repository.changesets.find(:all, @changesets = @repository.changesets.find(:all,
:limit => @changeset_pages.items_per_page, :limit => @changeset_pages.items_per_page,
:offset => @changeset_pages.current.offset, :offset => @changeset_pages.current.offset,
:include => [:user, :repository, :parents]) :include => [:user, :repository])
respond_to do |format| respond_to do |format|
format.html { render :layout => false if request.xhr? } format.html { render :layout => false if request.xhr? }
format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") } format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
end end
end end
def raw
entry_and_raw(true)
end
def entry def entry
entry_and_raw(false)
end
def entry_and_raw(is_raw)
@entry = @repository.entry(@path, @rev) @entry = @repository.entry(@path, @rev)
(show_error_not_found; return) unless @entry (show_error_not_found; return) unless @entry
@@ -169,107 +121,36 @@ class RepositoriesController < ApplicationController
@content = @repository.cat(@path, @rev) @content = @repository.cat(@path, @rev)
(show_error_not_found; return) unless @content (show_error_not_found; return) unless @content
if is_raw || if 'raw' == params[:format] || @content.is_binary_data? || (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte)
(@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) ||
! is_entry_text_data?(@content, @path)
# Force the download # Force the download
send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) } send_data @content, :filename => filename_for_content_disposition(@path.split('/').last)
send_type = Redmine::MimeType.of(@path)
send_opt[:type] = send_type.to_s if send_type
send_data @content, send_opt
else else
# Prevent empty lines when displaying a file with Windows style eol # Prevent empty lines when displaying a file with Windows style eol
# TODO: UTF-16
# Is this needs? AttachmentsController reads file simply.
@content.gsub!("\r\n", "\n") @content.gsub!("\r\n", "\n")
@changeset = @repository.find_changeset_by_name(@rev) end
end
end end
private :entry_and_raw
def is_entry_text_data?(ent, path)
# UTF-16 contains "\x00".
# It is very strict that file contains less than 30% of ascii symbols
# in non Western Europe.
return true if Redmine::MimeType.is_type?('text', path)
# Ruby 1.8.6 has a bug of integer divisions.
# http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
return false if ent.is_binary_data?
true
end
private :is_entry_text_data?
def annotate def annotate
@entry = @repository.entry(@path, @rev) @entry = @repository.entry(@path, @rev)
(show_error_not_found; return) unless @entry (show_error_not_found; return) unless @entry
@annotate = @repository.scm.annotate(@path, @rev) @annotate = @repository.scm.annotate(@path, @rev)
if @annotate.nil? || @annotate.empty? (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
(render_error l(:error_scm_annotate); return)
end
ann_buf_size = 0
@annotate.lines.each do |buf|
ann_buf_size += buf.size
end
if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte
(render_error l(:error_scm_annotate_big_text_file); return)
end
@changeset = @repository.find_changeset_by_name(@rev)
end end
def revision def revision
raise ChangesetNotFound if @rev.nil? || @rev.empty?
@changeset = @repository.find_changeset_by_name(@rev)
raise ChangesetNotFound unless @changeset
respond_to do |format| respond_to do |format|
format.html format.html
format.js {render :layout => false} format.js {render :layout => false}
end end
rescue ChangesetNotFound
show_error_not_found
end end
# Adds a related issue to a changeset
# POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
def add_related_issue
@issue = @changeset.find_referenced_issue_by_id(params[:issue_id])
if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
@issue = nil
end
if @issue
@changeset.issues << @issue
respond_to do |format|
format.js {
render :update do |page|
page.replace_html "related-issues", :partial => "related_issues"
page.visual_effect :highlight, "related-issue-#{@issue.id}"
end
}
end
else
respond_to do |format|
format.js {
render :update do |page|
page.alert(l(:label_issue) + ' ' + l('activerecord.errors.messages.invalid'))
end
}
end
end
end
# Removes a related issue from a changeset
# DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
def remove_related_issue
@issue = Issue.visible.find_by_id(params[:issue_id])
if @issue
@changeset.issues.delete(@issue)
end
respond_to do |format|
format.js {
render :update do |page|
page.remove "related-issue-#{@issue.id}"
end if @issue
}
end
end
def diff def diff
if params[:format] == 'diff' if params[:format] == 'diff'
@diff = @repository.diff(@path, @rev, @rev_to) @diff = @repository.diff(@path, @rev, @rev_to)
@@ -282,14 +163,14 @@ class RepositoriesController < ApplicationController
else else
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
@diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
# Save diff type as user preference # Save diff type as user preference
if User.current.logged? && @diff_type != User.current.pref[:diff_type] if User.current.logged? && @diff_type != User.current.pref[:diff_type]
User.current.pref[:diff_type] = @diff_type User.current.pref[:diff_type] = @diff_type
User.current.preference.save User.current.preference.save
end end
@cache_key = "repositories/diff/#{@repository.id}/" +
Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}") @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
unless read_fragment(@cache_key) unless read_fragment(@cache_key)
@diff = @repository.diff(@path, @rev, @rev_to) @diff = @repository.diff(@path, @rev, @rev_to)
show_error_not_found unless @diff show_error_not_found unless @diff
@@ -297,15 +178,14 @@ class RepositoriesController < ApplicationController
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
@changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
@diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
end end
end end
def stats def stats
end end
def graph def graph
data = nil data = nil
case params[:graph] case params[:graph]
when "commits_per_month" when "commits_per_month"
data = graph_commits_per_month(@repository) data = graph_commits_per_month(@repository)
@@ -319,30 +199,20 @@ class RepositoriesController < ApplicationController
render_404 render_404
end end
end end
private private
def find_repository
@repository = Repository.find(params[:id])
@project = @repository.project
rescue ActiveRecord::RecordNotFound
render_404
end
REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
def find_project_repository def find_repository
@project = Project.find(params[:id]) @project = Project.find(params[:id])
if params[:repository_id].present? @repository = @project.repository
@repository = @project.repositories.find_by_identifier_param(params[:repository_id])
else
@repository = @project.repository
end
(render_404; return false) unless @repository (render_404; return false) unless @repository
@path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s @path = params[:path].join('/') unless params[:path].nil?
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip @path ||= ''
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
@rev_to = params[:rev_to] @rev_to = params[:rev_to]
unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE) unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE)
if @repository.branches.blank? if @repository.branches.blank?
raise InvalidRevisionParam raise InvalidRevisionParam
@@ -354,41 +224,30 @@ class RepositoriesController < ApplicationController
show_error_not_found show_error_not_found
end end
def find_changeset
if @rev.present?
@changeset = @repository.find_changeset_by_name(@rev)
end
show_error_not_found unless @changeset
end
def show_error_not_found def show_error_not_found
render_error :message => l(:error_scm_not_found), :status => 404 render_error :message => l(:error_scm_not_found), :status => 404
end end
# Handler for Redmine::Scm::Adapters::CommandFailed exception # Handler for Redmine::Scm::Adapters::CommandFailed exception
def show_error_command_failed(exception) def show_error_command_failed(exception)
render_error l(:error_scm_command_failed, exception.message) render_error l(:error_scm_command_failed, exception.message)
end end
def graph_commits_per_month(repository) def graph_commits_per_month(repository)
@date_to = Date.today @date_to = Date.today
@date_from = @date_to << 11 @date_from = @date_to << 11
@date_from = Date.civil(@date_from.year, @date_from.month, 1) @date_from = Date.civil(@date_from.year, @date_from.month, 1)
commits_by_day = Changeset.count( commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
:all, :group => :commit_date,
:conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
commits_by_month = [0] * 12 commits_by_month = [0] * 12
commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
changes_by_day = Change.count( changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
:all, :group => :commit_date, :include => :changeset,
:conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
changes_by_month = [0] * 12 changes_by_month = [0] * 12
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
fields = [] fields = []
12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)} 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
graph = SVG::Graph::Bar.new( graph = SVG::Graph::Bar.new(
:height => 300, :height => 300,
:width => 800, :width => 800,
@@ -400,7 +259,7 @@ class RepositoriesController < ApplicationController
:graph_title => l(:label_commits_per_month), :graph_title => l(:label_commits_per_month),
:show_graph_title => true :show_graph_title => true
) )
graph.add_data( graph.add_data(
:data => commits_by_month[0..11].reverse, :data => commits_by_month[0..11].reverse,
:title => l(:label_revision_plural) :title => l(:label_revision_plural)
@@ -410,28 +269,28 @@ class RepositoriesController < ApplicationController
:data => changes_by_month[0..11].reverse, :data => changes_by_month[0..11].reverse,
:title => l(:label_change_plural) :title => l(:label_change_plural)
) )
graph.burn graph.burn
end end
def graph_commits_per_author(repository) def graph_commits_per_author(repository)
commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id]) commits_by_author = repository.changesets.count(:all, :group => :committer)
commits_by_author.to_a.sort! {|x, y| x.last <=> y.last} commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id]) changes_by_author = repository.changes.count(:all, :group => :committer)
h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o} h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
fields = commits_by_author.collect {|r| r.first} fields = commits_by_author.collect {|r| r.first}
commits_data = commits_by_author.collect {|r| r.last} commits_data = commits_by_author.collect {|r| r.last}
changes_data = commits_by_author.collect {|r| h[r.first] || 0} changes_data = commits_by_author.collect {|r| h[r.first] || 0}
fields = fields + [""]*(10 - fields.length) if fields.length<10 fields = fields + [""]*(10 - fields.length) if fields.length<10
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 adress 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 => 400, :height => 400,
:width => 800, :width => 800,
@@ -443,15 +302,34 @@ class RepositoriesController < ApplicationController
:graph_title => l(:label_commits_per_author), :graph_title => l(:label_commits_per_author),
:show_graph_title => true :show_graph_title => true
) )
graph.add_data( graph.add_data(
:data => commits_data, :data => commits_data,
:title => l(:label_revision_plural) :title => l(:label_revision_plural)
) )
graph.add_data( graph.add_data(
:data => changes_data, :data => changes_data,
:title => l(:label_change_plural) :title => l(:label_change_plural)
) )
graph.burn graph.burn
end end
end
class Date
def months_ago(date = Date.today)
(date.year - self.year)*12 + (date.month - self.month)
end
def weeks_ago(date = Date.today)
(date.year - self.year)*52 + (date.cweek - self.cweek)
end
end end
class String
def with_leading_slash
starts_with?('/') ? self : "/#{self}"
end
end
+19 -40
View File
@@ -1,48 +1,36 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class RolesController < ApplicationController class RolesController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin
before_filter :require_admin, :except => :index verify :method => :post, :only => [ :destroy, :move ],
before_filter :require_admin_or_api_request, :only => :index :redirect_to => { :action => :index }
before_filter :find_role, :only => [:edit, :update, :destroy]
accept_api_auth :index
def index def index
respond_to do |format| @role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
format.html { render :action => "index", :layout => false if request.xhr?
@role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
render :action => "index", :layout => false if request.xhr?
}
format.api {
@roles = Role.givable.all
}
end
end end
def new def new
# Prefills the form with 'Non member' role permissions # Prefills the form with 'Non member' role permissions
@role = Role.new(params[:role] || {:permissions => Role.non_member.permissions}) @role = Role.new(params[:role] || {:permissions => Role.non_member.permissions})
@roles = Role.sorted.all
end
def create
@role = Role.new(params[:role])
if request.post? && @role.save if request.post? && @role.save
# workflow copy # workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from])) if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from]))
@@ -51,33 +39,32 @@ class RolesController < ApplicationController
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index' redirect_to :action => 'index'
else else
@roles = Role.sorted.all @permissions = @role.setable_permissions
render :action => 'new' @roles = Role.find :all, :order => 'builtin, position'
end end
end end
def edit def edit
end @role = Role.find(params[:id])
if request.post? and @role.update_attributes(params[:role])
def update
if request.put? and @role.update_attributes(params[:role])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index' redirect_to :action => 'index'
else else
render :action => 'edit' @permissions = @role.setable_permissions
end end
end end
def destroy def destroy
@role = Role.find(params[:id])
@role.destroy @role.destroy
redirect_to :action => 'index' redirect_to :action => 'index'
rescue rescue
flash[:error] = l(:error_can_not_remove_role) flash[:error] = l(:error_can_not_remove_role)
redirect_to :action => 'index' redirect_to :action => 'index'
end end
def permissions def report
@roles = Role.sorted.all @roles = Role.find(:all, :order => 'builtin, position')
@permissions = Redmine::AccessControl.permissions.select { |p| !p.public? } @permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
if request.post? if request.post?
@roles.each do |role| @roles.each do |role|
@@ -88,12 +75,4 @@ class RolesController < ApplicationController
redirect_to :action => 'index' redirect_to :action => 'index'
end end
end end
private
def find_role
@role = Role.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+20 -20
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -24,9 +24,9 @@ class SearchController < ApplicationController
def index def index
@question = params[:q] || "" @question = params[:q] || ""
@question.strip! @question.strip!
@all_words = params[:all_words] ? params[:all_words].present? : true @all_words = params[:all_words] || (params[:submit] ? false : true)
@titles_only = params[:titles_only] ? params[:titles_only].present? : false @titles_only = !params[:titles_only].nil?
projects_to_search = projects_to_search =
case params[:scope] case params[:scope]
when 'all' when 'all'
@@ -34,20 +34,20 @@ class SearchController < ApplicationController
when 'my_projects' when 'my_projects'
User.current.memberships.collect(&:project) User.current.memberships.collect(&:project)
when 'subprojects' when 'subprojects'
@project ? (@project.self_and_descendants.active.all) : nil @project ? (@project.self_and_descendants.active) : nil
else else
@project @project
end end
offset = nil offset = nil
begin; offset = params[:offset].to_time if params[:offset]; rescue; end begin; offset = params[:offset].to_time if params[:offset]; rescue; end
# quick jump to an issue # quick jump to an issue
if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1.to_i) if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1.to_i)
redirect_to :controller => "issues", :action => "show", :id => $1 redirect_to :controller => "issues", :action => "show", :id => $1
return return
end end
@object_types = Redmine::Search.available_search_types.dup @object_types = Redmine::Search.available_search_types.dup
if projects_to_search.is_a? Project if projects_to_search.is_a? Project
# don't search projects # don't search projects
@@ -55,23 +55,23 @@ class SearchController < ApplicationController
# only show what the user is allowed to view # only show what the user is allowed to view
@object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)} @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)}
end end
@scope = @object_types.select {|t| params[t]} @scope = @object_types.select {|t| params[t]}
@scope = @object_types if @scope.empty? @scope = @object_types if @scope.empty?
# extract tokens from the question # extract tokens from the question
# eg. hello "bye bye" => ["hello", "bye bye"] # eg. hello "bye bye" => ["hello", "bye bye"]
@tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')} @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
# tokens must be at least 2 characters long # tokens must be at least 2 characters long
@tokens = @tokens.uniq.select {|w| w.length > 1 } @tokens = @tokens.uniq.select {|w| w.length > 1 }
if !@tokens.empty? if !@tokens.empty?
# no more than 5 tokens to search for # no more than 5 tokens to search for
@tokens.slice! 5..-1 if @tokens.size > 5 @tokens.slice! 5..-1 if @tokens.size > 5
@results = [] @results = []
@results_by_type = Hash.new {|h,k| h[k] = 0} @results_by_type = Hash.new {|h,k| h[k] = 0}
limit = 10 limit = 10
@scope.each do |s| @scope.each do |s|
r, c = s.singularize.camelcase.constantize.search(@tokens, projects_to_search, r, c = s.singularize.camelcase.constantize.search(@tokens, projects_to_search,
@@ -87,13 +87,13 @@ class SearchController < ApplicationController
if params[:previous].nil? if params[:previous].nil?
@pagination_previous_date = @results[0].event_datetime if offset && @results[0] @pagination_previous_date = @results[0].event_datetime if offset && @results[0]
if @results.size > limit if @results.size > limit
@pagination_next_date = @results[limit-1].event_datetime @pagination_next_date = @results[limit-1].event_datetime
@results = @results[0, limit] @results = @results[0, limit]
end end
else else
@pagination_next_date = @results[-1].event_datetime if offset && @results[-1] @pagination_next_date = @results[-1].event_datetime if offset && @results[-1]
if @results.size > limit if @results.size > limit
@pagination_previous_date = @results[-(limit)].event_datetime @pagination_previous_date = @results[-(limit)].event_datetime
@results = @results[-(limit), limit] @results = @results[-(limit), limit]
end end
end end
@@ -103,7 +103,7 @@ class SearchController < ApplicationController
render :layout => false if request.xhr? render :layout => false if request.xhr?
end end
private private
def find_optional_project def find_optional_project
return true unless params[:id] return true unless params[:id]
@project = Project.find(params[:id]) @project = Project.find(params[:id])
+6 -7
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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,7 @@
class SettingsController < ApplicationController class SettingsController < ApplicationController
layout 'admin' layout 'admin'
menu_item :plugins, :only => :plugin
before_filter :require_admin before_filter :require_admin
def index def index
@@ -44,7 +43,7 @@ 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?
Redmine::Themes.rescan Redmine::Themes.rescan
end end
end end
@@ -52,12 +51,12 @@ class SettingsController < ApplicationController
def plugin def plugin
@plugin = Redmine::Plugin.find(params[:id]) @plugin = Redmine::Plugin.find(params[:id])
if request.post? if request.post?
Setting.send "plugin_#{@plugin.id}=", params[:settings] Setting["plugin_#{@plugin.id}"] = params[:settings]
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'plugin', :id => @plugin.id redirect_to :action => 'plugin', :id => @plugin.id
else else
@partial = @plugin.settings[:partial] @partial = @plugin.settings[:partial]
@settings = Setting.send "plugin_#{@plugin.id}" @settings = Setting["plugin_#{@plugin.id}"]
end end
rescue Redmine::PluginNotFound rescue Redmine::PluginNotFound
render_404 render_404
+15 -32
View File
@@ -1,70 +1,53 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2009 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class SysController < ActionController::Base class SysController < ActionController::Base
before_filter :check_enabled before_filter :check_enabled
def projects def projects
p = Project.active.has_module(:repository).find( p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
:all, render :xml => p.to_xml(:include => :repository)
:include => :repository,
:order => "#{Project.table_name}.identifier"
)
# extra_info attribute from repository breaks activeresource client
render :xml => p.to_xml(
:only => [:id, :identifier, :name, :is_public, :status],
:include => {:repository => {:only => [:id, :url]}}
)
end end
def create_project_repository def create_project_repository
project = Project.find(params[:id]) project = Project.find(params[:id])
if project.repository if project.repository
render :nothing => true, :status => 409 render :nothing => true, :status => 409
else else
logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}." logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
repository = Repository.factory(params[:vendor], params[:repository]) project.repository = Repository.factory(params[:vendor], params[:repository])
repository.project = project if project.repository && project.repository.save
if repository.save render :xml => project.repository, :status => 201
render :xml => {repository.class.name.underscore.gsub('/', '-') => {:id => repository.id, :url => repository.url}}, :status => 201
else else
render :nothing => true, :status => 422 render :nothing => true, :status => 422
end end
end end
end end
def fetch_changesets def fetch_changesets
projects = [] projects = []
scope = Project.active.has_module(:repository)
if params[:id] if params[:id]
project = nil projects << Project.active.has_module(:repository).find(params[:id])
if params[:id].to_s =~ /^\d*$/
project = scope.find(params[:id])
else
project = scope.find_by_identifier(params[:id])
end
raise ActiveRecord::RecordNotFound unless project
projects << project
else else
projects = scope.all projects = Project.active.has_module(:repository).find(:all, :include => :repository)
end end
projects.each do |project| projects.each do |project|
project.repositories.each do |repository| if project.repository
repository.fetch_changesets project.repository.fetch_changesets
end end
end end
render :nothing => true, :status => 200 render :nothing => true, :status => 200
@@ -0,0 +1,209 @@
class TimeEntryReportsController < ApplicationController
menu_item :issues
before_filter :find_optional_project
before_filter :load_available_criterias
helper :sort
include SortHelper
helper :issues
helper :timelog
include TimelogHelper
helper :custom_fields
include CustomFieldsHelper
def report
@criterias = params[:criterias] || []
@criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
@criterias.uniq!
@criterias = @criterias[0,3]
@columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month'
retrieve_date_range
unless @criterias.empty?
sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
sql_condition = ''
if @project.nil?
sql_condition = Project.allowed_to_condition(User.current, :view_time_entries)
elsif @issue.nil?
sql_condition = @project.project_condition(Setting.display_subprojects_issues?)
else
sql_condition = "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
end
sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
sql << " FROM #{TimeEntry.table_name}"
sql << time_report_joins
sql << " WHERE"
sql << " (%s) AND" % sql_condition
sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)]
sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
@hours = ActiveRecord::Base.connection.select_all(sql)
@hours.each do |row|
case @columns
when 'year'
row['year'] = row['tyear']
when 'month'
row['month'] = "#{row['tyear']}-#{row['tmonth']}"
when 'week'
row['week'] = "#{row['tyear']}-#{row['tweek']}"
when 'day'
row['day'] = "#{row['spent_on']}"
end
end
@total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
@periods = []
# Date#at_beginning_of_ not supported in Rails 1.2.x
date_from = @from.to_time
# 100 columns max
while date_from <= @to.to_time && @periods.length < 100
case @columns
when 'year'
@periods << "#{date_from.year}"
date_from = (date_from + 1.year).at_beginning_of_year
when 'month'
@periods << "#{date_from.year}-#{date_from.month}"
date_from = (date_from + 1.month).at_beginning_of_month
when 'week'
@periods << "#{date_from.year}-#{date_from.to_date.cweek}"
date_from = (date_from + 7.day).at_beginning_of_week
when 'day'
@periods << "#{date_from.to_date}"
date_from = date_from + 1.day
end
end
end
respond_to do |format|
format.html { render :layout => !request.xhr? }
format.csv { send_data(report_to_csv(@criterias, @periods, @hours), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
end
end
private
# TODO: duplicated in TimelogController
def find_optional_project
if !params[:issue_id].blank?
@issue = Issue.find(params[:issue_id])
@project = @issue.project
elsif !params[:project_id].blank?
@project = Project.find(params[:project_id])
end
deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
end
# Retrieves the date range based on predefined ranges or specific from/to param dates
# TODO: duplicated in TimelogController
def retrieve_date_range
@free_period = false
@from, @to = nil, nil
if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
case params[:period].to_s
when 'today'
@from = @to = Date.today
when 'yesterday'
@from = @to = Date.today - 1
when 'current_week'
@from = Date.today - (Date.today.cwday - 1)%7
@to = @from + 6
when 'last_week'
@from = Date.today - 7 - (Date.today.cwday - 1)%7
@to = @from + 6
when '7_days'
@from = Date.today - 7
@to = Date.today
when 'current_month'
@from = Date.civil(Date.today.year, Date.today.month, 1)
@to = (@from >> 1) - 1
when 'last_month'
@from = Date.civil(Date.today.year, Date.today.month, 1) << 1
@to = (@from >> 1) - 1
when '30_days'
@from = Date.today - 30
@to = Date.today
when 'current_year'
@from = Date.civil(Date.today.year, 1, 1)
@to = Date.civil(Date.today.year, 12, 31)
end
elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
@free_period = true
else
# default
end
@from, @to = @to, @from if @from && @to && @from > @to
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
end
def load_available_criterias
@available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
:klass => Project,
:label => :label_project},
'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
:klass => Version,
:label => :label_version},
'category' => {:sql => "#{Issue.table_name}.category_id",
:klass => IssueCategory,
:label => :field_category},
'member' => {:sql => "#{TimeEntry.table_name}.user_id",
:klass => User,
:label => :label_member},
'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
:klass => Tracker,
:label => :label_tracker},
'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
:klass => TimeEntryActivity,
:label => :label_activity},
'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
:klass => Issue,
:label => :label_issue}
}
# Add list and boolean custom fields as available criterias
custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields)
custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)",
:format => cf.field_format,
:label => cf.name}
end if @project
# Add list and boolean time entry custom fields
TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)",
:format => cf.field_format,
:label => cf.name}
end
# Add list and boolean time entry activity custom fields
TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id)",
:format => cf.field_format,
:label => cf.name}
end
call_hook(:controller_timelog_available_criterias, { :available_criterias => @available_criterias, :project => @project })
@available_criterias
end
def time_report_joins
sql = ''
sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
# TODO: rename hook
call_hook(:controller_timelog_time_report_joins, {:sql => sql} )
sql
end
end
+116 -188
View File
@@ -1,42 +1,35 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2010 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class TimelogController < ApplicationController class TimelogController < ApplicationController
menu_item :issues menu_item :issues
before_filter :find_project, :only => [:new, :create]
before_filter :find_project_for_new_time_entry, :only => [:create] before_filter :find_time_entry, :only => [:show, :edit, :update, :destroy]
before_filter :find_time_entry, :only => [:show, :edit, :update] before_filter :authorize, :except => [:index]
before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy] before_filter :find_optional_project, :only => [:index]
before_filter :authorize, :except => [:new, :index, :report] accept_key_auth :index, :show, :create, :update, :destroy
before_filter :find_optional_project, :only => [:index, :report]
before_filter :find_optional_project_for_new_time_entry, :only => [:new]
before_filter :authorize_global, :only => [:new, :index, :report]
accept_rss_auth :index
accept_api_auth :index, :show, :create, :update, :destroy
helper :sort helper :sort
include SortHelper include SortHelper
helper :issues helper :issues
include TimelogHelper include TimelogHelper
helper :custom_fields helper :custom_fields
include CustomFieldsHelper include CustomFieldsHelper
def index def index
sort_init 'spent_on', 'desc' sort_init 'spent_on', 'desc'
sort_update 'spent_on' => 'spent_on', sort_update 'spent_on' => 'spent_on',
@@ -45,70 +38,65 @@ class TimelogController < ApplicationController
'project' => "#{Project.table_name}.name", 'project' => "#{Project.table_name}.name",
'issue' => 'issue_id', 'issue' => 'issue_id',
'hours' => 'hours' 'hours' => 'hours'
retrieve_date_range cond = ARCondition.new
if @project.nil?
scope = TimeEntry.visible.spent_between(@from, @to) cond << Project.allowed_to_condition(User.current, :view_time_entries)
if @issue elsif @issue.nil?
scope = scope.on_issue(@issue) cond << @project.project_condition(Setting.display_subprojects_issues?)
elsif @project else
scope = scope.on_project(@project, Setting.display_subprojects_issues?) cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
end end
retrieve_date_range
cond << ['spent_on BETWEEN ? AND ?', @from, @to]
respond_to do |format| TimeEntry.visible_by(User.current) do
format.html { respond_to do |format|
# Paginate results format.html {
@entry_count = scope.count # Paginate results
@entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] @entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions)
@entries = scope.all( @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
:include => [:project, :activity, :user, {:issue => :tracker}], @entries = TimeEntry.find(:all,
:order => sort_clause, :include => [:project, :activity, :user, {:issue => :tracker}],
:limit => @entry_pages.items_per_page, :conditions => cond.conditions,
:offset => @entry_pages.current.offset :order => sort_clause,
) :limit => @entry_pages.items_per_page,
@total_hours = scope.sum(:hours).to_f :offset => @entry_pages.current.offset)
@total_hours = TimeEntry.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f
render :layout => !request.xhr? render :layout => !request.xhr?
} }
format.api { format.api {
@entry_count = scope.count @entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions)
@offset, @limit = api_offset_and_limit @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
@entries = scope.all( @entries = TimeEntry.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}], :include => [:project, :activity, :user, {:issue => :tracker}],
:order => sort_clause, :conditions => cond.conditions,
:limit => @limit, :order => sort_clause,
:offset => @offset :limit => @entry_pages.items_per_page,
) :offset => @entry_pages.current.offset)
} }
format.atom { format.atom {
entries = scope.all( entries = TimeEntry.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}], :include => [:project, :activity, :user, {:issue => :tracker}],
:order => "#{TimeEntry.table_name}.created_on DESC", :conditions => cond.conditions,
:limit => Setting.feeds_limit.to_i :order => "#{TimeEntry.table_name}.created_on DESC",
) :limit => Setting.feeds_limit.to_i)
render_feed(entries, :title => l(:label_spent_time)) render_feed(entries, :title => l(:label_spent_time))
} }
format.csv { format.csv {
# Export all entries # Export all entries
@entries = scope.all( @entries = TimeEntry.find(:all,
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:order => sort_clause :conditions => cond.conditions,
) :order => sort_clause)
send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv') send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
} }
end
end end
end end
def report
retrieve_date_range
@report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to)
respond_to do |format|
format.html { render :layout => !request.xhr? }
format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
end
end
def show def show
respond_to do |format| respond_to do |format|
# TODO: Implement html response # TODO: Implement html response
@@ -119,52 +107,47 @@ class TimelogController < ApplicationController
def new def new
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.safe_attributes = params[:time_entry] @time_entry.attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
render :action => 'edit'
end end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.safe_attributes = params[:time_entry] @time_entry.attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
if @time_entry.save if @time_entry.save
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_update)
if params[:continue] redirect_back_or_default :action => 'index', :project_id => @time_entry.project
if params[:project_id]
redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue,
:time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url]
else
redirect_to :action => 'new',
:time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url]
end
else
redirect_back_or_default :action => 'index', :project_id => @time_entry.project
end
} }
format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) } format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
end end
else else
respond_to do |format| respond_to do |format|
format.html { render :action => 'new' } format.html { render :action => 'edit' }
format.api { render_validation_errors(@time_entry) } format.api { render_validation_errors(@time_entry) }
end end
end end
end end
def edit def edit
@time_entry.safe_attributes = params[:time_entry] @time_entry.attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
end end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update def update
@time_entry.safe_attributes = params[:time_entry] @time_entry.attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
if @time_entry.save if @time_entry.save
respond_to do |format| respond_to do |format|
format.html { format.html {
@@ -178,57 +161,30 @@ class TimelogController < ApplicationController
format.html { render :action => 'edit' } format.html { render :action => 'edit' }
format.api { render_validation_errors(@time_entry) } format.api { render_validation_errors(@time_entry) }
end end
end end
end
def bulk_edit
@available_activities = TimeEntryActivity.shared.active
@custom_fields = TimeEntry.first.available_custom_fields
end
def bulk_update
attributes = parse_params_for_bulk_time_entry_attributes(params)
unsaved_time_entry_ids = []
@time_entries.each do |time_entry|
time_entry.reload
time_entry.safe_attributes = attributes
call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
unless time_entry.save
# Keep unsaved time_entry ids to display them in flash error
unsaved_time_entry_ids << time_entry.id
end
end
set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
destroyed = TimeEntry.transaction do if @time_entry.destroy && @time_entry.destroyed?
@time_entries.each do |t| respond_to do |format|
unless t.destroy && t.destroyed? format.html {
raise ActiveRecord::Rollback flash[:notice] = l(:notice_successful_delete)
end redirect_to :back
}
format.api { head :ok }
end
else
respond_to do |format|
format.html {
flash[:error] = l(:notice_unable_delete_time_entry)
redirect_to :back
}
format.api { render_validation_errors(@time_entry) }
end end
end end
rescue ::ActionController::RedirectBackError
respond_to do |format| redirect_to :action => 'index', :project_id => @time_entry.project
format.html {
if destroyed
flash[:notice] = l(:notice_successful_delete)
else
flash[:error] = l(:notice_unable_delete_time_entry)
end
redirect_back_or_default(:action => 'index', :project_id => @projects.first)
}
format.api {
if destroyed
head :ok
else
render_validation_errors(@time_entries)
end
}
end
end end
private private
@@ -243,45 +199,20 @@ private
render_404 render_404
end end
def find_time_entries def find_project
@time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
raise ActiveRecord::RecordNotFound if @time_entries.empty?
@projects = @time_entries.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1
rescue ActiveRecord::RecordNotFound
render_404
end
def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
if unsaved_time_entry_ids.empty?
flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
else
flash[:error] = l(:notice_failed_to_save_time_entries,
:count => unsaved_time_entry_ids.size,
:total => time_entries.size,
:ids => '#' + unsaved_time_entry_ids.join(', #'))
end
end
def find_optional_project_for_new_time_entry
if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
@project = Project.find(project_id)
end
if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present? if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
@issue = Issue.find(issue_id) @issue = Issue.find(issue_id)
@project ||= @issue.project @project = @issue.project
elsif (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
@project = Project.find(project_id)
else
render_404
return false
end end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def find_project_for_new_time_entry
find_optional_project_for_new_time_entry
if @project.nil?
render_404
end
end
def find_optional_project def find_optional_project
if !params[:issue_id].blank? if !params[:issue_id].blank?
@issue = Issue.find(params[:issue_id]) @issue = Issue.find(params[:issue_id])
@@ -289,8 +220,9 @@ private
elsif !params[:project_id].blank? elsif !params[:project_id].blank?
@project = Project.find(params[:project_id]) @project = Project.find(params[:project_id])
end end
deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
end end
# Retrieves the date range based on predefined ranges or specific from/to param dates # Retrieves the date range based on predefined ranges or specific from/to param dates
def retrieve_date_range def retrieve_date_range
@free_period = false @free_period = false
@@ -331,14 +263,10 @@ private
else else
# default # default
end end
@from, @to = @to, @from if @from && @to && @from > @to @from, @to = @to, @from if @from && @to && @from > @to
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
end end
def parse_params_for_bulk_time_entry_attributes(params)
attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
attributes
end
end end
+14 -33
View File
@@ -1,46 +1,33 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2009 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class TrackersController < ApplicationController class TrackersController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin
before_filter :require_admin, :except => :index verify :method => :post, :only => :destroy, :redirect_to => { :action => :index }
before_filter :require_admin_or_api_request, :only => :index
accept_api_auth :index
def index def index
respond_to do |format| @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
format.html { render :action => "index", :layout => false if request.xhr?
@tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
render :action => "index", :layout => false if request.xhr?
}
format.api {
@trackers = Tracker.all
}
end
end end
def new def new
@tracker ||= Tracker.new(params[:tracker])
@trackers = Tracker.find :all, :order => 'position'
@projects = Project.find(:all)
end
def create
@tracker = Tracker.new(params[:tracker]) @tracker = Tracker.new(params[:tracker])
if request.post? and @tracker.save if request.post? and @tracker.save
# workflow copy # workflow copy
@@ -51,26 +38,20 @@ class TrackersController < ApplicationController
redirect_to :action => 'index' redirect_to :action => 'index'
return return
end end
new @trackers = Tracker.find :all, :order => 'position'
render :action => 'new' @projects = Project.find(:all)
end end
def edit def edit
@tracker ||= Tracker.find(params[:id])
@projects = Project.find(:all)
end
def update
@tracker = Tracker.find(params[:id]) @tracker = Tracker.find(params[:id])
if request.put? and @tracker.update_attributes(params[:tracker]) if request.post? and @tracker.update_attributes(params[:tracker])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index' redirect_to :action => 'index'
return return
end end
edit @projects = Project.find(:all)
render :action => 'edit'
end end
def destroy def destroy
@tracker = Tracker.find(params[:id]) @tracker = Tracker.find(params[:id])
unless @tracker.issues.empty? unless @tracker.issues.empty?
@@ -79,5 +60,5 @@ class TrackersController < ApplicationController
@tracker.destroy @tracker.destroy
end end
redirect_to :action => 'index' redirect_to :action => 'index'
end end
end end
+50 -52
View File
@@ -1,80 +1,80 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2010 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class UsersController < ApplicationController class UsersController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin, :except => :show before_filter :require_admin, :except => :show
before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership] before_filter :find_user, :only => [:show, :edit, :update, :edit_membership, :destroy_membership]
accept_api_auth :index, :show, :create, :update, :destroy accept_key_auth :index, :show, :create, :update
helper :sort helper :sort
include SortHelper include SortHelper
helper :custom_fields helper :custom_fields
include CustomFieldsHelper include CustomFieldsHelper
def index def index
sort_init 'login', 'asc' sort_init 'login', 'asc'
sort_update %w(login firstname lastname mail admin created_on last_login_on) sort_update %w(login firstname lastname mail admin created_on last_login_on)
case params[:format] case params[:format]
when 'xml', 'json' when 'xml', 'json'
@offset, @limit = api_offset_and_limit @offset, @limit = api_offset_and_limit
else else
@limit = per_page_option @limit = per_page_option
end end
@status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
@status = params[:status] || 1 unless params[:name].blank?
name = "%#{params[:name].strip.downcase}%"
scope = User.logged.status(@status) c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
scope = scope.like(params[:name]) if params[:name].present? end
scope = scope.in_group(params[:group_id]) if params[:group_id].present?
@user_count = User.count(:conditions => c.conditions)
@user_count = scope.count
@user_pages = Paginator.new self, @user_count, @limit, params['page'] @user_pages = Paginator.new self, @user_count, @limit, params['page']
@offset ||= @user_pages.current.offset @offset ||= @user_pages.current.offset
@users = scope.find :all, @users = User.find :all,
:order => sort_clause, :order => sort_clause,
:conditions => c.conditions,
:limit => @limit, :limit => @limit,
:offset => @offset :offset => @offset
respond_to do |format| respond_to do |format|
format.html { format.html { render :layout => !request.xhr? }
@groups = Group.all.sort
render :layout => !request.xhr?
}
format.api format.api
end end
end end
def show def show
# show projects based on current user visibility # show projects based on current user visibility
@memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current)) @memberships = @user.memberships.all(:conditions => Project.visible_by(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)
unless User.current.admin? unless User.current.admin?
if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?) if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
render_404 render_404
return return
end end
end end
respond_to do |format| respond_to do |format|
format.html { render :layout => 'base' } format.html { render :layout => 'base' }
format.api format.api
@@ -85,7 +85,8 @@ class UsersController < ApplicationController
@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)
@auth_sources = AuthSource.find(:all) @auth_sources = AuthSource.find(:all)
end end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
@user.safe_attributes = params[:user] @user.safe_attributes = params[:user]
@@ -93,19 +94,21 @@ class UsersController < ApplicationController
@user.login = params[:user][:login] @user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
# TODO: Similar to My#account
@user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
if @user.save if @user.save
@user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
@user.pref.save @user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information] Mailer.deliver_account_information(@user, params[:user][:password]) if params[:send_information]
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to(params[:continue] ? redirect_to(params[:continue] ?
{:controller => 'users', :action => 'new'} : {:controller => 'users', :action => 'new'} :
{:controller => 'users', :action => 'edit', :id => @user} {:controller => 'users', :action => 'edit', :id => @user}
) )
} }
@@ -127,7 +130,8 @@ class UsersController < ApplicationController
@auth_sources = AuthSource.find(:all) @auth_sources = AuthSource.find(:all)
@membership ||= Member.new @membership ||= Member.new
end end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update def update
@user.admin = params[:user][:admin] if params[:user][:admin] @user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login] @user.login = params[:user][:login] if params[:user][:login]
@@ -146,15 +150,15 @@ class UsersController < ApplicationController
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
if was_activated if was_activated
Mailer.account_activated(@user).deliver Mailer.deliver_account_activated(@user)
elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil? elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
Mailer.account_information(@user, params[:user][:password]).deliver Mailer.deliver_account_information(@user, params[:user][:password])
end end
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to_referer_or edit_user_path(@user) redirect_to :back
} }
format.api { head :ok } format.api { head :ok }
end end
@@ -169,19 +173,13 @@ class UsersController < ApplicationController
format.api { render_validation_errors(@user) } format.api { render_validation_errors(@user) }
end end
end end
end rescue ::ActionController::RedirectBackError
redirect_to :controller => 'users', :action => 'edit', :id => @user
def destroy
@user.destroy
respond_to do |format|
format.html { redirect_to_referer_or(users_url) }
format.api { head :ok }
end
end end
def edit_membership def edit_membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user) @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save @membership.save if request.post?
respond_to do |format| respond_to do |format|
if @membership.valid? if @membership.valid?
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
@@ -200,10 +198,10 @@ class UsersController < ApplicationController
end end
end end
end end
def destroy_membership def destroy_membership
@membership = Member.find(params[:membership_id]) @membership = Member.find(params[:membership_id])
if @membership.deletable? if request.post? && @membership.deletable?
@membership.destroy @membership.destroy
end end
respond_to do |format| respond_to do |format|
@@ -211,9 +209,9 @@ class UsersController < ApplicationController
format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} } format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
end end
end end
private private
def find_user def find_user
if params[:id] == 'current' if params[:id] == 'current'
require_login || return require_login || return
+56 -102
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -23,77 +23,55 @@ class VersionsController < ApplicationController
before_filter :find_project, :only => [:index, :new, :create, :close_completed] before_filter :find_project, :only => [:index, :new, :create, :close_completed]
before_filter :authorize before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
helper :custom_fields helper :custom_fields
helper :projects helper :projects
def index def index
respond_to do |format| @trackers = @project.trackers.find(:all, :order => 'position')
format.html { retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
@trackers = @project.trackers.find(:all, :order => 'position') @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?}) project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id] @versions = @project.shared_versions || []
@versions += @project.rolled_up_versions.visible if @with_subprojects
@versions = @project.shared_versions || [] @versions = @versions.uniq.sort
@versions += @project.rolled_up_versions.visible if @with_subprojects @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
@versions = @versions.uniq.sort
unless params[:completed] @issues_by_version = {}
@completed_versions = @versions.select {|version| version.closed? || version.completed? } unless @selected_tracker_ids.empty?
@versions -= @completed_versions @versions.each do |version|
end issues = version.fixed_issues.visible.find(:all,
:include => [:project, :status, :tracker, :priority],
@issues_by_version = {} :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids},
if @selected_tracker_ids.any? && @versions.any? :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
issues = Issue.visible.all( @issues_by_version[version] = issues
:include => [:project, :status, :tracker, :priority, :fixed_version],
:conditions => {: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"
)
@issues_by_version = issues.group_by(&:fixed_version)
end
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
}
format.api {
@versions = @project.shared_versions.all
}
end
end
def show
respond_to do |format|
format.html {
@issues = @version.fixed_issues.visible.find(:all,
:include => [:status, :tracker, :priority],
:order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
}
format.api
end
end
def new
@version = @project.versions.build
@version.safe_attributes = params[:version]
respond_to do |format|
format.html
format.js do
render :update do |page|
page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
page << "showModal('ajax-modal', '600px');"
page << "Form.Element.focus('version_name');"
end
end end
end end
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
end end
def create def show
@issues = @version.fixed_issues.visible.find(:all,
:include => [:status, :tracker, :priority],
:order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
end
def new
@version = @project.versions.build @version = @project.versions.build
if params[:version] if params[:version]
attributes = params[:version].dup attributes = params[:version].dup
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing']) attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
@version.safe_attributes = attributes @version.attributes = attributes
end
end
def create
# TODO: refactor with code above in #new
@version = @project.versions.build
if params[:version]
attributes = params[:version].dup
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
@version.attributes = attributes
end end
if request.post? if request.post?
@@ -101,30 +79,21 @@ class VersionsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end end
format.js do format.js do
render(:update) {|page| # IE doesn't support the replace_html rjs method for select box options
page << 'hideModal();' render(:update) {|page| page.replace "issue_fixed_version_id",
# IE doesn't support the replace_html rjs method for select box options content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
page.replace "issue_fixed_version_id",
content_tag('select', content_tag('option') + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
} }
end end
format.api do
render :action => 'show', :status => :created, :location => version_url(@version)
end
end end
else else
respond_to do |format| respond_to do |format|
format.html { render :action => 'new' } format.html { render :action => 'new' }
format.js do format.js do
render :update do |page| render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
page << "Form.Element.focus('version_name');"
end
end end
format.api { render_validation_errors(@version) }
end end
end end
end end
@@ -132,29 +101,22 @@ class VersionsController < ApplicationController
def edit def edit
end end
def update def update
if request.put? && params[:version] if request.put? && params[:version]
attributes = params[:version].dup attributes = params[:version].dup
attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing']) attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
@version.safe_attributes = attributes if @version.update_attributes(attributes)
if @version.save flash[:notice] = l(:notice_successful_update)
respond_to do |format| redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
}
format.api { head :ok }
end
else else
respond_to do |format| respond_to do |format|
format.html { render :action => 'edit' } format.html { render :action => 'edit' }
format.api { render_validation_errors(@version) }
end end
end end
end end
end end
def close_completed def close_completed
if request.put? if request.put?
@project.close_completed_versions @project.close_completed_versions
@@ -165,21 +127,13 @@ class VersionsController < ApplicationController
def destroy def destroy
if @version.fixed_issues.empty? if @version.fixed_issues.empty?
@version.destroy @version.destroy
respond_to do |format| redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project }
format.api { head :ok }
end
else else
respond_to do |format| flash[:error] = l(:notice_unable_delete_version)
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
flash[:error] = l(:notice_unable_delete_version)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
}
format.api { head :unprocessable_entity }
end
end end
end end
def status_by def status_by
respond_to do |format| respond_to do |format|
format.html { render :action => 'show' } format.html { render :action => 'show' }
+39 -66
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -19,7 +19,11 @@ class WatchersController < ApplicationController
before_filter :find_project before_filter :find_project
before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch] before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
before_filter :authorize, :only => [:new, :destroy] before_filter :authorize, :only => [:new, :destroy]
verify :method => :post,
:only => [ :watch, :unwatch ],
:render => { :nothing => true, :status => :method_not_allowed }
def watch def watch
if @watched.respond_to?(:visible?) && !@watched.visible?(User.current) if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
render_403 render_403
@@ -27,58 +31,27 @@ class WatchersController < ApplicationController
set_watcher(User.current, true) set_watcher(User.current, true)
end end
end end
def unwatch def unwatch
set_watcher(User.current, false) set_watcher(User.current, false)
end end
def new def new
@watcher = Watcher.new(params[:watcher])
@watcher.watchable = @watched
@watcher.save if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to :back }
format.js do format.js do
render :update do |page| render :update do |page|
page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
page << "showModal('ajax-modal', '400px');"
page << "$('ajax-modal').addClassName('new-watcher');"
end
end
end
end
def create
if params[:watcher].is_a?(Hash) && request.post?
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
user_ids.each do |user_id|
Watcher.create(:watchable => @watched, :user_id => user_id)
end
end
respond_to do |format|
format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
format.js do
render :update do |page|
page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched} page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
end end
end end
end end
rescue ::ActionController::RedirectBackError
render :text => 'Watcher added.', :layout => true
end end
def append
if params[:watcher].is_a?(Hash)
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
users = User.active.find_all_by_id(user_ids)
respond_to do |format|
format.js do
render :update do |page|
users.each do |user|
page << %|$$("#issue_watcher_user_ids_#{user.id}").each(function(el){el.remove();});|
end
page.insert_html :bottom, 'watchers_inputs', :text => watchers_checkboxes(nil, users, true)
end
end
end
end
end
def destroy def destroy
@watched.set_watcher(User.find(params[:user_id]), false) if request.post? @watched.set_watcher(User.find(params[:user_id]), false) if request.post?
respond_to do |format| respond_to do |format|
@@ -90,39 +63,39 @@ class WatchersController < ApplicationController
end end
end end
end end
def autocomplete_for_user
@users = User.active.like(params[:q]).find(:all, :limit => 100)
if @watched
@users -= @watched.watcher_users
end
render :layout => false
end
private private
def find_project def find_project
if params[:object_type] && params[:object_id] klass = Object.const_get(params[:object_type].camelcase)
klass = Object.const_get(params[:object_type].camelcase) return false unless klass.respond_to?('watched_by')
return false unless klass.respond_to?('watched_by') @watched = klass.find(params[:object_id])
@watched = klass.find(params[:object_id]) @project = @watched.project
@project = @watched.project
elsif params[:project_id]
@project = Project.visible.find_by_param(params[:project_id])
end
rescue rescue
render_404 render_404
end end
def set_watcher(user, watching) def set_watcher(user, watching)
@watched.set_watcher(user, watching) @watched.set_watcher(user, watching)
if params[:replace].present?
if params[:replace].is_a? Array
replace_ids = params[:replace]
else
replace_ids = [params[:replace]]
end
else
replace_ids = ['watcher']
end
respond_to do |format| respond_to do |format|
format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}} format.html { redirect_to :back }
format.js do format.js do
render(:update) do |page| render(:update) do |page|
c = watcher_css(@watched) replace_ids.each do |replace_id|
page << %|$$(".#{c}").each(function(el){el.innerHTML="#{escape_javascript watcher_link(@watched, user)}"});| page.replace_html replace_id, watcher_link(@watched, user, :replace => replace_ids)
end
end end
end end
end end
rescue ::ActionController::RedirectBackError
render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
end end
end end
+5 -5
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -22,7 +22,7 @@ class WelcomeController < ApplicationController
@news = News.latest User.current @news = News.latest User.current
@projects = Project.latest User.current @projects = Project.latest User.current
end end
def robots def robots
@projects = Project.all_public.active @projects = Project.all_public.active
render :layout => false, :content_type => 'text/plain' render :layout => false, :content_type => 'text/plain'
+53 -94
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -34,28 +34,23 @@ require 'diff'
class WikiController < ApplicationController class WikiController < ApplicationController
default_search_scope :wiki_pages default_search_scope :wiki_pages
before_filter :find_wiki, :authorize before_filter :find_wiki, :authorize
before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy] before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
verify :method => :post, :only => [:protect], :redirect_to => { :action => :show }
helper :attachments helper :attachments
include AttachmentsHelper include AttachmentsHelper
helper :watchers helper :watchers
include Redmine::Export::PDF
# List of pages, sorted alphabetically and by parent (hierarchy) # List of pages, sorted alphabetically and by parent (hierarchy)
def index def index
load_pages_for_index load_pages_grouped_by_date_without_content
@pages_by_parent_id = @pages.group_by(&:parent_id)
end
# List of page, by last update
def date_index
load_pages_for_index
@pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
end end
# 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
page_title = params[:id]
@page = @wiki.find_or_new_page(page_title)
if @page.new_record? if @page.new_record?
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? if User.current.allowed_to?(:edit_wiki_pages, @project) && editable?
edit edit
@@ -72,10 +67,7 @@ class WikiController < ApplicationController
end end
@content = @page.content_for_version(params[:version]) @content = @page.content_for_version(params[:version])
if User.current.allowed_to?(:export_wiki_pages, @project) if User.current.allowed_to?(:export_wiki_pages, @project)
if params[:format] == 'pdf' if params[:format] == 'html'
send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
return
elsif params[:format] == 'html'
export = render_to_string :action => 'export', :layout => false export = render_to_string :action => 'export', :layout => false
send_data(export, :type => 'text/html', :filename => "#{@page.title}.html") send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
return return
@@ -85,23 +77,15 @@ class WikiController < ApplicationController
end end
end end
@editable = editable? @editable = editable?
@sections_editable = @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) &&
@content.current_version? &&
Redmine::WikiFormatting.supports_section_edit?
render :action => 'show' render :action => 'show'
end end
# edit an existing page or a new one # edit an existing page or a new one
def edit def edit
@page = @wiki.find_or_new_page(params[:id])
return render_403 unless editable? return render_403 unless editable?
if @page.new_record? @page.content = WikiContent.new(:page => @page) if @page.new_record?
@page.content = WikiContent.new(:page => @page)
if params[:parent].present?
@page.parent = @page.wiki.find_page(params[:parent].to_s)
end
end
@content = @page.content_for_version(params[:version]) @content = @page.content_for_version(params[:version])
@content.text = initial_page_content(@page) if @content.text.blank? @content.text = initial_page_content(@page) if @content.text.blank?
# don't keep previous comment # don't keep previous comment
@@ -109,21 +93,15 @@ class WikiController < ApplicationController
# 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 @content.version = @page.content.version
@text = @content.text
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@section = params[:section].to_i
@text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section)
render_404 if @text.blank?
end
end end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
# Creates a new page or updates an existing one # Creates a new page or updates an existing one
def update def update
@page = @wiki.find_or_new_page(params[:id])
return render_403 unless editable? return render_403 unless editable?
@page.content = WikiContent.new(:page => @page) if @page.new_record? @page.content = WikiContent.new(:page => @page) if @page.new_record?
@page.safe_attributes = params[:wiki_page]
@content = @page.content_for_version(params[:version]) @content = @page.content_for_version(params[:version])
@content.text = initial_page_content(@page) if @content.text.blank? @content.text = initial_page_content(@page) if @content.text.blank?
# don't keep previous comment # don't keep previous comment
@@ -132,25 +110,14 @@ class WikiController < ApplicationController
if !@page.new_record? && params[:content].present? && @content.text == params[:content][:text] if !@page.new_record? && params[:content].present? && @content.text == params[:content][:text]
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)
# don't save content if text wasn't changed # don't save if text wasn't changed
@page.save
redirect_to :action => 'show', :project_id => @project, :id => @page.title redirect_to :action => 'show', :project_id => @project, :id => @page.title
return return
end end
@content.attributes = params[:content]
@content.comments = params[:content][:comments]
@text = params[:content][:text]
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@section = params[:section].to_i
@section_hash = params[:section_hash]
@content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
else
@content.version = params[:content][:version]
@content.text = @text
end
@content.author = User.current @content.author = User.current
@page.content = @content # if page is new @page.save will also save content, but not if page isn't a new record
if @page.save if (@page.new_record? ? @page.save : @content.save)
attachments = Attachment.attach_files(@page, params[:attachments]) attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page) render_attachment_warning_if_needed(@page)
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
@@ -159,12 +126,10 @@ class WikiController < ApplicationController
render :action => 'edit' render :action => 'edit'
end end
rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError rescue ActiveRecord::StaleObjectError
# Optimistic locking exception # Optimistic locking exception
flash.now[:error] = l(:notice_locking_conflict) flash.now[:error] = l(:notice_locking_conflict)
render :action => 'edit' render :action => 'edit'
rescue ActiveRecord::RecordNotSaved
render :action => 'edit'
end end
# rename a page # rename a page
@@ -178,7 +143,7 @@ class WikiController < ApplicationController
redirect_to :action => 'show', :project_id => @project, :id => @page.title redirect_to :action => 'show', :project_id => @project, :id => @page.title
end end
end end
def protect def protect
@page.update_attribute :protected, params[:protected] @page.update_attribute :protected, params[:protected]
redirect_to :action => 'show', :project_id => @project, :id => @page.title redirect_to :action => 'show', :project_id => @project, :id => @page.title
@@ -188,8 +153,8 @@ class WikiController < ApplicationController
def history def history
@version_count = @page.content.versions.count @version_count = @page.content.versions.count
@version_pages = Paginator.new self, @version_count, per_page_option, params['p'] @version_pages = Paginator.new self, @version_count, per_page_option, params['p']
# don't load text # don't load text
@versions = @page.content.versions.find :all, @versions = @page.content.versions.find :all,
:select => "id, author_id, comments, updated_on, version", :select => "id, author_id, comments, updated_on, version",
:order => 'version DESC', :order => 'version DESC',
:limit => @version_pages.items_per_page + 1, :limit => @version_pages.items_per_page + 1,
@@ -197,22 +162,23 @@ class WikiController < ApplicationController
render :layout => false if request.xhr? render :layout => false if request.xhr?
end end
def diff def diff
@diff = @page.diff(params[:version], params[:version_from]) @diff = @page.diff(params[:version], params[:version_from])
render_404 unless @diff render_404 unless @diff
end end
def annotate def annotate
@annotate = @page.annotate(params[:version]) @annotate = @page.annotate(params[:version])
render_404 unless @annotate render_404 unless @annotate
end end
verify :method => :delete, :only => [:destroy], :redirect_to => { :action => :show }
# Removes a wiki page and its history # Removes a wiki page and its history
# Children can be either set as root pages, removed or reassigned to another parent page # Children can be either set as root pages, removed or reassigned to another parent page
def destroy def destroy
return render_403 unless editable? return render_403 unless editable?
@descendants_count = @page.descendants.size @descendants_count = @page.descendants.size
if @descendants_count > 0 if @descendants_count > 0
case params[:todo] case params[:todo]
@@ -237,20 +203,21 @@ class WikiController < ApplicationController
redirect_to :action => 'index', :project_id => @project redirect_to :action => 'index', :project_id => @project
end end
# Export wiki to a single pdf or html file # Export wiki to a single html file
def export def export
@pages = @wiki.pages.all(:order => 'title', :include => [:content, :attachments], :limit => 75) if User.current.allowed_to?(:export_wiki_pages, @project)
respond_to do |format| @pages = @wiki.pages.find :all, :order => 'title'
format.html { export = render_to_string :action => 'export_multiple', :layout => false
export = render_to_string :action => 'export_multiple', :layout => false send_data(export, :type => 'text/html', :filename => "wiki.html")
send_data(export, :type => 'text/html', :filename => "wiki.html") else
} redirect_to :action => 'show', :project_id => @project, :id => nil
format.pdf {
send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
}
end end
end end
def date_index
load_pages_grouped_by_date_without_content
end
def preview def preview
page = @wiki.find_page(params[:id]) page = @wiki.find_page(params[:id])
# page is nil when previewing a new page # page is nil when previewing a new page
@@ -271,7 +238,7 @@ class WikiController < ApplicationController
end end
private private
def find_wiki def find_wiki
@project = Project.find(params[:project_id]) @project = Project.find(params[:project_id])
@wiki = @project.wiki @wiki = @project.wiki
@@ -279,27 +246,13 @@ private
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
# Finds the requested page or a new page if it doesn't exist
def find_existing_or_new_page
@page = @wiki.find_or_new_page(params[:id])
if @wiki.page_found_with_redirect?
redirect_to params.update(:id => @page.title)
end
end
# Finds the requested page and returns a 404 error if it doesn't exist # Finds the requested page and returns a 404 error if it doesn't exist
def find_existing_page def find_existing_page
@page = @wiki.find_page(params[:id]) @page = @wiki.find_page(params[:id])
if @page.nil? render_404 if @page.nil?
render_404
return
end
if @wiki.page_found_with_redirect?
redirect_to params.update(:id => @page.title)
end
end end
# Returns true if the current user is allowed to edit the page, otherwise false # Returns true if the current user is allowed to edit the page, otherwise false
def editable?(page = @page) def editable?(page = @page)
page.editable_by?(User.current) page.editable_by?(User.current)
@@ -312,7 +265,13 @@ private
helper.instance_method(:initial_page_content).bind(self).call(page) helper.instance_method(:initial_page_content).bind(self).call(page)
end end
def load_pages_for_index # eager load information about last updates, without loading text
@pages = @wiki.pages.with_updated_on.all(:order => 'title', :include => {:wiki => :project}) def load_pages_grouped_by_date_without_content
@pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
:joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id",
:order => 'title'
@pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
@pages_by_parent_id = @pages.group_by(&:parent_id)
end end
end end
+7 -7
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -18,11 +18,11 @@
class WikisController < ApplicationController class WikisController < ApplicationController
menu_item :settings menu_item :settings
before_filter :find_project, :authorize before_filter :find_project, :authorize
# Create or update a project's wiki # Create or update a project's wiki
def edit def edit
@wiki = @project.wiki || Wiki.new(:project => @project) @wiki = @project.wiki || Wiki.new(:project => @project)
@wiki.safe_attributes = params[:wiki] @wiki.attributes = params[:wiki]
@wiki.save if request.post? @wiki.save if request.post?
render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'} render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
end end
@@ -32,6 +32,6 @@ class WikisController < ApplicationController
if request.post? && params[:confirm] && @project.wiki if request.post? && params[:confirm] && @project.wiki
@project.wiki.destroy @project.wiki.destroy
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki' redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
end end
end end
end end
+17 -28
View File
@@ -1,68 +1,57 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2008 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class WorkflowsController < ApplicationController class WorkflowsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin
before_filter :find_roles before_filter :find_roles
before_filter :find_trackers before_filter :find_trackers
def index def index
@workflow_counts = Workflow.count_by_tracker_and_role @workflow_counts = Workflow.count_by_tracker_and_role
end end
def edit def edit
@role = Role.find_by_id(params[:role_id]) @role = Role.find_by_id(params[:role_id])
@tracker = Tracker.find_by_id(params[:tracker_id]) @tracker = Tracker.find_by_id(params[:tracker_id])
if request.post? if request.post?
Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
(params[:issue_status] || []).each { |status_id, transitions| (params[:issue_status] || []).each { |old, news|
transitions.each { |new_status_id, options| news.each { |new|
author = options.is_a?(Array) && options.include?('author') && !options.include?('always') @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new)
assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always')
@role.workflows.build(:tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee)
} }
} }
if @role.save if @role.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker
return
end end
end end
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
if @tracker && @used_statuses_only && @tracker.issue_statuses.any? if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
@statuses = @tracker.issue_statuses @statuses = @tracker.issue_statuses
end end
@statuses ||= IssueStatus.find(:all, :order => 'position') @statuses ||= IssueStatus.find(:all, :order => 'position')
if @tracker && @role && @statuses.any?
workflows = Workflow.all(:conditions => {:role_id => @role.id, :tracker_id => @tracker.id})
@workflows = {}
@workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
@workflows['author'] = workflows.select {|w| w.author}
@workflows['assignee'] = workflows.select {|w| w.assignee}
end
end end
def copy def copy
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any' if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
@source_tracker = nil @source_tracker = nil
else else
@@ -73,10 +62,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.find_all_by_id(params[:target_tracker_ids]) @target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids])
@target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids]) @target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids])
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)
@@ -95,7 +84,7 @@ class WorkflowsController < ApplicationController
def find_roles def find_roles
@roles = Role.find(:all, :order => 'builtin, position') @roles = Role.find(:all, :order => 'builtin, position')
end end
def find_trackers def find_trackers
@trackers = Tracker.find(:all, :order => 'position') @trackers = Tracker.find(:all, :order => 'position')
end end
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+6 -8
View File
@@ -1,25 +1,23 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
module AdminHelper module AdminHelper
def project_status_options_for_select(selected) def project_status_options_for_select(selected)
options_for_select([[l(:label_all), ''], options_for_select([[l(:label_all), ''],
[l(:status_active), '1']], selected.to_s) [l(:status_active), 1]], selected)
end end
end end
File diff suppressed because it is too large Load Diff
+8 -19
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -23,23 +21,14 @@ module AttachmentsHelper
# :author -- author names are not displayed if set to false # :author -- author names are not displayed if set to false
def link_to_attachments(container, options = {}) def link_to_attachments(container, options = {})
options.assert_valid_keys(:author) options.assert_valid_keys(:author)
if container.attachments.any? if container.attachments.any?
options = {:deletable => container.attachments_deletable?, :author => true}.merge(options) options = {:deletable => container.attachments_deletable?, :author => true}.merge(options)
render :partial => 'attachments/links', :locals => {:attachments => container.attachments, :options => options} render :partial => 'attachments/links', :locals => {:attachments => container.attachments, :options => options}
end end
end end
def render_api_attachment(attachment, api) def to_utf8(str)
api.attachment do str
api.id attachment.id
api.filename attachment.filename
api.filesize attachment.filesize
api.content_type attachment.content_type
api.description attachment.description
api.content_url url_for(:controller => 'attachments', :action => 'download', :id => attachment, :filename => attachment.filename, :only_path => false)
api.author(:id => attachment.author.id, :name => attachment.author.name) if attachment.author
api.created_on attachment.created_on
end
end end
end end
+4 -9
View File
@@ -1,24 +1,19 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
module AuthSourcesHelper module AuthSourcesHelper
def auth_source_partial_name(auth_source)
"form_#{auth_source.class.name.underscore}"
end
end end
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+12 -25
View File
@@ -1,22 +1,3 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module CalendarsHelper module CalendarsHelper
def link_to_previous_month(year, month, options={}) def link_to_previous_month(year, month, options={})
target_year, target_month = if month == 1 target_year, target_month = if month == 1
@@ -24,15 +5,14 @@ module CalendarsHelper
else else
[year, month - 1] [year, month - 1]
end end
name = if target_month == 12 name = if target_month == 12
"#{month_name(target_month)} #{target_year}" "#{month_name(target_month)} #{target_year}"
else else
"#{month_name(target_month)}" "#{month_name(target_month)}"
end end
# \xc2\xab(utf-8) = &#171; link_to_month(('&#171; ' + name), target_year, target_month, options)
link_to_month(("\xc2\xab " + name), target_year, target_month, options)
end end
def link_to_next_month(year, month, options={}) def link_to_next_month(year, month, options={})
@@ -48,11 +28,18 @@ module CalendarsHelper
"#{month_name(target_month)}" "#{month_name(target_month)}"
end end
# \xc2\xbb(utf-8) = &#187; link_to_month((name + ' &#187;'), target_year, target_month, options)
link_to_month((name + " \xc2\xbb"), target_year, target_month, options)
end end
def link_to_month(link_name, year, month, options={}) def link_to_month(link_name, year, month, options={})
link_to_content_update(h(link_name), params.merge(:year => year, :month => month)) project_id = options[:project].present? ? options[:project].to_param : nil
link_target = calendar_path(:year => year, :month => month, :project_id => project_id)
link_to_remote(link_name,
{:update => "content", :url => link_target, :method => :put},
{:href => link_target})
end end
end end
-43
View File
@@ -1,43 +0,0 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ContextMenusHelper
def context_menu_link(name, url, options={})
options[:class] ||= ''
if options.delete(:selected)
options[:class] << ' icon-checked disabled'
options[:disabled] = true
end
if options.delete(:disabled)
options.delete(:method)
options.delete(:confirm)
options.delete(:onclick)
options[:class] << ' disabled'
url = '#'
end
link_to h(name), url, options
end
def bulk_update_custom_field_context_menu_link(field, text, value)
context_menu_link h(text),
{:controller => 'issues', :action => 'bulk_update', :ids => @issues.collect(&:id), :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back},
:method => :post,
:selected => (@issue && @issue.custom_field_value(field) == value)
end
end
+32 -70
View File
@@ -1,18 +1,16 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -31,85 +29,63 @@ module CustomFieldsHelper
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName} {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName}
] ]
end end
# Return custom field html tag corresponding to its format # Return custom field html tag corresponding to its format
def custom_field_tag(name, custom_value) def custom_field_tag(name, custom_value)
custom_field = custom_value.custom_field custom_field = custom_value.custom_field
field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
field_id = "#{name}_custom_field_values_#{custom_field.id}" field_id = "#{name}_custom_field_values_#{custom_field.id}"
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.try(:edit_as)
when "date" when "date"
text_field_tag(field_name, custom_value.value, tag_options.merge(:size => 10)) + text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
calendar_for(field_id) calendar_for(field_id)
when "text" when "text"
text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3)) text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
when "bool" when "bool"
hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options) hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, :id => field_id)
when "list" when "list"
blank_option = ''.html_safe blank_option = custom_field.is_required? ?
unless custom_field.multiple? (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
if custom_field.is_required? '<option></option>'
unless custom_field.default_value.present? select_tag(field_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id)
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 else
text_field_tag(field_name, custom_value.value, tag_options) text_field_tag(field_name, custom_value.value, :id => field_id)
end end
end end
# Return custom field label tag # Return custom field label tag
def custom_field_label_tag(name, custom_value) def custom_field_label_tag(name, custom_value)
content_tag "label", h(custom_value.custom_field.name) + content_tag "label", custom_value.custom_field.name +
(custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>".html_safe : ""), (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : ""),
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}" :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
:class => (custom_value.errors.empty? ? nil : "error" )
end end
# Return custom field tag with its label tag # Return custom field tag with its label tag
def custom_field_tag_with_label(name, custom_value) def custom_field_tag_with_label(name, custom_value)
custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value) custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
end end
def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil) def custom_field_tag_for_bulk_edit(name, custom_field)
field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
field_id = "#{name}_custom_field_values_#{custom_field.id}" field_id = "#{name}_custom_field_values_#{custom_field.id}"
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.try(:edit_as)
when "date" when "date"
text_field_tag(field_name, '', tag_options.merge(:size => 10)) + text_field_tag(field_name, '', :id => field_id, :size => 10) +
calendar_for(field_id) calendar_for(field_id)
when "text" when "text"
text_area_tag(field_name, '', tag_options.merge(:rows => 3)) text_area_tag(field_name, '', :id => field_id, :rows => 3, :style => 'width:90%')
when "bool" when "bool"
select_tag(field_name, options_for_select([[l(:label_no_change_option), ''], select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
[l(:general_text_yes), '1'], [l(:general_text_yes), '1'],
[l(:general_text_no), '0']]), tag_options) [l(:general_text_no), '0']]), :id => field_id)
when "list" when "list"
options = [] select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values), :id => field_id)
options << [l(:label_no_change_option), ''] unless custom_field.multiple?
options << [l(:label_none), '__none__'] unless custom_field.is_required?
options += custom_field.possible_values_options(projects)
select_tag(field_name, options_for_select(options), tag_options.merge(:multiple => custom_field.multiple?))
else else
text_field_tag(field_name, '', tag_options) text_field_tag(field_name, '', :id => field_id)
end end
end end
@@ -118,37 +94,23 @@ module CustomFieldsHelper
return "" unless custom_value return "" unless custom_value
format_value(custom_value.value, custom_value.custom_field.field_format) 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, field_format) def format_value(value, field_format)
if value.is_a?(Array) Redmine::CustomFieldFormat.format_value(value, field_format) # Proxy
value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ')
else
Redmine::CustomFieldFormat.format_value(value, field_format)
end
end end
# Return an array of custom field formats which can be used in select_tag # Return an array of custom field formats which can be used in select_tag
def custom_field_formats_for_select(custom_field) def custom_field_formats_for_select
Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name) Redmine::CustomFieldFormat.as_select
end end
# Renders the custom_values in api views # Renders the custom_values in api views
def render_api_custom_values(custom_values, api) def render_api_custom_values(custom_values, api)
api.array :custom_fields do api.array :custom_fields do
custom_values.each do |custom_value| custom_values.each do |custom_value|
attrs = {:id => custom_value.custom_field_id, :name => custom_value.custom_field.name} api.custom_field :id => custom_value.custom_field_id, :name => custom_value.custom_field.name do
attrs.merge!(:multiple => true) if custom_value.custom_field.multiple? api.value custom_value.value
api.custom_field attrs do
if custom_value.value.is_a?(Array)
api.array :value do
custom_value.value.each do |value|
api.value value unless value.blank?
end
end
else
api.value custom_value.value
end
end end
end end
end unless custom_values.empty? end unless custom_values.empty?
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+21 -15
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -23,21 +21,29 @@ module GanttHelper
case in_or_out case in_or_out
when :in when :in
if gantt.zoom < 4 if gantt.zoom < 4
link_to_content_update l(:text_zoom_in), link_to_remote(l(:text_zoom_in),
params.merge(gantt.params.merge(:zoom => (gantt.zoom+1))), {:url => gantt.params.merge(:zoom => (gantt.zoom+1)), :method => :get, :update => 'content'},
:class => 'icon icon-zoom-in' {:href => url_for(gantt.params.merge(:zoom => (gantt.zoom+1))),
:class => 'icon icon-zoom-in'})
else else
content_tag('span', l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe content_tag('span', l(:text_zoom_in), :class => 'icon icon-zoom-in')
end end
when :out when :out
if gantt.zoom > 1 if gantt.zoom > 1
link_to_content_update l(:text_zoom_out), link_to_remote(l(:text_zoom_out),
params.merge(gantt.params.merge(:zoom => (gantt.zoom-1))), {:url => gantt.params.merge(:zoom => (gantt.zoom-1)), :method => :get, :update => 'content'},
:class => 'icon icon-zoom-out' {:href => url_for(gantt.params.merge(:zoom => (gantt.zoom-1))),
:class => 'icon icon-zoom-out'})
else else
content_tag('span', l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe content_tag('span', l(:text_zoom_out), :class => 'icon icon-zoom-out')
end end
end end
end end
def number_of_issues_on_versions(gantt)
versions = gantt.events.collect {|event| (event.is_a? Version) ? event : nil}.compact
versions.sum {|v| v.fixed_issues.for_gantt.with_query(@query).count}
end
end end
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2009 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -26,7 +24,7 @@ module GroupsHelper
end end
options options
end end
def group_settings_tabs def group_settings_tabs
tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general}, tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general},
{:name => 'users', :partial => 'groups/users', :label => :label_user_plural}, {:name => 'users', :partial => 'groups/users', :label => :label_user_plural},
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+2
View File
@@ -0,0 +1,2 @@
module IssueMovesHelper
end
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+99 -190
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -48,38 +46,28 @@ module IssuesHelper
@cached_label_priority ||= l(:field_priority) @cached_label_priority ||= l(:field_priority)
@cached_label_project ||= l(:field_project) @cached_label_project ||= l(:field_project)
link_to_issue(issue) + "<br /><br />".html_safe + link_to_issue(issue) + "<br /><br />" +
"<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe + "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />" +
"<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe + "<strong>#{@cached_label_status}</strong>: #{issue.status.name}<br />" +
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe + "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe + "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
"<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe + "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
"<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
end end
def issue_heading(issue)
h("#{issue.tracker} ##{issue.id}")
end
def render_issue_subject_with_tree(issue) def render_issue_subject_with_tree(issue)
s = '' s = ''
ancestors = issue.root? ? [] : issue.ancestors.visible.all issue.ancestors.each do |ancestor|
ancestors.each do |ancestor|
s << '<div>' + content_tag('p', link_to_issue(ancestor)) s << '<div>' + content_tag('p', link_to_issue(ancestor))
end end
s << '<div>' s << '<div>' + content_tag('h3', h(issue.subject))
subject = h(issue.subject) s << '</div>' * (issue.ancestors.size + 1)
if issue.is_private? s
subject = content_tag('span', l(:field_is_private), :class => 'private') + ' ' + subject
end
s << content_tag('h3', subject)
s << '</div>' * (ancestors.size + 1)
s.html_safe
end end
def render_descendants_tree(issue) def render_descendants_tree(issue)
s = '<form><table class="list issues">' s = '<form><table class="list issues">'
issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
s << content_tag('tr', s << content_tag('tr',
content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') + content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') + content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') +
@@ -88,10 +76,10 @@ module IssuesHelper
content_tag('td', progress_bar(child.done_ratio, :width => '80px')), content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
:class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}") :class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
end end
s << '</table></form>' s << '</form></table>'
s.html_safe s
end end
def render_custom_fields_rows(issue) def render_custom_fields_rows(issue)
return if issue.custom_field_values.empty? return if issue.custom_field_values.empty?
ordered_values = [] ordered_values = []
@@ -108,129 +96,49 @@ module IssuesHelper
n += 1 n += 1
end end
s << "</tr>\n" s << "</tr>\n"
s.html_safe s
end end
def issues_destroy_confirmation_message(issues)
issues = [issues] unless issues.is_a?(Array)
message = l(:text_issues_destroy_confirmation)
descendant_count = issues.inject(0) {|memo, i| memo += (i.right - i.left - 1)/2}
if descendant_count > 0
issues.each do |issue|
next if issue.root?
issues.each do |other_issue|
descendant_count -= 1 if issue.is_descendant_of?(other_issue)
end
end
if descendant_count > 0
message << "\n" + l(:text_issues_destroy_descendants_confirmation, :count => descendant_count)
end
end
message
end
def sidebar_queries def sidebar_queries
unless @sidebar_queries unless @sidebar_queries
@sidebar_queries = Query.visible.all( # User can see public queries and his own queries
:order => "#{Query.table_name}.name ASC", visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
# Project specific queries and global queries # Project specific queries and global queries
:conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]) visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
) @sidebar_queries = Query.find(:all,
:select => 'id, name',
:order => "name ASC",
:conditions => visible.conditions)
end end
@sidebar_queries @sidebar_queries
end end
def query_links(title, queries) def show_detail(detail, no_html=false)
# links to #index on issues/show
url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
content_tag('h3', h(title)) +
queries.collect {|query|
css = 'query'
css << ' selected' if query == @query
link_to(h(query.name), url_params.merge(:query_id => query), :class => css)
}.join('<br />').html_safe
end
def render_sidebar_queries
out = ''.html_safe
queries = sidebar_queries.select {|q| !q.is_public?}
out << query_links(l(:label_my_queries), queries) if queries.any?
queries = sidebar_queries.select {|q| q.is_public?}
out << query_links(l(:label_query_plural), queries) if queries.any?
out
end
# Returns the textual representation of a journal details
# as an array of strings
def details_to_strings(details, no_html=false, options={})
options[:only_path] = (options[:only_path] == false ? false : true)
strings = []
values_by_field = {}
details.each do |detail|
if detail.property == 'cf'
field_id = detail.prop_key
field = CustomField.find_by_id(field_id)
if field && field.multiple?
values_by_field[field_id] ||= {:added => [], :deleted => []}
if detail.old_value
values_by_field[field_id][:deleted] << detail.old_value
end
if detail.value
values_by_field[field_id][:added] << detail.value
end
next
end
end
strings << show_detail(detail, no_html, options)
end
values_by_field.each do |field_id, changes|
detail = JournalDetail.new(:property => 'cf', :prop_key => field_id)
if changes[:added].any?
detail.value = changes[:added]
strings << show_detail(detail, no_html, options)
elsif changes[:deleted].any?
detail.old_value = changes[:deleted]
strings << show_detail(detail, no_html, options)
end
end
strings
end
# Returns the textual representation of a single journal detail
def show_detail(detail, no_html=false, options={})
multiple = false
case detail.property case detail.property
when 'attr' when 'attr'
field = detail.prop_key.to_s.gsub(/\_id$/, "") field = detail.prop_key.to_s.gsub(/\_id$/, "")
label = l(("field_" + field).to_sym) label = l(("field_" + field).to_sym)
case detail.prop_key case
when 'due_date', 'start_date' when ['due_date', 'start_date'].include?(detail.prop_key)
value = format_date(detail.value.to_date) if detail.value value = format_date(detail.value.to_date) if detail.value
old_value = format_date(detail.old_value.to_date) if detail.old_value old_value = format_date(detail.old_value.to_date) if detail.old_value
when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id', when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
'priority_id', 'category_id', 'fixed_version_id'
value = find_name_by_reflection(field, detail.value) value = find_name_by_reflection(field, detail.value)
old_value = find_name_by_reflection(field, detail.old_value) old_value = find_name_by_reflection(field, detail.old_value)
when 'estimated_hours' when detail.prop_key == 'estimated_hours'
value = "%0.02f" % detail.value.to_f unless detail.value.blank? value = "%0.02f" % detail.value.to_f unless detail.value.blank?
old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank? old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
when 'parent_id' when detail.prop_key == 'parent_id'
label = l(:field_parent_issue) label = l(:field_parent_issue)
value = "##{detail.value}" unless detail.value.blank? value = "##{detail.value}" unless detail.value.blank?
old_value = "##{detail.old_value}" unless detail.old_value.blank? old_value = "##{detail.old_value}" unless detail.old_value.blank?
when 'is_private'
value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
end end
when 'cf' when 'cf'
custom_field = CustomField.find_by_id(detail.prop_key) custom_field = CustomField.find_by_id(detail.prop_key)
if custom_field if custom_field
multiple = custom_field.multiple?
label = custom_field.name label = custom_field.name
value = format_value(detail.value, custom_field.field_format) if detail.value value = format_value(detail.value, custom_field.field_format) if detail.value
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
@@ -238,57 +146,37 @@ module IssuesHelper
when 'attachment' when 'attachment'
label = l(:label_attachment) label = l(:label_attachment)
end end
call_hook(:helper_issues_show_detail_after_setting, call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
{:detail => detail, :label => label, :value => value, :old_value => old_value })
label ||= detail.prop_key label ||= detail.prop_key
value ||= detail.value value ||= detail.value
old_value ||= detail.old_value old_value ||= detail.old_value
unless no_html unless no_html
label = content_tag('strong', label) label = content_tag('strong', label)
old_value = content_tag("i", h(old_value)) if detail.old_value old_value = content_tag("i", h(old_value)) if detail.old_value
old_value = content_tag("strike", old_value) if detail.old_value and detail.value.blank? old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key) if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
# Link to the attachment if it has not been removed # Link to the attachment if it has not been removed
value = link_to_attachment(atta, :download => true, :only_path => options[:only_path]) value = link_to_attachment(a)
if options[:only_path] != false && atta.is_text?
value += link_to(
image_tag('magnifier.png'),
:controller => 'attachments', :action => 'show',
:id => atta, :filename => atta.filename
)
end
else else
value = content_tag("i", h(value)) if value value = content_tag("i", h(value)) if value
end end
end end
if detail.property == 'attr' && detail.prop_key == 'description' if !detail.value.blank?
s = l(:text_journal_changed_no_detail, :label => label)
unless no_html
diff_link = link_to 'diff',
{:controller => 'journals', :action => 'diff', :id => detail.journal_id,
:detail_id => detail.id, :only_path => options[:only_path]},
:title => l(:label_view_diff)
s << " (#{ diff_link })"
end
s.html_safe
elsif detail.value.present?
case detail.property case detail.property
when 'attr', 'cf' when 'attr', 'cf'
if detail.old_value.present? if !detail.old_value.blank?
l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe l(:text_journal_changed, :label => label, :old => old_value, :new => value)
elsif multiple
l(:text_journal_added, :label => label, :value => value).html_safe
else else
l(:text_journal_set_to, :label => label, :value => value).html_safe l(:text_journal_set_to, :label => label, :value => value)
end end
when 'attachment' when 'attachment'
l(:text_journal_added, :label => label, :value => value).html_safe l(:text_journal_added, :label => label, :value => value)
end end
else else
l(:text_journal_deleted, :label => label, :old => old_value).html_safe l(:text_journal_deleted, :label => label, :old => old_value)
end end
end end
@@ -300,7 +188,7 @@ module IssuesHelper
return record.name if record return record.name if record
end end
end end
# Renders issue children recursively # Renders issue children recursively
def render_api_issue_children(issue, api) def render_api_issue_children(issue, api)
return if issue.leaf? return if issue.leaf?
@@ -314,39 +202,60 @@ module IssuesHelper
end end
end end
end end
def issues_to_csv(issues, project, query, options={}) def issues_to_csv(issues, project = nil)
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
decimal_separator = l(:general_csv_decimal_separator) decimal_separator = l(:general_csv_decimal_separator)
encoding = l(:general_csv_encoding)
columns = (options[:columns] == 'all' ? query.available_columns : query.columns)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields # csv header fields
csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } + headers = [ "#",
(options[:description] ? [Redmine::CodesetUtil.from_utf8(l(:field_description), encoding)] : []) l(:field_status),
l(:field_project),
l(:field_tracker),
l(:field_priority),
l(:field_subject),
l(:field_assigned_to),
l(:field_category),
l(:field_fixed_version),
l(:field_author),
l(:field_start_date),
l(:field_due_date),
l(:field_done_ratio),
l(:field_estimated_hours),
l(:field_parent_issue),
l(:field_created_on),
l(:field_updated_on)
]
# Export project custom fields if project is given
# otherwise export custom fields marked as "For all projects"
custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields
custom_fields.each {|f| headers << f.name}
# Description in the last column
headers << l(:field_description)
csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
# csv lines # csv lines
issues.each do |issue| issues.each do |issue|
col_values = columns.collect do |column| fields = [issue.id,
s = if column.is_a?(QueryCustomFieldColumn) issue.status.name,
cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} issue.project.name,
show_value(cv) issue.tracker.name,
else issue.priority.name,
value = column.value(issue) issue.subject,
if value.is_a?(Date) issue.assigned_to,
format_date(value) issue.category,
elsif value.is_a?(Time) issue.fixed_version,
format_time(value) issue.author.name,
elsif value.is_a?(Float) format_date(issue.start_date),
("%.2f" % value).gsub('.', decimal_separator) format_date(issue.due_date),
else issue.done_ratio,
value issue.estimated_hours.to_s.gsub('.', decimal_separator),
end issue.parent_id,
end format_time(issue.created_on),
s.to_s format_time(issue.updated_on)
end ]
csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) } + custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) }
(options[:description] ? [Redmine::CodesetUtil.from_utf8(issue.description, encoding)] : []) fields << issue.description
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
end end
end end
export export
+8 -10
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2008 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -26,17 +24,17 @@ module JournalsHelper
links << link_to_remote(image_tag('comment.png'), links << link_to_remote(image_tag('comment.png'),
{ :url => {:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal} }, { :url => {:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal} },
:title => l(:button_quote)) if options[:reply_links] :title => l(:button_quote)) if options[:reply_links]
links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes", links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes",
{ :controller => 'journals', :action => 'edit', :id => journal }, { :controller => 'journals', :action => 'edit', :id => journal },
:title => l(:button_edit)) if editable :title => l(:button_edit)) if editable
end end
content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty? content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty?
content << textilizable(journal, :notes) content << textilizable(journal, :notes)
css_classes = "wiki" css_classes = "wiki"
css_classes << " editable" if editable css_classes << " editable" if editable
content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes) content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => css_classes)
end end
def link_to_in_place_notes_editor(text, field_id, url, options={}) def link_to_in_place_notes_editor(text, field_id, url, options={})
onclick = "new Ajax.Request('#{url_for(url)}', {asynchronous:true, evalScripts:true, method:'get'}); return false;" onclick = "new Ajax.Request('#{url_for(url)}', {asynchronous:true, evalScripts:true, method:'get'}); return false;"
link_to text, '#', options.merge(:onclick => onclick) link_to text, '#', options.merge(:onclick => onclick)
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2008 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+14 -6
View File
@@ -1,21 +1,29 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
module MessagesHelper module MessagesHelper
def link_to_message(message)
return '' unless message
link_to h(truncate(message.subject, :length => 60)), :controller => 'messages',
:action => 'show',
:board_id => message.board_id,
:id => message.root,
:r => (message.parent_id && message.id),
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
end
end end
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+14 -16
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -22,7 +20,7 @@ module ProjectsHelper
return '' unless version && version.is_a?(Version) return '' unless version && version.is_a?(Version)
link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => 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
tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural}, tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
{:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural}, {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
@@ -30,13 +28,13 @@ module ProjectsHelper
{:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural}, {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
{:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural}, {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
{:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki}, {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
{:name => 'repositories', :action => :manage_repository, :partial => 'projects/settings/repositories', :label => :label_repository_plural}, {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
{:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}, {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural},
{:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities} {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
] ]
tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)} tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
end end
def parent_project_select_tag(project) def parent_project_select_tag(project)
selected = project.parent selected = project.parent
# retrieve the requested parent project # retrieve the requested parent project
@@ -44,13 +42,13 @@ module ProjectsHelper
if parent_id if parent_id
selected = (parent_id.blank? ? nil : Project.find(parent_id)) selected = (parent_id.blank? ? nil : Project.find(parent_id))
end end
options = '' options = ''
options << "<option value=''></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, :name => 'project[parent_id]', :id => 'project_parent_id')
end end
# Renders a tree of projects as a nested set of unordered lists # Renders a tree of projects as a nested set of unordered lists
# The given collection may be a subset of the whole project tree # The given collection may be a subset of the whole project tree
# (eg. some intermediate nodes are private and can not be seen) # (eg. some intermediate nodes are private and can not be seen)
@@ -67,7 +65,7 @@ module ProjectsHelper
else else
ancestors.pop ancestors.pop
s << "</li>" s << "</li>"
while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
ancestors.pop ancestors.pop
s << "</ul></li>\n" s << "</ul></li>\n"
end end
@@ -82,7 +80,7 @@ module ProjectsHelper
s << ("</li></ul>\n" * ancestors.size) s << ("</li></ul>\n" * ancestors.size)
@project = original_project @project = original_project
end end
s.html_safe s
end end
# Returns a set of options for a select field, grouped by project. # Returns a set of options for a select field, grouped by project.
@@ -95,7 +93,7 @@ module ProjectsHelper
if selected && !versions.include?(selected) if selected && !versions.include?(selected)
grouped[selected.project.name] << [selected.name, selected.id] grouped[selected.project.name] << [selected.name, selected.id]
end end
if grouped.keys.size > 1 if grouped.keys.size > 1
grouped_options_for_select(grouped, selected && selected.id) grouped_options_for_select(grouped, selected && selected.id)
else else
+29 -61
View File
@@ -1,44 +1,35 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
module QueriesHelper module QueriesHelper
def operators_for_select(filter_type) def operators_for_select(filter_type)
Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]} Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
end end
def column_header(column) def column_header(column)
column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
:default_order => column.default_order) : :default_order => column.default_order) :
content_tag('th', h(column.caption)) content_tag('th', column.caption)
end
def column_content(column, issue)
value = column.value(issue)
if value.is_a?(Array)
value.collect {|v| column_value(column, issue, v)}.compact.sort.join(', ').html_safe
else
column_value(column, issue, value)
end
end end
def column_value(column, issue, value) def column_content(column, issue)
value = column.value(issue)
case value.class.name case value.class.name
when 'String' when 'String'
if column.name == :subject if column.name == :subject
@@ -53,10 +44,8 @@ module QueriesHelper
when 'Fixnum', 'Float' when 'Fixnum', 'Float'
if column.name == :done_ratio if column.name == :done_ratio
progress_bar(value, :width => '80px') progress_bar(value, :width => '80px')
elsif column.name == :spent_hours
sprintf "%.2f", value
else else
h(value.to_s) value.to_s
end end
when 'User' when 'User'
link_to_user value link_to_user value
@@ -81,51 +70,30 @@ module QueriesHelper
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 = Query.find(params[:query_id], :conditions => cond) @query = Query.find(params[:query_id], :conditions => cond)
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}
sort_clear sort_clear
elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
# Give it a name, required to be valid
@query = Query.new(:name => "_")
@query.project = @project
build_query_from_params
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
else else
# retrieve from session if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id] # Give it a name, required to be valid
@query ||= Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) @query = Query.new(:name => "_")
@query.project = @project @query.project = @project
end if params[:fields]
end @query.filters = {}
@query.add_filters(params[:fields], params[:operators], params[:values])
def retrieve_query_from_session else
if session[:query] @query.available_filters.keys.each do |field|
if session[:query][:id] @query.add_short_filter(field, params[field]) if params[field]
@query = Query.find_by_id(session[:query][:id]) end
return unless @query end
else @query.group_by = params[:group_by]
@query = Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) @query.column_names = params[:query] && params[:query][:column_names]
end session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
if session[:query].has_key?(:project_id)
@query.project_id = session[:query][:project_id]
else else
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
@query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
@query.project = @project @query.project = @project
end end
@query
end end
end end
def build_query_from_params
if params[:fields] || params[:f]
@query.filters = {}
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
else
@query.available_filters.keys.each do |field|
@query.add_short_filter(field, params[field]) if params[field]
end
end
@query.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
@query.column_names = params[:c] || (params[:query] && params[:query][:column_names])
end
end end
+8 -10
View File
@@ -1,24 +1,22 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
module ReportsHelper module ReportsHelper
def aggregate(data, criteria) def aggregate(data, criteria)
a = 0 a = 0
data.each { |row| data.each { |row|
@@ -30,9 +28,9 @@ module ReportsHelper
} unless data.nil? } unless data.nil?
a a
end end
def aggregate_link(data, criteria, *args) def aggregate_link(data, criteria, *args)
a = aggregate data, criteria a = aggregate data, criteria
a > 0 ? link_to(h(a), *args) : '-' a > 0 ? link_to(a, *args) : '-'
end end
end end
+64 -190
View File
@@ -1,24 +1,21 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv' require 'iconv'
require 'redmine/codeset_util'
module RepositoriesHelper module RepositoriesHelper
def format_revision(revision) def format_revision(revision)
@@ -28,40 +25,39 @@ module RepositoriesHelper
revision.to_s revision.to_s
end end
end end
def truncate_at_line_break(text, length = 255) def truncate_at_line_break(text, length = 255)
if text if text
text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...') text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')
end end
end end
def render_properties(properties) def render_properties(properties)
unless properties.nil? || properties.empty? unless properties.nil? || properties.empty?
content = '' content = ''
properties.keys.sort.each do |property| properties.keys.sort.each do |property|
content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>".html_safe) content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>")
end end
content_tag('ul', content.html_safe, :class => 'properties') content_tag('ul', content, :class => 'properties')
end end
end end
def render_changeset_changes def render_changeset_changes
changes = @changeset.filechanges.find(:all, :limit => 1000, :order => 'path').collect do |change| changes = @changeset.changes.find(:all, :limit => 1000, :order => 'path').collect do |change|
case change.action case change.action
when 'A' when 'A'
# Detects moved/copied files # Detects moved/copied files
if !change.from_path.blank? if !change.from_path.blank?
change.action = change.action = @changeset.changes.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
@changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
end end
change change
when 'D' when 'D'
@changeset.filechanges.detect {|c| c.from_path == change.path} ? nil : change @changeset.changes.detect {|c| c.from_path == change.path} ? nil : change
else else
change change
end end
end.compact end.compact
tree = { } tree = { }
changes.each do |change| changes.each do |change|
p = tree p = tree
@@ -76,11 +72,13 @@ module RepositoriesHelper
end end
p[:c] = change p[:c] = change
end end
render_changes_tree(tree[:s]) render_changes_tree(tree[:s])
end end
def render_changes_tree(tree) def render_changes_tree(tree)
return '' if tree.nil? return '' if tree.nil?
output = '' output = ''
output << '<ul>' output << '<ul>'
tree.keys.sort.each do |file| tree.keys.sort.each do |file|
@@ -89,230 +87,106 @@ module RepositoriesHelper
if s = tree[file][:s] if s = tree[file][:s]
style << ' folder' style << ' folder'
path_param = to_path_param(@repository.relative_path(file)) path_param = to_path_param(@repository.relative_path(file))
text = link_to(h(text), :controller => 'repositories', text = link_to(text, :controller => 'repositories',
:action => 'show', :action => 'show',
:id => @project, :id => @project,
:repository_id => @repository.identifier_param,
:path => path_param, :path => path_param,
:rev => @changeset.identifier) :rev => @changeset.identifier)
output << "<li class='#{style}'>#{text}" output << "<li class='#{style}'>#{text}</li>"
output << render_changes_tree(s) output << render_changes_tree(s)
output << "</li>"
elsif c = tree[file][:c] elsif c = tree[file][:c]
style << " change-#{c.action}" style << " change-#{c.action}"
path_param = to_path_param(@repository.relative_path(c.path)) path_param = to_path_param(@repository.relative_path(c.path))
text = link_to(h(text), :controller => 'repositories', text = link_to(text, :controller => 'repositories',
:action => 'entry', :action => 'entry',
:id => @project, :id => @project,
:repository_id => @repository.identifier_param,
:path => path_param, :path => path_param,
:rev => @changeset.identifier) unless c.action == 'D' :rev => @changeset.identifier) unless c.action == 'D'
text << " - #{h(c.revision)}" unless c.revision.blank? text << " - #{c.revision}" unless c.revision.blank?
text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories', text << ' (' + link_to('diff', :controller => 'repositories',
:action => 'diff', :action => 'diff',
:id => @project, :id => @project,
:repository_id => @repository.identifier_param,
:path => path_param, :path => path_param,
:rev => @changeset.identifier) + ') '.html_safe if c.action == 'M' :rev => @changeset.identifier) + ') ' if c.action == 'M'
text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank? text << ' ' + content_tag('span', c.from_path, :class => 'copied-from') unless c.from_path.blank?
output << "<li class='#{style}'>#{text}</li>" output << "<li class='#{style}'>#{text}</li>"
end end
end end
output << '</ul>' output << '</ul>'
output.html_safe output
end end
def repository_field_tags(form, repository) def to_utf8(str)
method = repository.class.name.demodulize.underscore + "_field_tags" return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
if repository.is_a?(Repository) && @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
respond_to?(method) && method != 'repository_field_tags' @encodings.each do |encoding|
send(method, form, repository) begin
return Iconv.conv('UTF-8', encoding, str)
rescue Iconv::Failure
# do nothing here and try the next encoding
end
end end
str
end end
def repository_field_tags(form, repository)
method = repository.class.name.demodulize.underscore + "_field_tags"
send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method) && method != 'repository_field_tags'
end
def scm_select_tag(repository) def scm_select_tag(repository)
scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']] scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
Redmine::Scm::Base.all.each do |scm| Redmine::Scm::Base.all.each do |scm|
if Setting.enabled_scm.include?(scm) || scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
(repository && repository.class.name.demodulize == scm)
scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
end
end end
select_tag('repository_scm',
select_tag('repository_scm',
options_for_select(scm_options, repository.class.name.demodulize), options_for_select(scm_options, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?), :disabled => (repository && !repository.new_record?),
:onchange => remote_function( :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
:url => new_project_repository_path(@project), )
:method => :get,
:update => 'content',
:with => "Form.serialize(this.form)")
)
end end
def with_leading_slash(path) def with_leading_slash(path)
path.to_s.starts_with?('/') ? path : "/#{path}" path.to_s.starts_with?('/') ? path : "/#{path}"
end end
def without_leading_slash(path) def without_leading_slash(path)
path.gsub(%r{^/+}, '') path.gsub(%r{^/+}, '')
end end
def subversion_field_tags(form, repository) def subversion_field_tags(form, repository)
content_tag('p', form.text_field(:url, :size => 60, :required => true, content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
:disabled => (repository && !repository.root_url.blank?)) + '<br />(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
'<br />'.html_safe +
'(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
content_tag('p', form.text_field(:login, :size => 30)) + content_tag('p', form.text_field(:login, :size => 30)) +
content_tag('p', form.password_field( content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
:password, :size => 30, :name => 'ignore', :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
:value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)), :onfocus => "this.value=''; this.name='repository[password]';",
:onfocus => "this.value=''; this.name='repository[password]';", :onchange => "this.name='repository[password]';"))
:onchange => "this.name='repository[password]';"))
end end
def darcs_field_tags(form, repository) def darcs_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => (repository && !repository.new_record?))) +
content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true))
end end
def mercurial_field_tags(form, repository) def mercurial_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => (repository && !repository.root_url.blank?)
) +
'<br />'.html_safe + l(:text_mercurial_repository_note)) +
content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
) +
'<br />'.html_safe + l(:text_scm_path_encoding_note))
end end
def git_field_tags(form, repository) def git_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => (repository && !repository.root_url.blank?)
) +
'<br />'.html_safe +
l(:text_git_repository_note)) +
content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
) +
'<br />'.html_safe + l(:text_scm_path_encoding_note)) +
content_tag('p', form.check_box(
:extra_report_last_commit,
:label => l(:label_git_report_last_commit)
))
end end
def cvs_field_tags(form, repository) def cvs_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
:root_url, content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
:label => l(:field_cvsroot),
:size => 60, :required => true,
:disabled => !repository.new_record?)) +
content_tag('p', form.text_field(
:url,
:label => l(:field_cvs_module),
:size => 30, :required => true,
:disabled => !repository.new_record?)) +
content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true)) +
content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
) +
'<br />'.html_safe + l(:text_scm_path_encoding_note))
end end
def bazaar_field_tags(form, repository) def bazaar_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => (repository && !repository.new_record?))) +
content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true))
end end
def filesystem_field_tags(form, repository) def filesystem_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
:url, :label => l(:field_root_directory),
:size => 60, :required => true,
:disabled => (repository && !repository.root_url.blank?))) +
content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
) +
'<br />'.html_safe + l(:text_scm_path_encoding_note))
end
def index_commits(commits, heads)
return nil if commits.nil? or commits.first.parents.nil?
refs_map = {}
heads.each do |head|
refs_map[head.scmid] ||= []
refs_map[head.scmid] << head
end
commits_by_scmid = {}
commits.reverse.each_with_index do |commit, commit_index|
commits_by_scmid[commit.scmid] = {
:parent_scmids => commit.parents.collect { |parent| parent.scmid },
:rdmid => commit_index,
:refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil,
:scmid => commit.scmid,
:href => block_given? ? yield(commit.scmid) : commit.scmid
}
end
heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
space = nil
heads.each do |head|
if commits_by_scmid.include? head.scmid
space = index_head((space || -1) + 1, head, commits_by_scmid)
end
end
# when no head matched anything use first commit
space ||= index_head(0, commits.first, commits_by_scmid)
return commits_by_scmid, space
end
def index_head(space, commit, commits_by_scmid)
stack = [[space, commits_by_scmid[commit.scmid]]]
max_space = space
until stack.empty?
space, commit = stack.pop
commit[:space] = space if commit[:space].nil?
space -= 1
commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
parent_commit = commits_by_scmid[parent_scmid]
if parent_commit and parent_commit[:space].nil?
stack.unshift [space += 1, parent_commit]
end
end
max_space = space if max_space < space
end
max_space
end end
end end
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+11 -17
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -21,7 +19,7 @@ module SearchHelper
def highlight_tokens(text, tokens) def highlight_tokens(text, tokens)
return text unless text && tokens && !tokens.empty? return text unless text && tokens && !tokens.empty?
re_tokens = tokens.collect {|t| Regexp.escape(t)} re_tokens = tokens.collect {|t| Regexp.escape(t)}
regexp = Regexp.new "(#{re_tokens.join('|')})", Regexp::IGNORECASE regexp = Regexp.new "(#{re_tokens.join('|')})", Regexp::IGNORECASE
result = '' result = ''
text.split(regexp).each_with_index do |words, i| text.split(regexp).each_with_index do |words, i|
if result.length > 1200 if result.length > 1200
@@ -37,22 +35,21 @@ module SearchHelper
result << content_tag('span', h(words), :class => "highlight token-#{t}") result << content_tag('span', h(words), :class => "highlight token-#{t}")
end end
end end
result.html_safe result
end end
def type_label(t) def type_label(t)
l("label_#{t.singularize}_plural", :default => t.to_s.humanize) l("label_#{t.singularize}_plural", :default => t.to_s.humanize)
end end
def project_select_tag def project_select_tag
options = [[l(:label_project_all), 'all']] options = [[l(:label_project_all), 'all']]
options << [l(:label_my_projects), 'my_projects'] unless User.current.memberships.empty? options << [l(:label_my_projects), 'my_projects'] unless User.current.memberships.empty?
options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.descendants.active.empty? options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.descendants.active.empty?
options << [@project.name, ''] unless @project.nil? options << [@project.name, ''] unless @project.nil?
label_tag("scope", l(:description_project_scope), :class => "hidden-for-sighted") +
select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1 select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1
end end
def render_results_by_type(results_by_type) def render_results_by_type(results_by_type)
links = [] links = []
# Sorts types by results count # Sorts types by results count
@@ -60,11 +57,8 @@ module SearchHelper
c = results_by_type[t] c = results_by_type[t]
next if c == 0 next if c == 0
text = "#{type_label(t)} (#{c})" text = "#{type_label(t)} (#{c})"
links << link_to(h(text), :q => params[:q], :titles_only => params[:titles_only], links << link_to(text, :q => params[:q], :titles_only => params[:title_only], :all_words => params[:all_words], :scope => params[:scope], t => 1)
:all_words => params[:all_words], :scope => params[:scope], t => 1)
end end
('<ul>'.html_safe + ('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
links.map {|link| content_tag('li', link)}.join(' ').html_safe +
'</ul>'.html_safe) unless links.empty?
end end
end end
+30 -39
View File
@@ -1,18 +1,16 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2009 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -29,56 +27,49 @@ module SettingsHelper
{:name => 'repositories', :partial => 'settings/repositories', :label => :label_repository_plural} {:name => 'repositories', :partial => 'settings/repositories', :label => :label_repository_plural}
] ]
end end
def setting_select(setting, choices, options={}) def setting_select(setting, choices, options={})
if blank_text = options.delete(:blank) if blank_text = options.delete(:blank)
choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices
end end
setting_label(setting, options).html_safe + setting_label(setting, options) +
select_tag("settings[#{setting}]", select_tag("settings[#{setting}]", options_for_select(choices, Setting.send(setting).to_s), options)
options_for_select(choices, Setting.send(setting).to_s),
options).html_safe
end end
def setting_multiselect(setting, choices, options={}) def setting_multiselect(setting, choices, options={})
setting_values = Setting.send(setting) setting_values = Setting.send(setting)
setting_values = [] unless setting_values.is_a?(Array) setting_values = [] unless setting_values.is_a?(Array)
setting_label(setting, options).html_safe + setting_label(setting, options) +
hidden_field_tag("settings[#{setting}][]", '').html_safe + hidden_field_tag("settings[#{setting}][]", '') +
choices.collect do |choice| choices.collect do |choice|
text, value = (choice.is_a?(Array) ? choice : [choice, choice]) text, value = (choice.is_a?(Array) ? choice : [choice, choice])
content_tag( content_tag('label',
'label', check_box_tag("settings[#{setting}][]", value, Setting.send(setting).include?(value)) + text.to_s,
check_box_tag(
"settings[#{setting}][]",
value,
Setting.send(setting).include?(value)
) + text.to_s,
:class => 'block' :class => 'block'
) )
end.join.html_safe end.join
end end
def setting_text_field(setting, options={}) def setting_text_field(setting, options={})
setting_label(setting, options).html_safe + setting_label(setting, options) +
text_field_tag("settings[#{setting}]", Setting.send(setting), options).html_safe text_field_tag("settings[#{setting}]", Setting.send(setting), options)
end end
def setting_text_area(setting, options={}) def setting_text_area(setting, options={})
setting_label(setting, options).html_safe + setting_label(setting, options) +
text_area_tag("settings[#{setting}]", Setting.send(setting), options).html_safe text_area_tag("settings[#{setting}]", Setting.send(setting), options)
end end
def setting_check_box(setting, options={}) def setting_check_box(setting, options={})
setting_label(setting, options).html_safe + setting_label(setting, options) +
hidden_field_tag("settings[#{setting}]", 0).html_safe + hidden_field_tag("settings[#{setting}]", 0) +
check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options).html_safe check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options)
end end
def setting_label(setting, options={}) def setting_label(setting, options={})
label = options.delete(:label) label = options.delete(:label)
label != false ? content_tag("label", l(label || "setting_#{setting}")).html_safe : '' label != false ? content_tag("label", l(label || "setting_#{setting}")) : ''
end end
# Renders a notification field for a Redmine::Notifiable option # Renders a notification field for a Redmine::Notifiable option
@@ -86,8 +77,8 @@ module SettingsHelper
return content_tag(:label, return content_tag(:label,
check_box_tag('settings[notified_events][]', check_box_tag('settings[notified_events][]',
notifiable.name, notifiable.name,
Setting.notified_events.include?(notifiable.name)).html_safe + Setting.notified_events.include?(notifiable.name)) +
l_or_humanize(notifiable.name, :prefix => 'label_').html_safe, l_or_humanize(notifiable.name, :prefix => 'label_'),
:class => notifiable.parent.present? ? "parent" : '').html_safe :class => notifiable.parent.present? ? "parent" : '')
end end
end end
+32 -31
View File
@@ -1,5 +1,3 @@
# encoding: utf-8
#
# Helpers to sort tables using clickable column headers. # Helpers to sort tables using clickable column headers.
# #
# Author: Stuart Rackham <srackham@methods.co.nz>, March 2005. # Author: Stuart Rackham <srackham@methods.co.nz>, March 2005.
@@ -17,18 +15,18 @@
# #
# helper :sort # helper :sort
# include SortHelper # include SortHelper
# #
# def list # def list
# sort_init 'last_name' # sort_init 'last_name'
# sort_update %w(first_name last_name) # sort_update %w(first_name last_name)
# @items = Contact.find_all nil, sort_clause # @items = Contact.find_all nil, sort_clause
# end # end
# #
# Controller (using Pagination module): # Controller (using Pagination module):
# #
# helper :sort # helper :sort
# include SortHelper # include SortHelper
# #
# def list # def list
# sort_init 'last_name' # sort_init 'last_name'
# sort_update %w(first_name last_name) # sort_update %w(first_name last_name)
@@ -36,9 +34,9 @@
# :order_by => sort_clause, # :order_by => sort_clause,
# :per_page => 10 # :per_page => 10
# end # end
# #
# View (table header in list.rhtml): # View (table header in list.rhtml):
# #
# <thead> # <thead>
# <tr> # <tr>
# <%= sort_header_tag('id', :title => 'Sort by contact ID') %> # <%= sort_header_tag('id', :title => 'Sort by contact ID') %>
@@ -54,32 +52,32 @@
module SortHelper module SortHelper
class SortCriteria class SortCriteria
def initialize def initialize
@criteria = [] @criteria = []
end end
def available_criteria=(criteria) def available_criteria=(criteria)
unless criteria.is_a?(Hash) unless criteria.is_a?(Hash)
criteria = criteria.inject({}) {|h,k| h[k] = k; h} criteria = criteria.inject({}) {|h,k| h[k] = k; h}
end end
@available_criteria = criteria @available_criteria = criteria
end end
def from_param(param) def from_param(param)
@criteria = param.to_s.split(',').collect {|s| s.split(':')[0..1]} @criteria = param.to_s.split(',').collect {|s| s.split(':')[0..1]}
normalize! normalize!
end end
def criteria=(arg) def criteria=(arg)
@criteria = arg @criteria = arg
normalize! normalize!
end end
def to_param def to_param
@criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',') @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',')
end end
def to_sql def to_sql
sql = @criteria.collect do |k,o| sql = @criteria.collect do |k,o|
if s = @available_criteria[k] if s = @available_criteria[k]
@@ -88,33 +86,33 @@ module SortHelper
end.compact.join(', ') end.compact.join(', ')
sql.blank? ? nil : sql sql.blank? ? nil : sql
end end
def add!(key, asc) def add!(key, asc)
@criteria.delete_if {|k,o| k == key} @criteria.delete_if {|k,o| k == key}
@criteria = [[key, asc]] + @criteria @criteria = [[key, asc]] + @criteria
normalize! normalize!
end end
def add(*args) def add(*args)
r = self.class.new.from_param(to_param) r = self.class.new.from_param(to_param)
r.add!(*args) r.add!(*args)
r r
end end
def first_key def first_key
@criteria.first && @criteria.first.first @criteria.first && @criteria.first.first
end end
def first_asc? def first_asc?
@criteria.first && @criteria.first.last @criteria.first && @criteria.first.last
end end
def empty? def empty?
@criteria.empty? @criteria.empty?
end end
private private
def normalize! def normalize!
@criteria ||= [] @criteria ||= []
@criteria = @criteria.collect {|s| s = s.to_a; [s.first, (s.last == false || s.last == 'desc') ? false : true]} @criteria = @criteria.collect {|s| s = s.to_a; [s.first, (s.last == false || s.last == 'desc') ? false : true]}
@@ -122,7 +120,7 @@ module SortHelper
@criteria.slice!(3) @criteria.slice!(3)
self self
end end
# Appends DESC to the sort criterion unless it has a fixed order # Appends DESC to the sort criterion unless it has a fixed order
def append_desc(criterion) def append_desc(criterion)
if criterion =~ / (asc|desc)$/i if criterion =~ / (asc|desc)$/i
@@ -132,14 +130,14 @@ module SortHelper
end end
end end
end end
def sort_name def sort_name
controller_name + '_' + action_name + '_sort' controller_name + '_' + action_name + '_sort'
end end
# Initializes the default sort. # Initializes the default sort.
# Examples: # Examples:
# #
# sort_init 'name' # sort_init 'name'
# sort_init 'id', 'desc' # sort_init 'id', 'desc'
# sort_init ['name', ['id', 'desc']] # sort_init ['name', ['id', 'desc']]
@@ -160,15 +158,14 @@ module SortHelper
# sort_clause. # sort_clause.
# - criteria can be either an array or a hash of allowed keys # - criteria can be either an array or a hash of allowed keys
# #
def sort_update(criteria, sort_name=nil) def sort_update(criteria)
sort_name ||= self.sort_name
@sort_criteria = SortCriteria.new @sort_criteria = SortCriteria.new
@sort_criteria.available_criteria = criteria @sort_criteria.available_criteria = criteria
@sort_criteria.from_param(params[:sort] || session[sort_name]) @sort_criteria.from_param(params[:sort] || session[sort_name])
@sort_criteria.criteria = @sort_default if @sort_criteria.empty? @sort_criteria.criteria = @sort_default if @sort_criteria.empty?
session[sort_name] = @sort_criteria.to_param session[sort_name] = @sort_criteria.to_param
end end
# Clears the sort criteria session data # Clears the sort criteria session data
# #
def sort_clear def sort_clear
@@ -190,7 +187,7 @@ module SortHelper
# #
def sort_link(column, caption, default_order) def sort_link(column, caption, default_order)
css, order = nil, default_order css, order = nil, default_order
if column.to_s == @sort_criteria.first_key if column.to_s == @sort_criteria.first_key
if @sort_criteria.first_asc? if @sort_criteria.first_asc?
css = 'sort asc' css = 'sort asc'
@@ -201,14 +198,18 @@ module SortHelper
end end
end end
caption = column.to_s.humanize unless caption caption = column.to_s.humanize unless caption
sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param } sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param }
url_options = params.merge(sort_options) # don't reuse params if filters are present
url_options = params.has_key?(:set_filter) ? sort_options : params.merge(sort_options)
# Add project_id to url_options # Add project_id to url_options
url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id) url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id)
link_to_content_update(h(caption), url_options, :class => css) link_to_remote(caption,
{:update => "content", :url => url_options, :method => :get},
{:href => url_for(url_options),
:class => css})
end end
# Returns a table header <th> tag with a sort link for the named column # Returns a table header <th> tag with a sort link for the named column
+49 -55
View File
@@ -1,25 +1,23 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
module TimelogHelper module TimelogHelper
include ApplicationHelper include ApplicationHelper
def render_timelog_breadcrumb def render_timelog_breadcrumb
links = [] links = []
links << link_to(l(:label_project_all), {:project_id => nil, :issue_id => nil}) links << link_to(l(:label_project_all), {:project_id => nil, :issue_id => nil})
@@ -54,15 +52,15 @@ module TimelogHelper
activities.each { |a| collection << [a.name, a.id] } activities.each { |a| collection << [a.name, a.id] }
collection collection
end end
def select_hours(data, criteria, value) def select_hours(data, criteria, value)
if value.to_s.empty? if value.to_s.empty?
data.select {|row| row[criteria].blank? } data.select {|row| row[criteria].blank? }
else else
data.select {|row| row[criteria].to_s == value.to_s} data.select {|row| row[criteria].to_s == value.to_s}
end end
end end
def sum_hours(data) def sum_hours(data)
sum = 0 sum = 0
data.each do |row| data.each do |row|
@@ -70,7 +68,7 @@ module TimelogHelper
end end
sum sum
end end
def options_for_period_select(value) def options_for_period_select(value)
options_for_select([[l(:label_all_time), 'all'], options_for_select([[l(:label_all_time), 'all'],
[l(:label_today), 'today'], [l(:label_today), 'today'],
@@ -84,8 +82,9 @@ module TimelogHelper
[l(:label_this_year), 'current_year']], [l(:label_this_year), 'current_year']],
value) value)
end end
def entries_to_csv(entries) def entries_to_csv(entries)
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
decimal_separator = l(:general_csv_decimal_separator) decimal_separator = l(:general_csv_decimal_separator)
custom_fields = TimeEntryCustomField.find(:all) custom_fields = TimeEntryCustomField.find(:all)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
@@ -102,10 +101,8 @@ module TimelogHelper
] ]
# Export custom fields # Export custom fields
headers += custom_fields.collect(&:name) headers += custom_fields.collect(&:name)
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8( csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
c.to_s,
l(:general_csv_encoding) ) }
# csv lines # csv lines
entries.each do |entry| entries.each do |entry|
fields = [format_date(entry.spent_on), fields = [format_date(entry.spent_on),
@@ -118,20 +115,18 @@ module TimelogHelper
entry.hours.to_s.gsub('.', decimal_separator), entry.hours.to_s.gsub('.', decimal_separator),
entry.comments entry.comments
] ]
fields += custom_fields.collect {|f| show_value(entry.custom_field_values.detect {|v| v.custom_field_id == f.id}) } fields += custom_fields.collect {|f| show_value(entry.custom_value_for(f)) }
csv << fields.collect {|c| Redmine::CodesetUtil.from_utf8( csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
c.to_s,
l(:general_csv_encoding) ) }
end end
end end
export export
end end
def format_criteria_value(criteria_options, value) def format_criteria_value(criteria, value)
if value.blank? if value.blank?
"[#{l(:label_none)}]" l(:label_none)
elsif k = criteria_options[:klass] elsif k = @available_criterias[criteria][:klass]
obj = k.find_by_id(value.to_i) obj = k.find_by_id(value.to_i)
if obj.is_a?(Issue) if obj.is_a?(Issue)
obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}" obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}"
@@ -139,58 +134,57 @@ module TimelogHelper
obj obj
end end
else else
format_value(value, criteria_options[:format]) format_value(value, @available_criterias[criteria][:format])
end end
end end
def report_to_csv(report) def report_to_csv(criterias, periods, hours)
decimal_separator = l(:general_csv_decimal_separator)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# Column headers # Column headers
headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) } headers = criterias.collect {|criteria| l(@available_criterias[criteria][:label]) }
headers += report.periods headers += periods
headers << l(:label_total) headers << l(:label_total)
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8( csv << headers.collect {|c| to_utf8(c) }
c.to_s,
l(:general_csv_encoding) ) }
# Content # Content
report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours) report_criteria_to_csv(csv, criterias, periods, hours)
# Total row # Total row
str_total = Redmine::CodesetUtil.from_utf8(l(:label_total), l(:general_csv_encoding)) row = [ l(:label_total) ] + [''] * (criterias.size - 1)
row = [ str_total ] + [''] * (report.criteria.size - 1)
total = 0 total = 0
report.periods.each do |period| periods.each do |period|
sum = sum_hours(select_hours(report.hours, report.columns, period.to_s)) sum = sum_hours(select_hours(hours, @columns, period.to_s))
total += sum total += sum
row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '') row << (sum > 0 ? "%.2f" % sum : '')
end end
row << ("%.2f" % total).gsub('.',decimal_separator) row << "%.2f" %total
csv << row csv << row
end end
export export
end end
def report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours, level=0) def report_criteria_to_csv(csv, criterias, periods, hours, level=0)
decimal_separator = l(:general_csv_decimal_separator) hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value|
hours.collect {|h| h[criteria[level]].to_s}.uniq.each do |value| hours_for_value = select_hours(hours, criterias[level], value)
hours_for_value = select_hours(hours, criteria[level], value)
next if hours_for_value.empty? next if hours_for_value.empty?
row = [''] * level row = [''] * level
row << Redmine::CodesetUtil.from_utf8( row << to_utf8(format_criteria_value(criterias[level], value))
format_criteria_value(available_criteria[criteria[level]], value).to_s, row += [''] * (criterias.length - level - 1)
l(:general_csv_encoding) )
row += [''] * (criteria.length - level - 1)
total = 0 total = 0
periods.each do |period| periods.each do |period|
sum = sum_hours(select_hours(hours_for_value, columns, period.to_s)) sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s))
total += sum total += sum
row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '') row << (sum > 0 ? "%.2f" % sum : '')
end end
row << ("%.2f" % total).gsub('.',decimal_separator) row << "%.2f" %total
csv << row csv << row
if criteria.length > level + 1
report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours_for_value, level + 1) if criterias.length > level + 1
report_criteria_to_csv(csv, criterias, periods, hours_for_value, level + 1)
end end
end end
end end
def to_utf8(s)
@ic ||= Iconv.new(l(:general_csv_encoding), 'UTF-8')
begin; @ic.iconv(s.to_s); rescue; s.to_s; end
end
end end
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+13 -15
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -20,12 +18,12 @@
module UsersHelper module UsersHelper
def users_status_options_for_select(selected) def users_status_options_for_select(selected)
user_count_by_status = User.count(:group => 'status').to_hash user_count_by_status = User.count(:group => 'status').to_hash
options_for_select([[l(:label_all), ''], options_for_select([[l(:label_all), ''],
["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'], ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", 1],
["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'], ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", 2],
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s) ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", 3]], selected)
end end
# Options for the new membership projects combo-box # Options for the new membership projects combo-box
def options_for_membership_project_select(user, projects) def options_for_membership_project_select(user, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
@@ -34,14 +32,14 @@ module UsersHelper
end end
options options
end end
def user_mail_notification_options(user) def user_mail_notification_options(user)
user.valid_notification_options.collect {|o| [l(o.last), o.first]} user.valid_notification_options.collect {|o| [l(o.last), o.first]}
end end
def change_status_link(user) def change_status_link(user)
url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil} url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
if user.locked? if user.locked?
link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock' link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
elsif user.registered? elsif user.registered?
@@ -50,7 +48,7 @@ module UsersHelper
link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock' link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock'
end end
end end
def user_settings_tabs def user_settings_tabs
tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general}, tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
{:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural} {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
+11 -12
View File
@@ -1,29 +1,28 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
module VersionsHelper module VersionsHelper
STATUS_BY_CRITERIAS = %w(category tracker status priority author assigned_to) STATUS_BY_CRITERIAS = %w(category tracker priority author assigned_to)
def render_issue_status_by(version, criteria) def render_issue_status_by(version, criteria)
criteria = 'category' unless STATUS_BY_CRITERIAS.include?(criteria) criteria ||= 'category'
raise 'Unknown criteria' unless STATUS_BY_CRITERIAS.include?(criteria)
h = Hash.new {|k,v| k[v] = [0, 0]} h = Hash.new {|k,v| k[v] = [0, 0]}
begin begin
# Total issue count # Total issue count
@@ -38,10 +37,10 @@ module VersionsHelper
end end
counts = h.keys.compact.sort.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}} counts = h.keys.compact.sort.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
max = counts.collect {|c| c[:total]}.max max = counts.collect {|c| c[:total]}.max
render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max} render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max}
end end
def status_by_options_for_select(value) def status_by_options_for_select(value)
options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value) options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value)
end end
+25 -36
View File
@@ -1,80 +1,69 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
module WatchersHelper module WatchersHelper
def watcher_tag(object, user, options={}) # Valid options
content_tag("span", watcher_link(object, user), :class => watcher_css(object)) # * :id - the element id
# * :replace - a string or array of element ids that will be
# replaced
def watcher_tag(object, user, options={:replace => 'watcher'})
id = options[:id]
id ||= options[:replace] if options[:replace].is_a? String
content_tag("span", watcher_link(object, user, options), :id => id)
end end
def watcher_link(object, user) # Valid options
# * :replace - a string or array of element ids that will be
# replaced
def watcher_link(object, user, options={:replace => 'watcher'})
return '' unless user && user.logged? && object.respond_to?('watched_by?') return '' unless user && user.logged? && object.respond_to?('watched_by?')
watched = object.watched_by?(user) watched = object.watched_by?(user)
url = {:controller => 'watchers', url = {:controller => 'watchers',
:action => (watched ? 'unwatch' : 'watch'), :action => (watched ? 'unwatch' : 'watch'),
:object_type => object.class.to_s.underscore, :object_type => object.class.to_s.underscore,
:object_id => object.id} :object_id => object.id,
:replace => options[:replace]}
link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)), link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
{:url => url}, {:url => url},
:href => url_for(url), :href => url_for(url),
:class => (watched ? 'icon icon-fav' : 'icon icon-fav-off')) :class => (watched ? 'icon icon-fav' : 'icon icon-fav-off'))
end end
# Returns the css class used to identify watch links for a given +object+
def watcher_css(object)
"#{object.class.to_s.underscore}-#{object.id}-watcher"
end
# Returns a comma separated list of users watching the given object # Returns a comma separated list of users watching the given object
def watchers_list(object) def watchers_list(object)
remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project) remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
content = ''.html_safe
lis = object.watcher_users.collect do |user| lis = object.watcher_users.collect do |user|
s = ''.html_safe s = avatar(user, :size => "16").to_s + link_to_user(user, :class => 'user').to_s
s << avatar(user, :size => "16").to_s
s << link_to_user(user, :class => 'user')
if remove_allowed if remove_allowed
url = {:controller => 'watchers', url = {:controller => 'watchers',
:action => 'destroy', :action => 'destroy',
:object_type => object.class.to_s.underscore, :object_type => object.class.to_s.underscore,
:object_id => object.id, :object_id => object.id,
:user_id => user} :user_id => user}
s << ' ' s += ' ' + link_to_remote(image_tag('delete.png'),
s << link_to_remote(image_tag('delete.png'),
{:url => url}, {:url => url},
:href => url_for(url), :href => url_for(url),
:style => "vertical-align: middle", :style => "vertical-align: middle",
:class => "delete") :class => "delete")
end end
content << content_tag('li', s) "<li>#{ s }</li>"
end end
content.present? ? content_tag('ul', content) : content lis.empty? ? "" : "<ul>#{ lis.join("\n") }</ul>"
end
def watchers_checkboxes(object, users, checked=nil)
users.map do |user|
c = checked.nil? ? object.watched_by?(user) : checked
tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil
content_tag 'label', "#{tag} #{h(user)}".html_safe,
:id => "issue_watcher_user_ids_#{user.id}",
:class => "floating"
end.join.html_safe
end end
end end
+4 -6
View File
@@ -1,18 +1,16 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+49 -23
View File
@@ -1,43 +1,69 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2012 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
module WikiHelper module WikiHelper
def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0) def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
pages = pages.group_by(&:parent) unless pages.is_a?(Hash) s = ''
s = ''.html_safe pages.select {|p| p.parent == parent}.each do |page|
if pages.has_key?(parent) attrs = "value='#{page.id}'"
pages[parent].each do |page| attrs << " selected='selected'" if selected == page
attrs = "value='#{page.id}'" indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : nil
attrs << " selected='selected'" if selected == page
indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : '' s << "<option #{attrs}>#{indent}#{h page.pretty_title}</option>\n" +
wiki_page_options_for_select(pages, selected, page, level + 1)
s << content_tag('option', (indent + h(page.pretty_title)).html_safe, :value => page.id.to_s, :selected => selected == page) +
wiki_page_options_for_select(pages, selected, page, level + 1)
end
end end
s s
end end
def wiki_page_breadcrumb(page) def html_diff(wdiff)
breadcrumb(page.ancestors.reverse.collect {|parent| words = wdiff.words.collect{|word| h(word)}
link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project}) words_add = 0
}) words_del = 0
dels = 0
del_off = 0
wdiff.diff.diffs.each do |diff|
add_at = nil
add_to = nil
del_at = nil
deleted = ""
diff.each do |change|
pos = change[1]
if change[0] == "+"
add_at = pos + dels unless add_at
add_to = pos + dels
words_add += 1
else
del_at = pos unless del_at
deleted << ' ' + h(change[2])
words_del += 1
end
end
if add_at
words[add_at] = '<span class="diff_in">' + words[add_at]
words[add_to] = words[add_to] + '</span>'
end
if del_at
words.insert del_at - del_off + dels + words_add, '<span class="diff_out">' + deleted + '</span>'
dels += 1
del_off += words_del
words_del = 0
end
end
simple_format_without_paragraph(words.join(' '))
end end
end end
+3 -5
View File
@@ -1,18 +1,16 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2008 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
+65 -114
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -20,11 +20,10 @@ require "digest/md5"
class Attachment < ActiveRecord::Base class Attachment < ActiveRecord::Base
belongs_to :container, :polymorphic => true belongs_to :container, :polymorphic => true
belongs_to :author, :class_name => "User", :foreign_key => "author_id" belongs_to :author, :class_name => "User", :foreign_key => "author_id"
validates_presence_of :filename, :author validates_presence_of :container, :filename, :author
validates_length_of :filename, :maximum => 255 validates_length_of :filename, :maximum => 255
validates_length_of :disk_filename, :maximum => 255 validates_length_of :disk_filename, :maximum => 255
validate :validate_max_file_size
acts_as_event :title => :filename, acts_as_event :title => :filename,
:url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}} :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
@@ -32,34 +31,23 @@ class Attachment < ActiveRecord::Base
acts_as_activity_provider :type => 'files', acts_as_activity_provider :type => 'files',
:permission => :view_files, :permission => :view_files,
:author_key => :author_id, :author_key => :author_id,
:find_options => {:select => "#{Attachment.table_name}.*", :find_options => {:select => "#{Attachment.table_name}.*",
:joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " + :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
"LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_id = #{Project.table_name}.id )"} "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_id = #{Project.table_name}.id )"}
acts_as_activity_provider :type => 'documents', acts_as_activity_provider :type => 'documents',
:permission => :view_documents, :permission => :view_documents,
:author_key => :author_id, :author_key => :author_id,
:find_options => {:select => "#{Attachment.table_name}.*", :find_options => {:select => "#{Attachment.table_name}.*",
:joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " + :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"} "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
cattr_accessor :storage_path cattr_accessor :storage_path
@@storage_path = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, "files") @@storage_path = "#{RAILS_ROOT}/files"
before_save :files_to_final_location def validate
after_destroy :delete_from_disk if self.filesize > Setting.attachment_max_size.to_i.kilobytes
errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
# Returns an unsaved copy of the attachment
def copy(attributes=nil)
copy = self.class.new
copy.attributes = self.attributes.dup.except("id", "downloads")
copy.attributes = attributes if attributes
copy
end
def validate_max_file_size
if @temp_file && self.filesize > Setting.attachment_max_size.to_i.kilobytes
errors.add(:base, l(:error_attachment_too_big, :max_size => Setting.attachment_max_size.to_i.kilobytes))
end end
end end
@@ -67,160 +55,123 @@ class Attachment < ActiveRecord::Base
unless incoming_file.nil? unless incoming_file.nil?
@temp_file = incoming_file @temp_file = incoming_file
if @temp_file.size > 0 if @temp_file.size > 0
if @temp_file.respond_to?(:original_filename) self.filename = sanitize_filename(@temp_file.original_filename)
self.filename = @temp_file.original_filename self.disk_filename = Attachment.disk_filename(filename)
self.filename.force_encoding("UTF-8") if filename.respond_to?(:force_encoding) self.content_type = @temp_file.content_type.to_s.chomp
end if content_type.blank?
if @temp_file.respond_to?(:content_type)
self.content_type = @temp_file.content_type.to_s.chomp
end
if content_type.blank? && filename.present?
self.content_type = Redmine::MimeType.of(filename) self.content_type = Redmine::MimeType.of(filename)
end end
self.filesize = @temp_file.size self.filesize = @temp_file.size
end end
end end
end end
def file def file
nil nil
end end
def filename=(arg)
write_attribute :filename, sanitize_filename(arg.to_s)
if new_record? && disk_filename.blank?
self.disk_filename = Attachment.disk_filename(filename)
end
filename
end
# Copies the temporary file to its final location # Copies the temporary file to its final location
# and computes its MD5 hash # and computes its MD5 hash
def files_to_final_location def before_save
if @temp_file && (@temp_file.size > 0) if @temp_file && (@temp_file.size > 0)
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") logger.debug("saving '#{self.diskfile}'")
md5 = Digest::MD5.new md5 = Digest::MD5.new
File.open(diskfile, "wb") do |f| File.open(diskfile, "wb") do |f|
if @temp_file.respond_to?(:read) buffer = ""
buffer = "" while (buffer = @temp_file.read(8192))
while (buffer = @temp_file.read(8192)) f.write(buffer)
f.write(buffer) md5.update(buffer)
md5.update(buffer)
end
else
f.write(@temp_file)
md5.update(@temp_file)
end end
end end
self.digest = md5.hexdigest self.digest = md5.hexdigest
end end
@temp_file = nil
# Don't save the content type if it's longer than the authorized length # Don't save the content type if it's longer than the authorized length
if self.content_type && self.content_type.length > 255 if self.content_type && self.content_type.length > 255
self.content_type = nil self.content_type = nil
end end
end end
# Deletes the file from the file system if it's not referenced by other attachments # Deletes file on the disk
def delete_from_disk def after_destroy
if Attachment.first(:conditions => ["disk_filename = ? AND id <> ?", disk_filename, id]).nil? File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
delete_from_disk!
end
end end
# Returns file's location on disk # Returns file's location on disk
def diskfile def diskfile
File.join(self.class.storage_path, disk_filename.to_s) "#{@@storage_path}/#{self.disk_filename}"
end end
def increment_download def increment_download
increment!(:downloads) increment!(:downloads)
end end
def project def project
container.try(:project) container.project
end end
def visible?(user=User.current) def visible?(user=User.current)
container && container.attachments_visible?(user) container.attachments_visible?(user)
end end
def deletable?(user=User.current) def deletable?(user=User.current)
container && container.attachments_deletable?(user) container.attachments_deletable?(user)
end end
def image? def image?
self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i self.filename =~ /\.(jpe?g|gif|png)$/i
end end
def is_text? def is_text?
Redmine::MimeType.is_type?('text', filename) Redmine::MimeType.is_type?('text', filename)
end end
def is_diff? def is_diff?
self.filename =~ /\.(patch|diff)$/i self.filename =~ /\.(patch|diff)$/i
end end
# Returns true if the file is readable # Returns true if the file is readable
def readable? def readable?
File.readable?(diskfile) File.readable?(diskfile)
end end
# Returns the attachment token
def token
"#{id}.#{digest}"
end
# Finds an attachment that matches the given token and that has no container
def self.find_by_token(token)
if token.to_s =~ /^(\d+)\.([0-9a-f]+)$/
attachment_id, attachment_digest = $1, $2
attachment = Attachment.first(:conditions => {:id => attachment_id, :digest => attachment_digest})
if attachment && attachment.container.nil?
attachment
end
end
end
# Bulk attaches a set of files to an object # Bulk attaches a set of files to an object
# #
# Returns a Hash of the results: # Returns a Hash of the results:
# :files => array of the attached files # :files => array of the attached files
# :unsaved => array of the files that could not be attached # :unsaved => array of the files that could not be attached
def self.attach_files(obj, attachments) def self.attach_files(obj, attachments)
result = obj.save_attachments(attachments, User.current) attached = []
obj.attach_saved_attachments if attachments && attachments.is_a?(Hash)
result attachments.each_value do |attachment|
end file = attachment['file']
next unless file && file.size > 0
a = Attachment.create(:container => obj,
:file => file,
:description => attachment['description'].to_s.strip,
:author => User.current)
def self.latest_attach(attachments, filename) if a.new_record?
attachments.sort_by(&:created_on).reverse.detect { obj.unsaved_attachments ||= []
|att| att.filename.downcase == filename.downcase obj.unsaved_attachments << a
} else
end attached << a
end
def self.prune(age=1.day) end
attachments = Attachment.all(:conditions => ["created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age])
attachments.each(&:destroy)
end
private
# Physically deletes the file from the file system
def delete_from_disk!
if disk_filename.present? && File.exist?(diskfile)
File.delete(diskfile)
end end
{:files => attached, :unsaved => obj.unsaved_attachments}
end end
private
def sanitize_filename(value) def sanitize_filename(value)
# get only the filename, not the whole path # get only the filename, not the whole path
just_filename = value.gsub(/^.*(\\|\/)/, '') just_filename = value.gsub(/^.*(\\|\/)/, '')
# NOTE: File.basename doesn't work right with Windows paths on Unix
# INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
# Finally, replace invalid characters with underscore # Finally, replace all non alphanumeric, hyphens or periods with underscore
@filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_') @filename = just_filename.gsub(/[^\w\.\-]/,'_')
end end
# Returns an ASCII or hashed filename # Returns an ASCII or hashed filename
def self.disk_filename(filename) def self.disk_filename(filename)
timestamp = DateTime.now.strftime("%y%m%d%H%M%S") timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
+7 -22
View File
@@ -1,52 +1,37 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Generic exception for when the AuthSource can not be reached
# (eg. can not connect to the LDAP)
class AuthSourceException < Exception; end
class AuthSource < ActiveRecord::Base class AuthSource < ActiveRecord::Base
include Redmine::SubclassFactory
include Redmine::Ciphering
has_many :users has_many :users
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
validates_length_of :name, :maximum => 60 validates_length_of :name, :maximum => 60
def authenticate(login, password) def authenticate(login, password)
end end
def test_connection def test_connection
end end
def auth_method_name def auth_method_name
"Abstract" "Abstract"
end end
def account_password
read_ciphered_attribute(:account_password)
end
def account_password=(arg)
write_ciphered_attribute(:account_password, arg)
end
def allow_password_changes? def allow_password_changes?
self.class.allow_password_changes? self.class.allow_password_changes?
end end
+30 -65
View File
@@ -1,93 +1,68 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv'
require 'net/ldap' require 'net/ldap'
require 'net/ldap/dn' require 'iconv'
class AuthSourceLdap < AuthSource class AuthSourceLdap < AuthSource
validates_presence_of :host, :port, :attr_login validates_presence_of :host, :port, :attr_login
validates_length_of :name, :host, :maximum => 60, :allow_nil => true validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true
validates_length_of :account, :account_password, :base_dn, :filter, :maximum => 255, :allow_blank => true validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true
validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
validates_numericality_of :port, :only_integer => true validates_numericality_of :port, :only_integer => true
validate :validate_filter
before_validation :strip_ldap_attributes before_validation :strip_ldap_attributes
def self.human_attribute_name(attribute_key_name, *args) def after_initialize
attr_name = attribute_key_name.to_s
if attr_name == "filter"
attr_name = "ldap_filter"
end
super(attr_name, *args)
end
def initialize(attributes=nil, *args)
super
self.port = 389 if self.port == 0 self.port = 389 if self.port == 0
end end
def authenticate(login, password) def authenticate(login, password)
return nil if login.blank? || password.blank? return nil if login.blank? || password.blank?
attrs = get_user_dn(login, password) attrs = get_user_dn(login)
if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password) if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
return attrs.except(:dn) return attrs.except(:dn)
end end
rescue Net::LDAP::LdapError => e rescue Net::LDAP::LdapError => text
raise AuthSourceException.new(e.message) raise "LdapError: " + text
end end
# test the connection to the LDAP # test the connection to the LDAP
def test_connection def test_connection
ldap_con = initialize_ldap_con(self.account, self.account_password) ldap_con = initialize_ldap_con(self.account, self.account_password)
ldap_con.open { } ldap_con.open { }
rescue Net::LDAP::LdapError => e rescue Net::LDAP::LdapError => text
raise "LdapError: " + e.message raise "LdapError: " + text
end end
def auth_method_name def auth_method_name
"LDAP" "LDAP"
end end
private private
def ldap_filter
if filter.present?
Net::LDAP::Filter.construct(filter)
end
rescue Net::LDAP::LdapError
nil
end
def validate_filter
if filter.present? && ldap_filter.nil?
errors.add(:filter, :invalid)
end
end
def strip_ldap_attributes def strip_ldap_attributes
[:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr| [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr|
write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil? write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil?
end end
end end
def initialize_ldap_con(ldap_user, ldap_password) def initialize_ldap_con(ldap_user, ldap_password)
options = { :host => self.host, options = { :host => self.host,
:port => self.port, :port => self.port,
@@ -125,24 +100,14 @@ class AuthSourceLdap < AuthSource
end end
# Get the user's dn and any attributes for them, given their login # Get the user's dn and any attributes for them, given their login
def get_user_dn(login, password) def get_user_dn(login)
ldap_con = nil ldap_con = initialize_ldap_con(self.account, self.account_password)
if self.account && self.account.include?("$login") login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password) object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
else
ldap_con = initialize_ldap_con(self.account, self.account_password)
end
login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
attrs = {} attrs = {}
search_filter = object_filter & login_filter ldap_con.search( :base => self.base_dn,
if f = ldap_filter :filter => object_filter & login_filter,
search_filter = search_filter & f
end
ldap_con.search( :base => self.base_dn,
:filter => search_filter,
:attributes=> search_attributes) do |entry| :attributes=> search_attributes) do |entry|
if onthefly_register? if onthefly_register?
@@ -156,7 +121,7 @@ class AuthSourceLdap < AuthSource
attrs attrs
end end
def self.get_attr(entry, attr_name) def self.get_attr(entry, attr_name)
if !attr_name.blank? if !attr_name.blank?
entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
+9 -15
View File
@@ -1,50 +1,44 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class Board < ActiveRecord::Base class Board < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :project belongs_to :project
has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC" has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC" has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC"
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
acts_as_list :scope => :project_id acts_as_list :scope => :project_id
acts_as_watchable acts_as_watchable
validates_presence_of :name, :description validates_presence_of :name, :description
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_length_of :description, :maximum => 255 validates_length_of :description, :maximum => 255
scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
safe_attributes 'name', 'description', 'move_to'
def visible?(user=User.current) def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_messages, project) !user.nil? && user.allowed_to?(:view_messages, project)
end end
def to_s def to_s
name name
end end
def reset_counters! def reset_counters!
self.class.reset_counters!(id) self.class.reset_counters!(id)
end end
# 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
+7 -13
View File
@@ -1,36 +1,30 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
class Change < ActiveRecord::Base class Change < ActiveRecord::Base
belongs_to :changeset belongs_to :changeset
validates_presence_of :changeset_id, :action, :path validates_presence_of :changeset_id, :action, :path
before_save :init_path before_save :init_path
before_validation :replace_invalid_utf8_of_path
def relative_path def relative_path
changeset.repository.relative_path(path) changeset.repository.relative_path(path)
end end
def replace_invalid_utf8_of_path
self.path = Redmine::CodesetUtil.replace_invalid_utf8(self.path)
self.from_path = Redmine::CodesetUtil.replace_invalid_utf8(self.from_path)
end
def init_path def init_path
self.path ||= "" self.path ||= ""
end end
+82 -101
View File
@@ -1,16 +1,16 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang # Copyright (C) 2006-2010 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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.
@@ -20,42 +20,30 @@ require 'iconv'
class Changeset < ActiveRecord::Base class Changeset < ActiveRecord::Base
belongs_to :repository belongs_to :repository
belongs_to :user belongs_to :user
has_many :filechanges, :class_name => 'Change', :dependent => :delete_all has_many :changes, :dependent => :delete_all
has_and_belongs_to_many :issues has_and_belongs_to_many :issues
has_and_belongs_to_many :parents,
:class_name => "Changeset",
:join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
:association_foreign_key => 'parent_id', :foreign_key => 'changeset_id'
has_and_belongs_to_many :children,
:class_name => "Changeset",
:join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
:association_foreign_key => 'changeset_id', :foreign_key => 'parent_id'
acts_as_event :title => Proc.new {|o| o.title}, acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
:description => :long_comments, :description => :long_comments,
:datetime => :committed_on, :datetime => :committed_on,
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :repository_id => o.repository.identifier_param, :rev => o.identifier}} :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
acts_as_searchable :columns => 'comments', acts_as_searchable :columns => 'comments',
:include => {:repository => :project}, :include => {:repository => :project},
:project_key => "#{Repository.table_name}.project_id", :project_key => "#{Repository.table_name}.project_id",
:date_column => 'committed_on' :date_column => 'committed_on'
acts_as_activity_provider :timestamp => "#{table_name}.committed_on", acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
:author_key => :user_id, :author_key => :user_id,
:find_options => {:include => [:user, {:repository => :project}]} :find_options => {:include => [:user, {:repository => :project}]}
validates_presence_of :repository_id, :revision, :committed_on, :commit_date validates_presence_of :repository_id, :revision, :committed_on, :commit_date
validates_uniqueness_of :revision, :scope => :repository_id validates_uniqueness_of :revision, :scope => :repository_id
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
scope :visible, named_scope :visible, lambda {|*args| { :include => {:repository => :project},
lambda {|*args| { :include => {:repository => :project}, :conditions => Project.allowed_to_condition(args.first || User.current, :view_changesets) } }
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } }
after_create :scan_for_issues
before_create :before_create_cs
def revision=(r) def revision=(r)
write_attribute :revision, (r.nil? ? nil : r.to_s) write_attribute :revision, (r.nil? ? nil : r.to_s)
end end
@@ -68,6 +56,10 @@ class Changeset < ActiveRecord::Base
revision.to_s revision.to_s
end end
end end
def comments=(comment)
write_attribute(:comments, Changeset.normalize_comments(comment))
end
def committed_on=(date) def committed_on=(date)
self.commit_date = date self.commit_date = date
@@ -82,26 +74,27 @@ class Changeset < ActiveRecord::Base
identifier identifier
end end
end end
def committer=(arg)
write_attribute(:committer, self.class.to_utf8(arg.to_s))
end
def project def project
repository.project repository.project
end end
def author def author
user || committer.to_s.split('<').first user || committer.to_s.split('<').first
end end
def before_create_cs def before_create
self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding) self.user = repository.find_committer_user(committer)
self.comments = self.class.normalize_comments(
self.comments, repository.repo_log_encoding)
self.user = repository.find_committer_user(self.committer)
end end
def scan_for_issues def after_create
scan_comment_for_issue_ids scan_comment_for_issue_ids
end end
TIMELOG_RE = / TIMELOG_RE = /
( (
((\d+)(h|hours?))((\d+)(m|min)?)? ((\d+)(h|hours?))((\d+)(m|min)?)?
@@ -113,7 +106,7 @@ class Changeset < ActiveRecord::Base
(\d+([\.,]\d+)?)h? (\d+([\.,]\d+)?)h?
) )
/x /x
def scan_comment_for_issue_ids def scan_comment_for_issue_ids
return if comments.blank? return if comments.blank?
# keywords used to reference issues # keywords used to reference issues
@@ -121,15 +114,15 @@ class Changeset < ActiveRecord::Base
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_fix_keywords.downcase.split(",").collect(&:strip) 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], 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
@@ -139,98 +132,79 @@ class Changeset < ActiveRecord::Base
end end
end end
end end
referenced_issues.uniq! referenced_issues.uniq!
self.issues = referenced_issues unless referenced_issues.empty? self.issues = referenced_issues unless referenced_issues.empty?
end end
def short_comments def short_comments
@short_comments || split_comments.first @short_comments || split_comments.first
end end
def long_comments def long_comments
@long_comments || split_comments.last @long_comments || split_comments.last
end end
def text_tag(ref_project=nil) def text_tag
tag = if scmid? if scmid?
"commit:#{scmid}" "commit:#{scmid}"
else else
"r#{revision}" "r#{revision}"
end end
if repository && repository.identifier.present?
tag = "#{repository.identifier}|#{tag}"
end
if ref_project && project && ref_project != project
tag = "#{project.identifier}:#{tag}"
end
tag
end end
# Returns the title used for the changeset in the activity/search results
def title
repo = (repository && repository.identifier.present?) ? " (#{repository.identifier})" : ''
comm = short_comments.blank? ? '' : (': ' + short_comments)
"#{l(:label_revision)} #{format_identifier}#{repo}#{comm}"
end
# Returns the previous changeset # Returns the previous changeset
def previous def previous
@previous ||= Changeset.find(:first, @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
:conditions => ['id < ? AND repository_id = ?',
self.id, self.repository_id],
:order => 'id DESC')
end end
# Returns the next changeset # Returns the next changeset
def next def next
@next ||= Changeset.find(:first, @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
:conditions => ['id > ? AND repository_id = ?', end
self.id, self.repository_id],
:order => 'id ASC') # Strips and reencodes a commit log before insertion into the database
def self.normalize_comments(str)
to_utf8(str.to_s.strip)
end end
# Creates a new Change from it's common parameters # Creates a new Change from it's common parameters
def create_change(change) def create_change(change)
Change.create(:changeset => self, Change.create(:changeset => self,
:action => change[:action], :action => change[:action],
:path => change[:path], :path => change[:path],
:from_path => change[:from_path], :from_path => change[:from_path],
:from_revision => change[:from_revision]) :from_revision => change[:from_revision])
end end
private
# Finds an issue that can be referenced by the commit message # Finds an issue that can be referenced by the commit message
# i.e. an issue that belong to the repository project, a subproject or a parent project
def find_referenced_issue_by_id(id) def find_referenced_issue_by_id(id)
return nil if id.blank? return nil if id.blank?
issue = Issue.find_by_id(id.to_i, :include => :project) issue = Issue.find_by_id(id.to_i, :include => :project)
if Setting.commit_cross_project_ref? if issue
# all issues can be referenced/fixed unless issue.project && (project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project))
elsif issue
# issue that belong to the repository project, a subproject or a parent project only
unless issue.project &&
(project == issue.project || project.is_ancestor_of?(issue.project) ||
project.is_descendant_of?(issue.project))
issue = nil issue = nil
end end
end end
issue issue
end end
private
def fix_issue(issue) def fix_issue(issue)
status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i) status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
if status.nil? if status.nil?
logger.warn("No status matches commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger logger.warn("No status macthes commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
return issue return issue
end 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, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project))) journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag))
issue.status = status issue.status = status
unless Setting.commit_fix_done_ratio.blank? unless Setting.commit_fix_done_ratio.blank?
issue.done_ratio = Setting.commit_fix_done_ratio.to_i issue.done_ratio = Setting.commit_fix_done_ratio.to_i
@@ -242,30 +216,29 @@ class Changeset < ActiveRecord::Base
end end
issue issue
end end
def log_time(issue, hours) def log_time(issue, hours)
time_entry = TimeEntry.new( time_entry = TimeEntry.new(
:user => user, :user => user,
:hours => hours, :hours => hours,
:issue => issue, :issue => issue,
:spent_on => commit_date, :spent_on => commit_date,
:comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project), :comments => l(:text_time_logged_by_changeset, :value => text_tag, :locale => Setting.default_language)
:locale => Setting.default_language)
) )
time_entry.activity = log_time_activity unless log_time_activity.nil? time_entry.activity = log_time_activity unless log_time_activity.nil?
unless time_entry.save unless time_entry.save
logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
end end
time_entry time_entry
end end
def log_time_activity def log_time_activity
if Setting.commit_logtime_activity_id.to_i > 0 if Setting.commit_logtime_activity_id.to_i > 0
TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i) TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
end end
end end
def split_comments def split_comments
comments =~ /\A(.+?)\r?\n(.*)$/m comments =~ /\A(.+?)\r?\n(.*)$/m
@short_comments = $1 || comments @short_comments = $1 || comments
@@ -273,14 +246,22 @@ class Changeset < ActiveRecord::Base
return @short_comments, @long_comments return @short_comments, @long_comments
end end
public def self.to_utf8(str)
return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
# Strips and reencodes a commit log before insertion into the database encoding = Setting.commit_logs_encoding.to_s.strip
def self.normalize_comments(str, encoding) unless encoding.blank? || encoding == 'UTF-8'
Changeset.to_utf8(str.to_s.strip, encoding) begin
end str = Iconv.conv('UTF-8', encoding, str)
rescue Iconv::Failure
def self.to_utf8(str, encoding) # do nothing here
Redmine::CodesetUtil.to_utf8(str, encoding) end
end
# removes invalid UTF8 sequences
begin
Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
rescue Iconv::InvalidEncoding
# "UTF-8//IGNORE" is not supported on some OS
str
end
end end
end end

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