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
550 changed files with 13286 additions and 43503 deletions
-3
View File
@@ -1,7 +1,6 @@
/.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
@@ -10,8 +9,6 @@
/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.*
-5
View File
@@ -3,7 +3,6 @@ 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
@@ -12,8 +11,6 @@ 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.*
@@ -25,5 +22,3 @@ tmp/sockets/*
tmp/test/* tmp/test/*
vendor/rails vendor/rails
*.rbc *.rbc
.svn/
.git/
+2 -14
View File
@@ -203,25 +203,13 @@ class AccountController < ApplicationController
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
+2 -18
View File
@@ -1,24 +1,7 @@
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class 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
@@ -72,4 +55,5 @@ class ActivitiesController < ApplicationController
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
end end
+5 -3
View File
@@ -17,7 +17,9 @@
class AdminController < ApplicationController class AdminController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin
helper :sort helper :sort
include SortHelper include SortHelper
@@ -28,10 +30,12 @@ class AdminController < ApplicationController
def projects def projects
@status = params[:status] ? params[:status].to_i : 1 @status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status]) c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
unless params[:name].blank? unless params[:name].blank?
name = "%#{params[:name].strip.downcase}%" name = "%#{params[:name].strip.downcase}%"
c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name] c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name]
end end
@projects = Project.find :all, :order => 'lft', @projects = Project.find :all, :order => 'lft',
:conditions => c.conditions :conditions => c.conditions
@@ -73,9 +77,7 @@ class AdminController < ApplicationController
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, [:text_default_administrator_account_changed, User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
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?(Engines.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)]
+8 -42
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -18,8 +18,6 @@
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
@@ -43,7 +41,6 @@ class ApplicationController < ActionController::Base
protect_from_forgery 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
@@ -71,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
@@ -224,10 +221,6 @@ class ApplicationController < ActionController::Base
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
@@ -333,40 +326,13 @@ class ApplicationController < ActionController::Base
render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml' render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
end end
# TODO: remove in Redmine 1.4
def self.accept_key_auth(*actions) def self.accept_key_auth(*actions)
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead." actions = actions.flatten.map(&:to_s)
accept_rss_auth(*actions) write_inheritable_attribute('accept_key_auth_actions', actions)
end end
# TODO: remove in Redmine 1.4
def accept_key_auth_actions def accept_key_auth_actions
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead." self.class.read_inheritable_attribute('accept_key_auth_actions') || []
self.class.accept_rss_auth
end
def self.accept_rss_auth(*actions)
if actions.any?
write_inheritable_attribute('accept_rss_auth_actions', actions)
else
read_inheritable_attribute('accept_rss_auth_actions') || []
end
end
def accept_rss_auth?(action=action_name)
self.class.accept_rss_auth.include?(action.to_sym)
end
def self.accept_api_auth(*actions)
if actions.any?
write_inheritable_attribute('accept_api_auth_actions', actions)
else
read_inheritable_attribute('accept_api_auth_actions') || []
end
end
def accept_api_auth?(action=action_name)
self.class.accept_api_auth.include?(action.to_sym)
end end
# Returns the number of objects that should be displayed # Returns the number of objects that should be displayed
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
+3 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -18,7 +18,7 @@
class BoardsController < ApplicationController class BoardsController < ApplicationController
default_search_scope :messages default_search_scope :messages
before_filter :find_project, :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 helper :messages
include MessagesHelper include MessagesHelper
+5 -17
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CalendarsController < ApplicationController class CalendarsController < ApplicationController
menu_item :calendar menu_item :calendar
before_filter :find_optional_project before_filter :find_optional_project
@@ -53,4 +36,9 @@ class CalendarsController < ApplicationController
render :action => 'show', :layout => false if request.xhr? render :action => 'show', :layout => false if request.xhr?
end end
def update
show
end
end end
@@ -1,6 +1,5 @@
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)
@@ -42,17 +41,4 @@ class ContextMenusController < ApplicationController
render :layout => false render :layout => false
end 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?(:log_time, @projects),
:update => User.current.allowed_to?(:log_time, @projects),
:delete => User.current.allowed_to?(:log_time, @projects)
}
@back = back_url
render :layout => false
end
end end
+5 -17
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class GanttsController < ApplicationController class GanttsController < ApplicationController
menu_item :gantt menu_item :gantt
before_filter :find_optional_project before_filter :find_optional_project
@@ -45,4 +28,9 @@ class GanttsController < ApplicationController
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
+1 -1
View File
@@ -132,7 +132,7 @@ class GroupsController < ApplicationController
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
@@ -1,4 +1,4 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2007 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
+5 -16
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -27,8 +27,7 @@ class IssuesController < ApplicationController
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
@@ -107,17 +106,13 @@ class IssuesController < ApplicationController
@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?
if User.current.allowed_to?(:view_changesets, @project)
@changesets = @issue.changesets.visible.all @changesets = @issue.changesets.visible.all
@changesets.reverse! if User.current.wants_comments_in_reverse_order? @changesets.reverse! if User.current.wants_comments_in_reverse_order?
end
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
@allowed_statuses = @issue.new_statuses_allowed_to(User.current) @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@priorities = IssuePriority.all @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 { render :template => 'issues/show.rhtml' } format.html { render :template => 'issues/show.rhtml' }
format.api format.api
@@ -256,13 +251,7 @@ 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
@@ -282,7 +271,7 @@ private
@allowed_statuses = @issue.new_statuses_allowed_to(User.current) @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@priorities = IssuePriority.all @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)
@@ -302,7 +291,6 @@ private
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?
@@ -316,6 +304,7 @@ private
@issue.watcher_user_ids = params[:issue]['watcher_user_ids'] @issue.watcher_user_ids = params[:issue]['watcher_user_ids']
end end
end end
@issue.author = User.current
@priorities = IssuePriority.all @priorities = IssuePriority.all
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, true) @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
end end
+6 -19
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -16,12 +16,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class 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
@@ -45,17 +44,6 @@ class JournalsController < ApplicationController
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
@@ -80,7 +68,6 @@ class JournalsController < ApplicationController
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?
@@ -101,9 +88,9 @@ class JournalsController < ApplicationController
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
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
+3 -6
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -23,10 +23,7 @@ class NewsController < ApplicationController
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
accept_rss_auth :index accept_key_auth :index
accept_api_auth :index
helper :watchers
def index def index
case params[:format] case params[:format]
-17
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class PreviewsController < ApplicationController class PreviewsController < ApplicationController
before_filter :find_project before_filter :find_project
+7 -7
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -24,8 +24,7 @@ class ProjectsController < ApplicationController
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?
@@ -143,7 +142,7 @@ class ProjectsController < ApplicationController
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
@@ -156,10 +155,11 @@ class ProjectsController < ApplicationController
: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|
+4 -7
View File
@@ -25,12 +25,11 @@ class QueriesController < ApplicationController
@query.project = params[:query_is_for_all] ? nil : @project @query.project = params[:query_is_for_all] ? nil : @project
@query.user = User.current @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?
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f]
@query.group_by ||= params[:group_by]
@query.column_names = params[:c] if params[:c]
@query.column_names = nil if params[:default_columns] @query.column_names = nil if params[:default_columns]
@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 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
@@ -42,12 +41,10 @@ class QueriesController < ApplicationController
def edit def edit
if request.post? if request.post?
@query.filters = {} @query.filters = {}
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f] @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?
@query.group_by ||= params[:group_by]
@query.column_names = params[:c] if params[:c]
@query.column_names = nil if params[:default_columns] @query.column_names = nil if params[:default_columns]
if @query.save if @query.save
+17 -56
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -30,34 +30,22 @@ class RepositoriesController < ApplicationController
before_filter :find_repository, :except => :edit before_filter :find_repository, :except => :edit
before_filter :find_project, :only => :edit before_filter :find_project, :only => :edit
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 edit def edit
@repository = @project.repository @repository = @project.repository
if !@repository && !params[:repository_scm].blank? if !@repository
@repository = Repository.factory(params[:repository_scm]) @repository = Repository.factory(params[:repository_scm])
@repository.project = @project if @repository @repository.project = @project if @repository
end end
if request.post? && @repository if request.post? && @repository
p1 = params[:repository] @repository.attributes = params[:repository]
p = {}
p_extra = {}
p1.each do |k, v|
if k =~ /^extra_/
p_extra[k] = v
else
p[k] = v
end
end
@repository.attributes = p
@repository.merge_extra_info(p_extra)
@repository.save @repository.save
end end
render(:update) do |page| render(:update) do |page|
page.replace_html "tab-content-repository", page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'
:partial => 'projects/settings/repository'
if @repository && !@project.repository if @repository && !@project.repository
@project.reload #needed to reload association @project.reload #needed to reload association
page.replace_html "main-menu", render_main_menu(@project) page.replace_html "main-menu", render_main_menu(@project)
@@ -82,17 +70,13 @@ class RepositoriesController < ApplicationController
def destroy def destroy
@repository.destroy @repository.destroy
redirect_to :controller => 'projects', redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
: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
@@ -110,7 +94,6 @@ class RepositoriesController < ApplicationController
(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
@@ -138,46 +121,25 @@ 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 'raw' == params[:format] || 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
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)
(render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty? (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
@changeset = @repository.find_changeset_by_name(@rev)
end end
def revision def revision
raise ChangesetNotFound if @rev.blank? raise ChangesetNotFound if @rev.nil? || @rev.empty?
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
raise ChangesetNotFound unless @changeset raise ChangesetNotFound unless @changeset
@@ -207,8 +169,8 @@ class RepositoriesController < ApplicationController
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
@@ -216,7 +178,6 @@ 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
@@ -276,15 +237,11 @@ class RepositoriesController < ApplicationController
@date_to = Date.today @date_to = Date.today
@date_from = @date_to << 11 @date_from = @date_to << 11
@date_from = Date.civil(@date_from.year, @date_from.month, 1) @date_from = Date.civil(@date_from.year, @date_from.month, 1)
commits_by_day = repository.changesets.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 => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
commits_by_month = [0] * 12 commits_by_month = [0] * 12
commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last } commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
changes_by_day = repository.changes.count( changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
:all, :group => :commit_date,
:conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
changes_by_month = [0] * 12 changes_by_month = [0] * 12
changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last } changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
@@ -345,16 +302,20 @@ 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 end
class Date class Date
+4 -4
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -24,8 +24,8 @@ 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]
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
+31 -83
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -18,12 +18,10 @@
class TimelogController < ApplicationController class TimelogController < ApplicationController
menu_item :issues menu_item :issues
before_filter :find_project, :only => [:new, :create] before_filter :find_project, :only => [:new, :create]
before_filter :find_time_entry, :only => [:show, :edit, :update] before_filter :find_time_entry, :only => [:show, :edit, :update, :destroy]
before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
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, :show, :create, :update, :destroy
accept_api_auth :index, :show, :create, :update, :destroy
helper :sort helper :sort
include SortHelper include SortHelper
@@ -42,42 +40,45 @@ class TimelogController < ApplicationController
'hours' => 'hours' 'hours' => 'hours'
cond = ARCondition.new cond = ARCondition.new
if @issue if @project.nil?
cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}" cond << Project.allowed_to_condition(User.current, :view_time_entries)
elsif @project elsif @issue.nil?
cond << @project.project_condition(Setting.display_subprojects_issues?) cond << @project.project_condition(Setting.display_subprojects_issues?)
else
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 retrieve_date_range
cond << ['spent_on BETWEEN ? AND ?', @from, @to] cond << ['spent_on BETWEEN ? AND ?', @from, @to]
TimeEntry.visible_by(User.current) do
respond_to do |format| respond_to do |format|
format.html { format.html {
# Paginate results # Paginate results
@entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions) @entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions)
@entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
@entries = TimeEntry.visible.find(:all, @entries = TimeEntry.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}], :include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions, :conditions => cond.conditions,
:order => sort_clause, :order => sort_clause,
:limit => @entry_pages.items_per_page, :limit => @entry_pages.items_per_page,
:offset => @entry_pages.current.offset) :offset => @entry_pages.current.offset)
@total_hours = TimeEntry.visible.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f @total_hours = 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 = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions) @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 = TimeEntry.visible.find(:all, @entries = TimeEntry.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}], :include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions, :conditions => cond.conditions,
:order => sort_clause, :order => sort_clause,
:limit => @limit, :limit => @entry_pages.items_per_page,
:offset => @offset) :offset => @entry_pages.current.offset)
} }
format.atom { format.atom {
entries = TimeEntry.visible.find(:all, entries = TimeEntry.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}], :include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions, :conditions => cond.conditions,
:order => "#{TimeEntry.table_name}.created_on DESC", :order => "#{TimeEntry.table_name}.created_on DESC",
@@ -86,7 +87,7 @@ class TimelogController < ApplicationController
} }
format.csv { format.csv {
# Export all entries # Export all entries
@entries = TimeEntry.visible.find(:all, @entries = TimeEntry.find(:all,
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:conditions => cond.conditions, :conditions => cond.conditions,
:order => sort_clause) :order => sort_clause)
@@ -94,6 +95,7 @@ class TimelogController < ApplicationController
} }
end end
end end
end
def show def show
respond_to do |format| respond_to do |format|
@@ -162,55 +164,27 @@ class TimelogController < ApplicationController
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.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
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed } verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
@time_entries.each do |t| if @time_entry.destroy && @time_entry.destroyed?
begin respond_to do |format|
unless t.destroy && t.destroyed? format.html {
flash[:notice] = l(:notice_successful_delete)
redirect_to :back
}
format.api { head :ok }
end
else
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:error] = l(:notice_unable_delete_time_entry) flash[:error] = l(:notice_unable_delete_time_entry)
redirect_to :back redirect_to :back
} }
format.api { render_validation_errors(t) } format.api { render_validation_errors(@time_entry) }
end end
return
end end
rescue ::ActionController::RedirectBackError rescue ::ActionController::RedirectBackError
redirect_to :action => 'index', :project_id => @projects.first redirect_to :action => 'index', :project_id => @time_entry.project
return
end
end
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_delete)
redirect_back_or_default(:action => 'index', :project_id => @projects.first)
}
format.api { head :ok }
end
end end
private private
@@ -225,26 +199,6 @@ private
render_404 render_404
end end
def find_time_entries
@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_project def find_project
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)
@@ -315,10 +269,4 @@ private
@to ||= (TimeEntry.latest_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
+7 -22
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -19,8 +19,8 @@ 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
@@ -38,9 +38,6 @@ class UsersController < ApplicationController
@limit = per_page_option @limit = per_page_option
end end
scope = User
scope = scope.in_group(params[:group_id].to_i) if params[:group_id].present?
@status = params[:status] ? params[:status].to_i : 1 @status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status]) c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
@@ -49,27 +46,24 @@ class UsersController < ApplicationController
c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name] c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
end end
@user_count = scope.count(:conditions => c.conditions) @user_count = User.count(:conditions => c.conditions)
@user_pages = Paginator.new self, @user_count, @limit, params['page'] @user_pages = Paginator.new self, @user_count, @limit, params['page']
@offset ||= @user_pages.current.offset @offset ||= @user_pages.current.offset
@users = scope.find :all, @users = User.find :all,
:order => sort_clause, :order => sort_clause,
:conditions => c.conditions, :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)
@@ -183,15 +177,6 @@ class UsersController < ApplicationController
redirect_to :controller => 'users', :action => 'edit', :id => @user redirect_to :controller => 'users', :action => 'edit', :id => @user
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy
@user.destroy
respond_to do |format|
format.html { redirect_to(users_url) }
format.api { head :ok }
end
end
def edit_membership 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 if request.post? @membership.save if request.post?
+13 -5
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -76,13 +76,21 @@ private
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 :back } 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.select(".#{c}").each do |item| page.replace_html replace_id, watcher_link(@watched, user, :replace => replace_ids)
page.replace_html item, watcher_link(@watched, user)
end end
end end
end end
+22 -29
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -34,27 +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
# 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
@@ -86,6 +82,7 @@ class WikiController < ApplicationController
# 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?
@page.content = WikiContent.new(:page => @page) if @page.new_record? @page.content = WikiContent.new(:page => @page) if @page.new_record?
@@ -101,6 +98,7 @@ class WikiController < ApplicationController
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed } 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?
@@ -146,7 +144,6 @@ class WikiController < ApplicationController
end end
end end
verify :method => :post, :only => :protect, :redirect_to => { :action => :show }
def protect def protect
@page.update_attribute :protected, params[:protected] @page.update_attribute :protected, params[:protected]
redirect_to :action => 'show', :project_id => @project, :id => @page.title redirect_to :action => 'show', :project_id => @project, :id => @page.title
@@ -217,6 +214,10 @@ class WikiController < ApplicationController
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
@@ -246,24 +247,10 @@ private
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
@@ -278,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
+3 -14
View File
@@ -32,17 +32,14 @@ class WorkflowsController < ApplicationController
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
@@ -51,14 +48,6 @@ class WorkflowsController < ApplicationController
@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
+50 -80
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -110,20 +110,6 @@ module ApplicationHelper
:title => l(:label_revision_id, format_revision(revision))) :title => l(:label_revision_id, format_revision(revision)))
end end
# Generates a link to a message
def link_to_message(message, options={}, html_options = nil)
link_to(
h(truncate(message.subject, :length => 60)),
{ :controller => 'messages', :action => 'show',
:board_id => message.board_id,
:id => message.root,
:r => (message.parent_id && message.id),
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
}.merge(options),
html_options
)
end
# Generates a link to a project if active # Generates a link to a project if active
# Examples: # Examples:
# #
@@ -187,15 +173,15 @@ module ApplicationHelper
end end
end end
def render_page_hierarchy(pages, node=nil, options={}) def render_page_hierarchy(pages, node=nil)
content = '' content = ''
if pages[node] if pages[node]
content << "<ul class=\"pages-hierarchy\">\n" content << "<ul class=\"pages-hierarchy\">\n"
pages[node].each do |page| pages[node].each do |page|
content << "<li>" content << "<li>"
content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}, content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
:title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
content << "</li>\n" content << "</li>\n"
end end
content << "</ul>\n" content << "</ul>\n"
@@ -223,8 +209,8 @@ module ApplicationHelper
# Renders the project quick-jump box # Renders the project quick-jump box
def render_project_jump_box def render_project_jump_box
return unless User.current.logged? # Retrieve them now to avoid a COUNT query
projects = User.current.memberships.collect(&:project).compact.uniq projects = User.current.projects.all
if projects.any? if projects.any?
s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' + s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
"<option value=''>#{ l(:label_jump_to_a_project) }</option>" + "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
@@ -336,18 +322,20 @@ module ApplicationHelper
page_param = options.delete(:page_param) || :page page_param = options.delete(:page_param) || :page
per_page_links = options.delete(:per_page_links) per_page_links = options.delete(:per_page_links)
url_param = params.dup url_param = params.dup
# don't reuse query params if filters are present
url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
html = '' html = ''
if paginator.current.previous if paginator.current.previous
html << link_to_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' ' html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
end end
html << (pagination_links_each(paginator, options) do |n| html << (pagination_links_each(paginator, options) do |n|
link_to_content_update(n.to_s, url_param.merge(page_param => n)) link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
end || '') end || '')
if paginator.current.next if paginator.current.next
html << ' ' + link_to_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next)) html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
end end
unless count.nil? unless count.nil?
@@ -361,8 +349,14 @@ module ApplicationHelper
end end
def per_page_links(selected=nil) def per_page_links(selected=nil)
url_param = params.dup
url_param.clear if url_param.has_key?(:set_filter)
links = Setting.per_page_options_array.collect do |n| links = Setting.per_page_options_array.collect do |n|
n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) n == selected ? n : link_to_remote(n, {:update => "content",
:url => params.dup.merge(:per_page => n),
:method => :get},
{:href => url_for(url_param.merge(:per_page => n))})
end end
links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
end end
@@ -390,7 +384,7 @@ module ApplicationHelper
h(Setting.app_title) h(Setting.app_title)
else else
b = [] b = []
ancestors = (@project.root? ? [] : @project.ancestors.visible.all) ancestors = (@project.root? ? [] : @project.ancestors.visible)
if ancestors.any? if ancestors.any?
root = ancestors.shift root = ancestors.shift
b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
@@ -550,20 +544,15 @@ module ApplicationHelper
if page =~ /^(.+?)\#(.+)$/ if page =~ /^(.+?)\#(.+)$/
page, anchor = $1, $2 page, anchor = $1, $2
end end
anchor = sanitize_anchor_name(anchor) if anchor.present?
# check if page exists # check if page exists
wiki_page = link_project.wiki.find_page(page) wiki_page = link_project.wiki.find_page(page)
url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page url = case options[:wiki_links]
"##{anchor}" when :local; "#{title}.html"
else
case options[:wiki_links]
when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
when :anchor; "##{title}" # used for single-file wiki export when :anchor; "##{title}" # used for single-file wiki export
else else
wiki_page_id = page.present? ? Wiki.titleize(page) : nil wiki_page_id = page.present? ? Wiki.titleize(page) : nil
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor) url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
end end
end
link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
else else
# project or wiki doesn't exist # project or wiki doesn't exist
@@ -601,24 +590,14 @@ module ApplicationHelper
# export:some/file -> Force the download of the file # export:some/file -> Force the download of the file
# Forum messages: # Forum messages:
# message#1218 -> Link to message with id 1218 # message#1218 -> Link to message with id 1218
#
# Links can refer other objects from other projects, using project identifier:
# identifier:r52
# identifier:document:"Some document"
# identifier:version:1.0.0
# identifier:source:some/file
def parse_redmine_links(text, project, obj, attr, only_path, options) def parse_redmine_links(text, project, obj, attr, only_path, options)
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m| text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
link = nil link = nil
if project_identifier
project = Project.visible.find_by_identifier(project_identifier)
end
if esc.nil? if esc.nil?
if prefix.nil? && sep == 'r' if prefix.nil? && sep == 'r'
# project.changesets.visible raises an SQL error because of a double join on repositories if project && (changeset = project.changesets.find_by_revision(identifier))
if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier)) link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
:class => 'changeset', :class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100)) :title => truncate_single_line(changeset.comments, :length => 100))
end end
@@ -632,18 +611,24 @@ module ApplicationHelper
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
end end
when 'document' when 'document'
if document = Document.visible.find_by_id(oid) if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
:class => 'document' :class => 'document'
end end
when 'version' when 'version'
if version = Version.visible.find_by_id(oid) if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
:class => 'version' :class => 'version'
end end
when 'message' when 'message'
if message = Message.visible.find_by_id(oid, :include => :parent) if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
link = link_to_message(message, {:only_path => only_path}, :class => 'message') link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
:controller => 'messages',
:action => 'show',
:board_id => message.board,
:id => message.root,
:anchor => (message.parent ? "message-#{message.id}" : nil)},
:class => 'message'
end end
when 'project' when 'project'
if p = Project.visible.find_by_id(oid) if p = Project.visible.find_by_id(oid)
@@ -655,26 +640,26 @@ module ApplicationHelper
name = identifier.gsub(%r{^"(.*)"$}, "\\1") name = identifier.gsub(%r{^"(.*)"$}, "\\1")
case prefix case prefix
when 'document' when 'document'
if project && document = project.documents.visible.find_by_title(name) if project && document = project.documents.find_by_title(name)
link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
:class => 'document' :class => 'document'
end end
when 'version' when 'version'
if project && version = project.versions.visible.find_by_name(name) if project && version = project.versions.find_by_name(name)
link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
:class => 'version' :class => 'version'
end end
when 'commit' when 'commit'
if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
:class => 'changeset', :class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100) :title => truncate_single_line(changeset.comments, :length => 100)
end end
when 'source', 'export' when 'source', 'export'
if project && project.repository && User.current.allowed_to?(:browse_repository, project) if project && project.repository
name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
path, rev, anchor = $1, $3, $5 path, rev, anchor = $1, $3, $5
link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
:path => to_path_param(path), :path => to_path_param(path),
:rev => rev, :rev => rev,
:anchor => anchor, :anchor => anchor,
@@ -694,7 +679,7 @@ module ApplicationHelper
end end
end end
end end
leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}") leading + (link || "#{prefix}#{sep}#{identifier}")
end end
end end
@@ -708,9 +693,9 @@ module ApplicationHelper
text.gsub!(HEADING_RE) do text.gsub!(HEADING_RE) do
level, attrs, content = $1.to_i, $2, $3 level, attrs, content = $1.to_i, $2, $3
item = strip_tags(content).strip item = strip_tags(content).strip
anchor = sanitize_anchor_name(item) anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
@parsed_headings << [level, anchor, item] @parsed_headings << [level, anchor, item]
"<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>" "<h#{level} #{attrs} id=\"#{anchor}\">#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
end end
end end
@@ -852,8 +837,6 @@ module ApplicationHelper
'Calendar._FD = 1;' # Monday 'Calendar._FD = 1;' # Monday
when 7 when 7
'Calendar._FD = 0;' # Sunday 'Calendar._FD = 0;' # Sunday
when 6
'Calendar._FD = 6;' # Saturday
else else
'' # use language '' # use language
end end
@@ -894,27 +877,10 @@ module ApplicationHelper
end end
end end
def sanitize_anchor_name(anchor)
anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
end
# Returns the javascript tags that are included in the html layout head
def javascript_heads
tags = javascript_include_tag(:defaults)
unless User.current.pref.warn_on_leaving_unsaved == '0'
tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
end
tags
end
def favicon def favicon
"<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />" "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
end end
def robot_exclusion_tag
'<meta name="robots" content="noindex,follow,noarchive" />'
end
# Returns true if arg is expected in the API response # Returns true if arg is expected in the API response
def include_in_api_response?(arg) def include_in_api_response?(arg)
unless @included_in_api_response unless @included_in_api_response
@@ -945,7 +911,11 @@ module ApplicationHelper
return self return self
end end
def link_to_content_update(text, url_params = {}, html_options = {}) def link_to_remote_content_update(text, url_params)
link_to(text, url_params, html_options) link_to_remote(text,
{:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
{:href => url_for(:params => url_params)}
)
end end
end end
+2 -14
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -29,18 +29,6 @@ module AttachmentsHelper
end end
def to_utf8(str) def to_utf8(str)
if str.respond_to?(:force_encoding)
str.force_encoding('UTF-8')
return str if str.valid_encoding?
else
return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
end
begin
Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
rescue Iconv::InvalidEncoding
# "UTF-8//IGNORE" is not supported on some OS
str str
end end
end end
end
+9 -1
View File
@@ -32,6 +32,14 @@ module CalendarsHelper
end end
def link_to_month(link_name, year, month, options={}) def link_to_month(link_name, year, month, options={})
link_to_content_update(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
+5 -5
View File
@@ -49,7 +49,7 @@ module CustomFieldsHelper
blank_option = custom_field.is_required? ? blank_option = custom_field.is_required? ?
(custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') : (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
'<option></option>' '<option></option>'
select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id) select_tag(field_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id)
else else
text_field_tag(field_name, custom_value.value, :id => field_id) text_field_tag(field_name, custom_value.value, :id => field_id)
end end
@@ -68,7 +68,7 @@ module CustomFieldsHelper
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_id = "#{name}_custom_field_values_#{custom_field.id}" field_id = "#{name}_custom_field_values_#{custom_field.id}"
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
@@ -83,7 +83,7 @@ module CustomFieldsHelper
[l(:general_text_yes), '1'], [l(:general_text_yes), '1'],
[l(:general_text_no), '0']]), :id => field_id) [l(:general_text_no), '0']]), :id => field_id)
when "list" when "list"
select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values_options(projects)), :id => field_id) select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values), :id => field_id)
else else
text_field_tag(field_name, '', :id => field_id) text_field_tag(field_name, '', :id => field_id)
end end
@@ -101,8 +101,8 @@ module CustomFieldsHelper
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
+16 -8
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -21,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') 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') 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
+8 -68
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -55,29 +55,19 @@ module IssuesHelper
"<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}" "<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?
subject = content_tag('span', l(:field_is_private), :class => 'private') + ' ' + subject
end
s << content_tag('h3', subject)
s << '</div>' * (ancestors.size + 1)
s s
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') +
@@ -109,24 +99,6 @@ module IssuesHelper
s 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
# User can see public queries and his own queries # User can see public queries and his own queries
@@ -134,32 +106,13 @@ module IssuesHelper
# Project specific queries and global queries # Project specific queries and global queries
visible << (@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, @sidebar_queries = Query.find(:all,
:select => 'id, name, is_public', :select => 'id, name',
:order => "name ASC", :order => "name ASC",
:conditions => visible.conditions) :conditions => visible.conditions)
end end
@sidebar_queries @sidebar_queries
end end
def query_links(title, queries)
# links to #index on issues/show
url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
content_tag('h3', title) +
queries.collect {|query|
link_to(h(query.name), url_params.merge(:query_id => query))
}.join('<br />')
end
def render_sidebar_queries
out = ''
queries = sidebar_queries.select {|q| !q.is_public?}
out << query_links(l(:label_my_queries), queries) if queries.any?
queries = sidebar_queries.select {|q| q.is_public?}
out << query_links(l(:label_query_plural), queries) if queries.any?
out
end
def show_detail(detail, no_html=false) def show_detail(detail, no_html=false)
case detail.property case detail.property
when 'attr' when 'attr'
@@ -182,10 +135,6 @@ module IssuesHelper
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 detail.prop_key == '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)
@@ -215,16 +164,7 @@ module IssuesHelper
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},
:title => l(:label_view_diff)
s << " (#{ diff_link })"
end
s
elsif !detail.value.blank?
case detail.property case detail.property
when 'attr', 'cf' when 'attr', 'cf'
if !detail.old_value.blank? if !detail.old_value.blank?
+10
View File
@@ -16,4 +16,14 @@
# 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
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
+5 -6
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -70,7 +70,6 @@ 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
@@ -79,16 +78,16 @@ module QueriesHelper
# Give it a name, required to be valid # Give it a name, required to be valid
@query = Query.new(:name => "_") @query = Query.new(:name => "_")
@query.project = @project @query.project = @project
if params[:fields] || params[:f] if params[:fields]
@query.filters = {} @query.filters = {}
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) @query.add_filters(params[:fields], params[:operators], params[:values])
else else
@query.available_filters.keys.each do |field| @query.available_filters.keys.each do |field|
@query.add_short_filter(field, params[field]) if params[field] @query.add_short_filter(field, params[field]) if params[field]
end end
end end
@query.group_by = params[:group_by] @query.group_by = params[:group_by]
@query.column_names = params[:c] || (params[:query] && params[:query][:column_names]) @query.column_names = params[:query] && params[:query][:column_names]
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
else else
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id] @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
+19 -111
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -16,7 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv' require 'iconv'
require 'redmine/codeset_util'
module RepositoriesHelper module RepositoriesHelper
def format_revision(revision) def format_revision(revision)
@@ -49,8 +48,7 @@ module RepositoriesHelper
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.changes.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
end end
change change
when 'D' when 'D'
@@ -74,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|
@@ -117,24 +117,7 @@ module RepositoriesHelper
end end
def to_utf8(str) def to_utf8(str)
return str if str.nil?
str = to_utf8_internal(str)
if str.respond_to?(:force_encoding)
str.force_encoding('UTF-8')
end
str
end
def to_utf8_internal(str)
return str if str.nil?
if str.respond_to?(:force_encoding)
str.force_encoding('ASCII-8BIT')
end
return str if str.empty?
return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
if str.respond_to?(:force_encoding)
str.force_encoding('UTF-8')
end
@encodings ||= Setting.repositories_encodings.split(',').collect(&:strip) @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
@encodings.each do |encoding| @encodings.each do |encoding|
begin begin
@@ -143,37 +126,24 @@ module RepositoriesHelper
# do nothing here and try the next encoding # do nothing here and try the next encoding
end end
end end
str = Redmine::CodesetUtil.replace_invalid_utf8(str) str
end end
private :to_utf8_internal
def repository_field_tags(form, repository) def repository_field_tags(form, repository)
method = repository.class.name.demodulize.underscore + "_field_tags" method = repository.class.name.demodulize.underscore + "_field_tags"
if repository.is_a?(Repository) && send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method) && method != 'repository_field_tags'
respond_to?(method) && method != 'repository_field_tags'
send(method, form, repository)
end
end 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 => {
:controller => 'repositories',
:action => 'edit',
:id => @project
},
:method => :get,
:with => "Form.serialize(this.form)")
) )
end end
@@ -186,99 +156,37 @@ module RepositoriesHelper
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 />(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 />' + l(:text_mercurial_repository_note)) +
content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
) +
'<br />' + 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 />' + l(:text_git_repository_note)) +
content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
) +
'<br />' + 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 />' + 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 />' + l(:text_scm_path_encoding_note))
end end
end end
+3 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -57,7 +57,7 @@ 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(text, :q => params[:q], :titles_only => params[:titles_only], :all_words => params[:all_words], :scope => params[:scope], t => 1) links << link_to(text, :q => params[:q], :titles_only => params[:title_only], :all_words => params[:all_words], :scope => params[:scope], t => 1)
end end
('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty? ('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
end end
+4 -11
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -33,9 +33,7 @@ module SettingsHelper
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) + 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)
end end
def setting_multiselect(setting, choices, options={}) def setting_multiselect(setting, choices, options={})
@@ -46,13 +44,8 @@ module SettingsHelper
hidden_field_tag("settings[#{setting}][]", '') + 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 end.join
+6 -2
View File
@@ -200,12 +200,16 @@ module SortHelper
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(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
+5 -4
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -17,10 +17,11 @@
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
+16 -11
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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,17 +17,27 @@
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),
@@ -35,11 +45,6 @@ module WatchersHelper
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)
+40 -6
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -18,10 +18,8 @@
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 = ''
if pages.has_key?(parent) pages.select {|p| p.parent == parent}.each do |page|
pages[parent].each do |page|
attrs = "value='#{page.id}'" attrs = "value='#{page.id}'"
attrs << " selected='selected'" if selected == page attrs << " selected='selected'" if selected == page
indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : nil indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : nil
@@ -29,7 +27,43 @@ module WikiHelper
s << "<option #{attrs}>#{indent}#{h page.pretty_title}</option>\n" + s << "<option #{attrs}>#{indent}#{h page.pretty_title}</option>\n" +
wiki_page_options_for_select(pages, selected, page, level + 1) wiki_page_options_for_select(pages, selected, page, level + 1)
end end
end
s s
end end
def html_diff(wdiff)
words = wdiff.words.collect{|word| h(word)}
words_add = 0
words_del = 0
dels = 0
del_off = 0
wdiff.diff.diffs.each do |diff|
add_at = nil
add_to = nil
del_at = nil
deleted = ""
diff.each do |change|
pos = change[1]
if change[0] == "+"
add_at = pos + dels unless add_at
add_to = pos + dels
words_add += 1
else
del_at = pos unless del_at
deleted << ' ' + h(change[2])
words_del += 1
end
end
if add_at
words[add_at] = '<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
+3 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -43,7 +43,7 @@ class Attachment < ActiveRecord::Base
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"} "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
cattr_accessor :storage_path cattr_accessor :storage_path
@@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{RAILS_ROOT}/files" @@storage_path = "#{RAILS_ROOT}/files"
def validate def validate
if self.filesize > Setting.attachment_max_size.to_i.kilobytes if self.filesize > Setting.attachment_max_size.to_i.kilobytes
-10
View File
@@ -16,8 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AuthSource < ActiveRecord::Base class AuthSource < ActiveRecord::Base
include Redmine::Ciphering
has_many :users has_many :users
validates_presence_of :name validates_presence_of :name
@@ -34,14 +32,6 @@ class AuthSource < ActiveRecord::Base
"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
+2 -2
View File
@@ -20,8 +20,8 @@ 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, :maximum => 255, :allow_nil => 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
+2 -7
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -25,11 +25,6 @@ class Change < ActiveRecord::Base
changeset.repository.relative_path(path) changeset.repository.relative_path(path)
end end
def before_validation
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
+34 -58
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -42,7 +42,7 @@ class Changeset < ActiveRecord::Base
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
named_scope :visible, lambda {|*args| { :include => {:repository => :project}, named_scope :visible, lambda {|*args| { :include => {:repository => :project},
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } } :conditions => Project.allowed_to_condition(args.first || User.current, :view_changesets) } }
def revision=(r) def revision=(r)
write_attribute :revision, (r.nil? ? nil : r.to_s) write_attribute :revision, (r.nil? ? nil : r.to_s)
@@ -57,6 +57,10 @@ class Changeset < ActiveRecord::Base
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
super super
@@ -71,6 +75,10 @@ class Changeset < ActiveRecord::Base
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
@@ -80,10 +88,7 @@ class Changeset < ActiveRecord::Base
end end
def before_create 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 after_create def after_create
@@ -150,18 +155,17 @@ class Changeset < ActiveRecord::Base
# 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
@@ -181,9 +185,7 @@ class Changeset < ActiveRecord::Base
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 issue if issue
unless issue.project && unless issue.project && (project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(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
@@ -193,7 +195,7 @@ class Changeset < ActiveRecord::Base
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
@@ -221,8 +223,7 @@ class Changeset < ActiveRecord::Base
: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, :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?
@@ -245,47 +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)
end
def self.to_utf8(str, encoding)
return str if str.nil?
str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
if str.empty?
str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
return str
end
enc = encoding.blank? ? "UTF-8" : encoding
if str.respond_to?(:force_encoding)
if enc.upcase != "UTF-8"
str.force_encoding(enc)
str = str.encode("UTF-8", :invalid => :replace,
:undef => :replace, :replace => '?')
else
str.force_encoding("UTF-8")
if ! str.valid_encoding?
str = str.encode("US-ASCII", :invalid => :replace,
:undef => :replace, :replace => '?').encode("UTF-8")
end
end
else
ic = Iconv.new('UTF-8', enc)
txtar = ""
begin begin
txtar += ic.iconv(str) str = Iconv.conv('UTF-8', encoding, str)
rescue Iconv::IllegalSequence rescue Iconv::Failure
txtar += $!.success # do nothing here
str = '?' + $!.failed[1,$!.failed.length]
retry
rescue
txtar += $!.success
end end
str = txtar
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 str
end end
end end
end
-24
View File
@@ -1,24 +0,0 @@
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CommentObserver < ActiveRecord::Observer
def after_create(comment)
if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added')
Mailer.deliver_news_comment_added(comment)
end
end
end
+3 -41
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -23,6 +23,7 @@ class CustomField < ActiveRecord::Base
validates_presence_of :name, :field_format validates_presence_of :name, :field_format
validates_uniqueness_of :name, :scope => :type validates_uniqueness_of :name, :scope => :type
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\.\'\-]*$/i
validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
def initialize(attributes = nil) def initialize(attributes = nil)
@@ -42,49 +43,12 @@ class CustomField < ActiveRecord::Base
errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
end end
if regexp.present?
begin
Regexp.new(regexp)
rescue
errors.add(:regexp, :invalid)
end
end
# validate default value # validate default value
v = CustomValue.new(:custom_field => self.clone, :value => default_value, :customized => nil) v = CustomValue.new(:custom_field => self.clone, :value => default_value, :customized => nil)
v.custom_field.is_required = false v.custom_field.is_required = false
errors.add(:default_value, :invalid) unless v.valid? errors.add(:default_value, :invalid) unless v.valid?
end end
def possible_values_options(obj=nil)
case field_format
when 'user', 'version'
if obj.respond_to?(:project) && obj.project
case field_format
when 'user'
obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
when 'version'
obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
end
elsif obj.is_a?(Array)
obj.collect {|o| possible_values_options(o)}.inject {|memo, v| memo & v}
else
[]
end
else
read_attribute :possible_values
end
end
def possible_values(obj=nil)
case field_format
when 'user', 'version'
possible_values_options(obj).collect(&:last)
else
read_attribute :possible_values
end
end
# Makes possible_values accept a multiline string # Makes possible_values accept a multiline string
def possible_values=(arg) def possible_values=(arg)
if arg.is_a?(Array) if arg.is_a?(Array)
@@ -108,8 +72,6 @@ class CustomField < ActiveRecord::Base
casted = value.to_i casted = value.to_i
when 'float' when 'float'
casted = value.to_f casted = value.to_f
when 'user', 'version'
casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
end end
end end
casted casted
+2 -5
View File
@@ -1,5 +1,5 @@
# RedMine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -29,9 +29,6 @@ class Document < ActiveRecord::Base
validates_presence_of :project, :title, :category validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60 validates_length_of :title, :maximum => 60
named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_documents, *args) } }
def visible?(user=User.current) def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_documents, project) !user.nil? && user.allowed_to?(:view_documents, project)
end end
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
+3 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -20,3 +20,4 @@ class DocumentCategoryCustomField < CustomField
:enumeration_doc_categories :enumeration_doc_categories
end end
end end
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
-1
View File
@@ -32,7 +32,6 @@ class Enumeration < ActiveRecord::Base
named_scope :shared, :conditions => { :project_id => nil } named_scope :shared, :conditions => { :project_id => nil }
named_scope :active, :conditions => { :active => true } named_scope :active, :conditions => { :active => true }
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
def self.default def self.default
# Creates a fake default scope so Enumeration.default will check # Creates a fake default scope so Enumeration.default will check
+30 -88
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -60,7 +60,7 @@ class Issue < ActiveRecord::Base
validates_numericality_of :estimated_hours, :allow_nil => true validates_numericality_of :estimated_hours, :allow_nil => true
named_scope :visible, lambda {|*args| { :include => :project, named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Issue.visible_condition(args.shift || User.current, *args) } } :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
@@ -68,6 +68,11 @@ class Issue < ActiveRecord::Base
named_scope :with_limit, lambda { |limit| { :limit => limit} } named_scope :with_limit, lambda { |limit| { :limit => limit} }
named_scope :on_active_project, :include => [:status, :project, :tracker], named_scope :on_active_project, :include => [:status, :project, :tracker],
:conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"] :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
named_scope :for_gantt, lambda {
{
:include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version]
}
}
named_scope :without_version, lambda { named_scope :without_version, lambda {
{ {
@@ -86,36 +91,9 @@ class Issue < ActiveRecord::Base
after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
after_destroy :update_parent_attributes after_destroy :update_parent_attributes
# Returns a SQL conditions string used to find all issues visible by the specified user
def self.visible_condition(user, options={})
Project.allowed_to_condition(user, :view_issues, options) do |role, user|
case role.issues_visibility
when 'all'
nil
when 'default'
"(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})"
when 'own'
"(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})"
else
'1=0'
end
end
end
# Returns true if usr or current user is allowed to view the issue # Returns true if usr or current user is allowed to view the issue
def visible?(usr=nil) def visible?(usr=nil)
(usr || User.current).allowed_to?(:view_issues, self.project) do |role, user| (usr || User.current).allowed_to?(:view_issues, self.project)
case role.issues_visibility
when 'all'
true
when 'default'
!self.is_private? || self.author == user || self.assigned_to == user
when 'own'
self.author == user || self.assigned_to == user
else
false
end
end
end end
def after_initialize def after_initialize
@@ -128,7 +106,7 @@ class Issue < ActiveRecord::Base
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
def available_custom_fields def available_custom_fields
(project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : [] (project && tracker) ? project.all_issue_custom_fields.select {|c| tracker.custom_fields.include? c } : []
end end
def copy_from(arg) def copy_from(arg)
@@ -175,7 +153,6 @@ class Issue < ActiveRecord::Base
issue.reset_custom_values! issue.reset_custom_values!
end end
if options[:copy] if options[:copy]
issue.author = User.current
issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
issue.status = if options[:attributes] && options[:attributes][:status_id] issue.status = if options[:attributes] && options[:attributes][:status_id]
IssueStatus.find_by_id(options[:attributes][:status_id]) IssueStatus.find_by_id(options[:attributes][:status_id])
@@ -188,13 +165,7 @@ class Issue < ActiveRecord::Base
issue.attributes = options[:attributes] issue.attributes = options[:attributes]
end end
if issue.save if issue.save
if options[:copy] unless options[:copy]
if current_journal && current_journal.notes.present?
issue.init_journal(current_journal.user, current_journal.notes)
issue.current_journal.notify = false
issue.save
end
else
# Manually update project_id on related time entries # Manually update project_id on related time entries
TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id}) TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
@@ -228,13 +199,6 @@ class Issue < ActiveRecord::Base
result result
end end
def description=(arg)
if arg.is_a?(String)
arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
end
write_attribute(:description, arg)
end
# Overrides attributes= so that tracker_id gets assigned first # Overrides attributes= so that tracker_id gets assigned first
def attributes_with_tracker_first=(new_attributes, *args) def attributes_with_tracker_first=(new_attributes, *args)
return if new_attributes.nil? return if new_attributes.nil?
@@ -275,12 +239,6 @@ class Issue < ActiveRecord::Base
'done_ratio', 'done_ratio',
:if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? } :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
safe_attributes 'is_private',
:if => lambda {|issue, user|
user.allowed_to?(:set_issues_private, issue.project) ||
(issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
}
# Safely sets attributes # Safely sets attributes
# Should be called from controllers instead of #attributes= # Should be called from controllers instead of #attributes=
# attr_accessible is too rough because we still want things like # attr_accessible is too rough because we still want things like
@@ -449,7 +407,6 @@ class Issue < ActiveRecord::Base
def assignable_users def assignable_users
users = project.assignable_users users = project.assignable_users
users << author if author users << author if author
users << assigned_to if assigned_to
users.uniq.sort users.uniq.sort
end end
@@ -465,12 +422,7 @@ class Issue < ActiveRecord::Base
# Returns an array of status that user is able to apply # Returns an array of status that user is able to apply
def new_statuses_allowed_to(user, include_default=false) def new_statuses_allowed_to(user, include_default=false)
statuses = status.find_new_statuses_allowed_to( statuses = status.find_new_statuses_allowed_to(user.roles_for_project(project), tracker)
user.roles_for_project(project),
tracker,
author == user,
assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
)
statuses << status unless statuses.empty? statuses << status unless statuses.empty?
statuses << IssueStatus.default if include_default statuses << IssueStatus.default if include_default
statuses = statuses.uniq.sort statuses = statuses.uniq.sort
@@ -503,14 +455,11 @@ class Issue < ActiveRecord::Base
(relations_from + relations_to).sort (relations_from + relations_to).sort
end end
def all_dependent_issues(except=[]) def all_dependent_issues
except << self
dependencies = [] dependencies = []
relations_from.each do |relation| relations_from.each do |relation|
if relation.issue_to && !except.include?(relation.issue_to)
dependencies << relation.issue_to dependencies << relation.issue_to
dependencies += relation.issue_to.all_dependent_issues(except) dependencies += relation.issue_to.all_dependent_issues
end
end end
dependencies dependencies
end end
@@ -575,9 +524,6 @@ class Issue < ActiveRecord::Base
s = "issue status-#{status.position} priority-#{priority.position}" s = "issue status-#{status.position} priority-#{priority.position}"
s << ' closed' if closed? s << ' closed' if closed?
s << ' overdue' if overdue? s << ' overdue' if overdue?
s << ' child' if child?
s << ' parent' unless leaf?
s << ' private' if is_private?
s << ' created-by-me' if User.current.logged? && author_id == User.current.id s << ' created-by-me' if User.current.logged? && author_id == User.current.id
s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
s s
@@ -587,7 +533,7 @@ class Issue < ActiveRecord::Base
# Returns false if save fails # Returns false if save fails
def save_issue_with_child_records(params, existing_time_entry=nil) def save_issue_with_child_records(params, existing_time_entry=nil)
Issue.transaction do Issue.transaction do
if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project) if params[:time_entry] && params[:time_entry][:hours].present? && User.current.allowed_to?(:log_time, project)
@time_entry = existing_time_entry || TimeEntry.new @time_entry = existing_time_entry || TimeEntry.new
@time_entry.project = project @time_entry.project = project
@time_entry.issue = self @time_entry.issue = self
@@ -691,16 +637,14 @@ class Issue < ActiveRecord::Base
def self.by_subproject(project) def self.by_subproject(project)
ActiveRecord::Base.connection.select_all("select s.id as status_id, ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed, s.is_closed as closed,
#{Issue.table_name}.project_id as project_id, i.project_id as project_id,
count(#{Issue.table_name}.id) as total count(i.id) as total
from from
#{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s #{Issue.table_name} i, #{IssueStatus.table_name} s
where where
#{Issue.table_name}.status_id=s.id i.status_id=s.id
and #{Issue.table_name}.project_id = #{Project.table_name}.id and i.project_id IN (#{project.descendants.active.collect{|p| p.id}.join(',')})
and #{visible_condition(User.current, :project => project, :with_subprojects => true)} group by s.id, s.is_closed, i.project_id") if project.descendants.active.any?
and #{Issue.table_name}.project_id <> #{project.id}
group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
end end
# End ReportsController extraction # End ReportsController extraction
@@ -877,14 +821,11 @@ class Issue < ActiveRecord::Base
def create_journal def create_journal
if @current_journal if @current_journal
# attributes changes # attributes changes
(Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c| (Issue.column_names - %w(id description root_id lft rgt lock_version created_on updated_on)).each {|c|
before = @issue_before_change.send(c)
after = send(c)
next if before == after || (before.blank? && after.blank?)
@current_journal.details << JournalDetail.new(:property => 'attr', @current_journal.details << JournalDetail.new(:property => 'attr',
:prop_key => c, :prop_key => c,
:old_value => @issue_before_change.send(c), :old_value => @issue_before_change.send(c),
:value => send(c)) :value => send(c)) unless send(c)==@issue_before_change.send(c)
} }
# custom fields changes # custom fields changes
custom_values.each {|c| custom_values.each {|c|
@@ -913,19 +854,20 @@ class Issue < ActiveRecord::Base
select_field = options.delete(:field) select_field = options.delete(:field)
joins = options.delete(:joins) joins = options.delete(:joins)
where = "#{Issue.table_name}.#{select_field}=j.id" where = "i.#{select_field}=j.id"
ActiveRecord::Base.connection.select_all("select s.id as status_id, ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed, s.is_closed as closed,
j.id as #{select_field}, j.id as #{select_field},
count(#{Issue.table_name}.id) as total count(i.id) as total
from from
#{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j #{Issue.table_name} i, #{IssueStatus.table_name} s, #{joins} j
where where
#{Issue.table_name}.status_id=s.id i.status_id=s.id
and #{where} and #{where}
and #{Issue.table_name}.project_id=#{Project.table_name}.id and i.project_id=#{project.id}
and #{visible_condition(User.current, :project => project)}
group by s.id, s.is_closed, j.id") group by s.id, s.is_closed, j.id")
end end
end end
+2 -4
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -24,8 +24,6 @@ class IssueCategory < ActiveRecord::Base
validates_uniqueness_of :name, :scope => [:project_id] validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
alias :destroy_without_reassign :destroy alias :destroy_without_reassign :destroy
# Destroy the category # Destroy the category
-5
View File
@@ -47,12 +47,7 @@ class IssueRelation < ActiveRecord::Base
if issue_from && issue_to if issue_from && issue_to
errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations? errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations?
#detect circular dependencies depending wether the relation should be reversed
if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
errors.add_to_base :circular_dependency if issue_from.all_dependent_issues.include? issue_to
else
errors.add_to_base :circular_dependency if issue_to.all_dependent_issues.include? issue_from errors.add_to_base :circular_dependency if issue_to.all_dependent_issues.include? issue_from
end
errors.add_to_base :cant_link_an_issue_with_a_descendant if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to) errors.add_to_base :cant_link_an_issue_with_a_descendant if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
end end
end end
+15 -19
View File
@@ -25,10 +25,9 @@ class IssueStatus < ActiveRecord::Base
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
def after_save def after_save
IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default? IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default?
end end
@@ -52,15 +51,10 @@ class IssueStatus < ActiveRecord::Base
# Returns an array of all statuses the given role can switch to # Returns an array of all statuses the given role can switch to
# Uses association cache when called more than one time # Uses association cache when called more than one time
def new_statuses_allowed_to(roles, tracker, author=false, assignee=false) def new_statuses_allowed_to(roles, tracker)
if roles && tracker if roles && tracker
role_ids = roles.collect(&:id) role_ids = roles.collect(&:id)
transitions = workflows.select do |w| new_statuses = workflows.select {|w| role_ids.include?(w.role_id) && w.tracker_id == tracker.id}.collect{|w| w.new_status}.compact.sort
role_ids.include?(w.role_id) &&
w.tracker_id == tracker.id &&
((!w.author && !w.assignee) || (author && w.author) || (assignee && w.assignee))
end
transitions.collect{|w| w.new_status}.compact.sort
else else
[] []
end end
@@ -68,23 +62,25 @@ class IssueStatus < ActiveRecord::Base
# Same thing as above but uses a database query # Same thing as above but uses a database query
# More efficient than the previous method if called just once # More efficient than the previous method if called just once
def find_new_statuses_allowed_to(roles, tracker, author=false, assignee=false) def find_new_statuses_allowed_to(roles, tracker)
if roles.present? && tracker if roles && tracker
conditions = "(author = :false AND assignee = :false)"
conditions << " OR author = :true" if author
conditions << " OR assignee = :true" if assignee
workflows.find(:all, workflows.find(:all,
:include => :new_status, :include => :new_status,
:conditions => ["role_id IN (:role_ids) AND tracker_id = :tracker_id AND (#{conditions})", :conditions => { :role_id => roles.collect(&:id),
{:role_ids => roles.collect(&:id), :tracker_id => tracker.id, :true => true, :false => false} :tracker_id => tracker.id}).collect{ |w| w.new_status }.compact.sort
]
).collect{|w| w.new_status}.compact.sort
else else
[] []
end end
end end
def new_status_allowed_to?(status, roles, tracker)
if status && roles && tracker
!workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => roles.collect(&:id), :tracker_id => tracker.id}).nil?
else
false
end
end
def <=>(status) def <=>(status)
position <=> status.position position <=> status.position
end end
+3 -15
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -32,16 +32,12 @@ class Journal < ActiveRecord::Base
:url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}} :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}}
acts_as_activity_provider :type => 'issues', acts_as_activity_provider :type => 'issues',
:permission => :view_issues,
:author_key => :user_id, :author_key => :user_id,
:find_options => {:include => [{:issue => :project}, :details, :user], :find_options => {:include => [{:issue => :project}, :details, :user],
:conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" + :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
" (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
named_scope :visible, lambda {|*args| {
:include => {:issue => :project},
:conditions => Issue.visible_condition(args.shift || User.current, *args)
}}
def save(*args) def save(*args)
# Do not save an empty journal # Do not save an empty journal
(details.empty? && notes.blank?) ? false : super (details.empty? && notes.blank?) ? false : super
@@ -77,12 +73,4 @@ class Journal < ActiveRecord::Base
s << ' has-details' unless details.blank? s << ' has-details' unless details.blank?
s s
end end
def notify?
@notify != false
end
def notify=(arg)
@notify = arg
end
end end
+5 -18
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -17,22 +17,9 @@
class JournalDetail < ActiveRecord::Base class JournalDetail < ActiveRecord::Base
belongs_to :journal belongs_to :journal
before_save :normalize_values
private def before_save
self.value = value[0..254] if value && value.is_a?(String)
def normalize_values self.old_value = old_value[0..254] if old_value && old_value.is_a?(String)
self.value = normalize(value)
self.old_value = normalize(old_value)
end
def normalize(v)
if v == true
"1"
elsif v == false
"0"
else
v
end
end end
end end
+3 -5
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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,12 +17,10 @@
class JournalObserver < ActiveRecord::Observer class JournalObserver < ActiveRecord::Observer
def after_create(journal) def after_create(journal)
if journal.notify? && if Setting.notified_events.include?('issue_updated') ||
(Setting.notified_events.include?('issue_updated') ||
(Setting.notified_events.include?('issue_note_added') && journal.notes.present?) || (Setting.notified_events.include?('issue_note_added') && journal.notes.present?) ||
(Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) || (Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) ||
(Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?) (Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?)
)
Mailer.deliver_issue_edit(journal) Mailer.deliver_issue_edit(journal)
end end
end end
+10 -10
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -198,7 +198,7 @@ class MailHandler < ActionMailer::Base
end end
def add_attachments(obj) def add_attachments(obj)
if email.attachments && email.attachments.any? if email.has_attachments?
email.attachments.each do |attachment| email.attachments.each do |attachment|
Attachment.create(:container => obj, Attachment.create(:container => obj,
:file => attachment, :file => attachment,
@@ -240,8 +240,8 @@ class MailHandler < ActionMailer::Base
def extract_keyword!(text, attr, format=nil) def extract_keyword!(text, attr, format=nil)
keys = [attr.to_s.humanize] keys = [attr.to_s.humanize]
if attr.is_a?(Symbol) if attr.is_a?(Symbol)
keys << l("field_#{attr}", :default => '', :locale => user.language) if user && user.language.present? keys << l("field_#{attr}", :default => '', :locale => user.language) if user
keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) if Setting.default_language.present? keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
end end
keys.reject! {|k| k.blank?} keys.reject! {|k| k.blank?}
keys.collect! {|k| Regexp.escape(k)} keys.collect! {|k| Regexp.escape(k)}
@@ -265,12 +265,12 @@ class MailHandler < ActionMailer::Base
assigned_to = nil if assigned_to && !issue.assignable_users.include?(assigned_to) assigned_to = nil if assigned_to && !issue.assignable_users.include?(assigned_to)
attrs = { attrs = {
'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id), 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.find_by_name(k).try(:id),
'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id), 'status_id' => (k = get_keyword(:status)) && IssueStatus.find_by_name(k).try(:id),
'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id), 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.find_by_name(k).try(:id),
'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id), 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.find_by_name(k).try(:id),
'assigned_to_id' => assigned_to.try(:id), 'assigned_to_id' => assigned_to.try(:id),
'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && issue.project.shared_versions.named(k).first.try(:id), 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && issue.project.shared_versions.find_by_name(k).try(:id),
'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
'estimated_hours' => get_keyword(:estimated_hours, :override => true), 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
+3 -21
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -149,24 +149,6 @@ class Mailer < ActionMailer::Base
render_multipart('news_added', body) render_multipart('news_added', body)
end end
# Builds a tmail object used to email recipients of a news' project when a news comment is added.
#
# Example:
# news_comment_added(comment) => tmail object
# Mailer.news_comment_added(comment) => sends an email to the news' project recipients
def news_comment_added(comment)
news = comment.commented
redmine_headers 'Project' => news.project.identifier
message_id comment
recipients news.recipients
cc news.watcher_recipients
subject "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}"
body :news => news,
:comment => comment,
:news_url => url_for(:controller => 'news', :action => 'show', :id => news)
render_multipart('news_comment_added', body)
end
# Builds a tmail object used to email the recipients of the specified message that was posted. # Builds a tmail object used to email the recipients of the specified message that was posted.
# #
# Example: # Example:
@@ -314,7 +296,7 @@ class Mailer < ActionMailer::Base
if raise_errors if raise_errors
raise e raise e
elsif mylogger elsif mylogger
mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml." mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/email.yml."
end end
ensure ensure
self.class.raise_delivery_errors = raise_errors self.class.raise_delivery_errors = raise_errors
+3 -6
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -24,7 +24,7 @@ class Message < ActiveRecord::Base
acts_as_searchable :columns => ['subject', 'content'], acts_as_searchable :columns => ['subject', 'content'],
:include => {:board => :project}, :include => {:board => :project},
:project_key => "#{Board.table_name}.project_id", :project_key => 'project_id',
:date_column => "#{table_name}.created_on" :date_column => "#{table_name}.created_on"
acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
:description => :content, :description => :content,
@@ -42,9 +42,6 @@ class Message < ActiveRecord::Base
after_create :add_author_as_watcher after_create :add_author_as_watcher
named_scope :visible, lambda {|*args| { :include => {:board => :project},
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
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
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
+2 -11
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -28,13 +28,10 @@ class News < ActiveRecord::Base
acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}} acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
acts_as_activity_provider :find_options => {:include => [:project, :author]}, acts_as_activity_provider :find_options => {:include => [:project, :author]},
:author_key => :author_id :author_key => :author_id
acts_as_watchable
after_create :add_author_as_watcher
named_scope :visible, lambda {|*args| { named_scope :visible, lambda {|*args| {
:include => :project, :include => :project,
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_news, *args) :conditions => Project.allowed_to_condition(args.first || User.current, :view_news)
}} }}
def visible?(user=User.current) def visible?(user=User.current)
@@ -45,10 +42,4 @@ class News < ActiveRecord::Base
def self.latest(user = User.current, count = 5) def self.latest(user = User.current, count = 5)
find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
end end
private
def add_author_as_watcher
Watcher.create(:watchable => self, :user => author)
end
end end
+30 -81
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -84,7 +84,7 @@ class Project < ActiveRecord::Base
named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } } named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
named_scope :all_public, { :conditions => { :is_public => true } } named_scope :all_public, { :conditions => { :is_public => true } }
named_scope :visible, lambda {|*args| {:conditions => Project.visible_condition(args.shift || User.current, *args) }} named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
def initialize(attributes = nil) def initialize(attributes = nil)
super super
@@ -115,36 +115,27 @@ class Project < ActiveRecord::Base
# returns latest created projects # returns latest created projects
# non public projects will be returned only if user is a member of those # non public projects will be returned only if user is a member of those
def self.latest(user=nil, count=5) def self.latest(user=nil, count=5)
visible(user).find(:all, :limit => count, :order => "created_on DESC") find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
end end
# Returns true if the project is visible to +user+ or to the current user. # Returns a SQL :conditions string used to find all active projects for the specified user.
def visible?(user=User.current)
user.allowed_to?(:view_project, self)
end
def self.visible_by(user=nil)
ActiveSupport::Deprecation.warn "Project.visible_by is deprecated and will be removed in Redmine 1.3.0. Use Project.visible_condition instead."
visible_condition(user || User.current)
end
# Returns a SQL conditions string used to find all projects visible by the specified user.
# #
# Examples: # Examples:
# Project.visible_condition(admin) => "projects.status = 1" # Projects.visible_by(admin) => "projects.status = 1"
# Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))" # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
# Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))" def self.visible_by(user=nil)
def self.visible_condition(user, options={}) user ||= User.current
allowed_to_condition(user, :view_project, options) if user && user.admin?
return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
elsif user && user.memberships.any?
return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
else
return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
end
end end
# Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
#
# Valid options:
# * :project => limit the condition to project
# * :with_subprojects => limit the condition to project and its subprojects
# * :member => limit the condition to the user projects
def self.allowed_to_condition(user, permission, options={}) def self.allowed_to_condition(user, permission, options={})
statements = []
base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
if perm = Redmine::AccessControl.permission(permission) if perm = Redmine::AccessControl.permission(permission)
unless perm.project_module.nil? unless perm.project_module.nil?
@@ -157,37 +148,24 @@ class Project < ActiveRecord::Base
project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects] project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
base_statement = "(#{project_statement}) AND (#{base_statement})" base_statement = "(#{project_statement}) AND (#{base_statement})"
end end
if user.admin? if user.admin?
base_statement # no restriction
else else
statement_by_role = {} statements << "1=0"
unless options[:member]
role = user.logged? ? Role.non_member : Role.anonymous
if role.allowed_to?(permission)
statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
end
end
if user.logged? if user.logged?
user.projects_by_role.each do |role, projects| if Role.non_member.allowed_to?(permission) && !options[:member]
if role.allowed_to?(permission) statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
end end
end allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
end statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
if statement_by_role.empty?
"1=0"
else else
if block_given? if Role.anonymous.allowed_to?(permission) && !options[:member]
statement_by_role.each do |role, statement| # anonymous user allowed on public project
if s = yield(role, user) statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
statement_by_role[role] = "(#{statement} AND (#{s}))"
end end
end end
end end
"((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))" statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
end
end
end end
# Returns the Systemwide and project specific activities # Returns the Systemwide and project specific activities
@@ -369,7 +347,7 @@ class Project < ActiveRecord::Base
# Returns an array of the trackers used by the project and its active sub projects # Returns an array of the trackers used by the project and its active sub projects
def rolled_up_trackers def rolled_up_trackers
@rolled_up_trackers ||= @rolled_up_trackers ||=
Tracker.find(:all, :joins => :projects, Tracker.find(:all, :include => :projects,
:select => "DISTINCT #{Tracker.table_name}.*", :select => "DISTINCT #{Tracker.table_name}.*",
:conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt], :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
:order => "#{Tracker.table_name}.position") :order => "#{Tracker.table_name}.position")
@@ -395,18 +373,16 @@ class Project < ActiveRecord::Base
# Returns a scope of the Versions used by the project # Returns a scope of the Versions used by the project
def shared_versions def shared_versions
@shared_versions ||= begin @shared_versions ||=
r = root? ? self : root
Version.scoped(:include => :project, Version.scoped(:include => :project,
:conditions => "#{Project.table_name}.id = #{id}" + :conditions => "#{Project.table_name}.id = #{id}" +
" OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" + " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
" #{Version.table_name}.sharing = 'system'" + " #{Version.table_name}.sharing = 'system'" +
" OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" + " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" +
" OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" + " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
" OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" + " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
"))") "))")
end end
end
# Returns a hash of project users grouped by role # Returns a hash of project users grouped by role
def users_by_role def users_by_role
@@ -448,12 +424,6 @@ class Project < ActiveRecord::Base
@all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
end end
# Returns an array of all custom fields enabled for project time entries
# (explictly associated custom fields and custom fields enabled for all projects)
def all_time_entry_custom_fields
@all_time_entry_custom_fields ||= (TimeEntryCustomField.for_all + time_entry_custom_fields).uniq.sort
end
def project def project
self self
end end
@@ -550,26 +520,6 @@ class Project < ActiveRecord::Base
enabled_modules.collect(&:name) enabled_modules.collect(&:name)
end end
# Enable a specific module
#
# Examples:
# project.enable_module!(:issue_tracking)
# project.enable_module!("issue_tracking")
def enable_module!(name)
enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
end
# Disable a module if it exists
#
# Examples:
# project.disable_module!(:issue_tracking)
# project.disable_module!("issue_tracking")
# project.disable_module!(project.enabled_modules.first)
def disable_module!(target)
target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
target.destroy unless target.blank?
end
safe_attributes 'name', safe_attributes 'name',
'description', 'description',
'homepage', 'homepage',
@@ -714,7 +664,6 @@ class Project < ActiveRecord::Base
end end
# Copies issues from +project+ # Copies issues from +project+
# Note: issues assigned to a closed version won't be copied due to validation rules
def copy_issues(project) def copy_issues(project)
# Stores the source issue id as a key and the copied issues as the # Stores the source issue id as a key and the copied issues as the
# value. Used to map the two togeather for issue relations. # value. Used to map the two togeather for issue relations.
+29 -53
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -42,10 +42,6 @@ class QueryColumn
def value(issue) def value(issue)
issue.send name issue.send name
end end
def css_classes
name
end
end end
class QueryCustomFieldColumn < QueryColumn class QueryCustomFieldColumn < QueryColumn
@@ -72,10 +68,6 @@ class QueryCustomFieldColumn < QueryColumn
cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id} cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
cv && @cf.cast_value(cv.value) cv && @cf.cast_value(cv.value)
end end
def css_classes
@css_classes ||= "#{name} #{@cf.field_format}"
end
end end
class Query < ActiveRecord::Base class Query < ActiveRecord::Base
@@ -166,11 +158,6 @@ class Query < ActiveRecord::Base
end if filters end if filters
end end
# Returns true if the query is visible to +user+ or the current user.
def visible?(user=User.current)
self.is_public? || self.user_id == user.id
end
def editable_by?(user) def editable_by?(user)
return false unless user return false unless user
# Admin can edit them all and regular users can edit their private queries # Admin can edit them all and regular users can edit their private queries
@@ -229,19 +216,14 @@ class Query < ActiveRecord::Base
if project if project
# project specific filters # project specific filters
categories = @project.issue_categories.all unless @project.issue_categories.empty?
unless categories.empty? @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
end end
versions = @project.shared_versions.all unless @project.shared_versions.empty?
unless versions.empty? @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
end
unless @project.leaf?
subprojects = @project.descendants.visible.all
unless subprojects.empty?
@available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
end end
unless @project.descendants.active.empty?
@available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
end end
add_custom_fields_filters(@project.all_issue_custom_fields) add_custom_fields_filters(@project.all_issue_custom_fields)
else else
@@ -333,17 +315,14 @@ class Query < ActiveRecord::Base
end end
def columns def columns
# preserve the column_names order if has_default_columns?
(has_default_columns? ? default_columns_names : column_names).collect do |name| available_columns.select do |c|
available_columns.find { |col| col.name == name } # Adds the project column by default for cross-project lists
end.compact Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
end end
else
def default_columns_names # preserve the column_names order
@default_columns_names ||= begin column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
default_columns = Setting.issue_list_default_columns.map(&:to_sym)
project.present? ? default_columns : [:project] | default_columns
end end
end end
@@ -352,7 +331,7 @@ class Query < ActiveRecord::Base
names = names.select {|n| n.is_a?(Symbol) || !n.blank? } names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
# Set column_names to nil if default columns # Set column_names to nil if default columns
if names == default_columns_names if names.map(&:to_s) == Setting.issue_list_default_columns
names = nil names = nil
end end
end end
@@ -432,7 +411,8 @@ class Query < ActiveRecord::Base
elsif project elsif project
project_clauses << "#{Project.table_name}.id = %d" % project.id project_clauses << "#{Project.table_name}.id = %d" % project.id
end end
project_clauses.any? ? project_clauses.join(' AND ') : nil project_clauses << Project.allowed_to_condition(User.current, :view_issues)
project_clauses.join(' AND ')
end end
def statement def statement
@@ -513,15 +493,12 @@ class Query < ActiveRecord::Base
end if filters and valid? end if filters and valid?
filters_clauses << project_statement (filters_clauses << project_statement).join(' AND ')
filters_clauses.reject!(&:blank?)
filters_clauses.any? ? filters_clauses.join(' AND ') : nil
end end
# Returns the issue count # Returns the issue count
def issue_count def issue_count
Issue.visible.count(:include => [:status, :project], :conditions => statement) Issue.count(:include => [:status, :project], :conditions => statement)
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message) raise StatementInvalid.new(e.message)
end end
@@ -532,7 +509,7 @@ class Query < ActiveRecord::Base
if grouped? if grouped?
begin begin
# Rails will raise an (unexpected) RecordNotFound if there's only a nil group value # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement) r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
r = {nil => issue_count} r = {nil => issue_count}
end end
@@ -552,7 +529,7 @@ class Query < ActiveRecord::Base
order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
order_option = nil if order_option.blank? order_option = nil if order_option.blank?
Issue.visible.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq, Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
:conditions => Query.merge_conditions(statement, options[:conditions]), :conditions => Query.merge_conditions(statement, options[:conditions]),
:order => order_option, :order => order_option,
:limit => options[:limit], :limit => options[:limit],
@@ -564,7 +541,7 @@ class Query < ActiveRecord::Base
# Returns the journals # Returns the journals
# Valid options are :order, :offset, :limit # Valid options are :order, :offset, :limit
def journals(options={}) def journals(options={})
Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
:conditions => statement, :conditions => statement,
:order => options[:order], :order => options[:order],
:limit => options[:limit], :limit => options[:limit],
@@ -576,7 +553,7 @@ class Query < ActiveRecord::Base
# Returns the versions # Returns the versions
# Valid options are :conditions # Valid options are :conditions
def versions(options={}) def versions(options={})
Version.visible.find :all, :include => :project, Version.find :all, :include => :project,
:conditions => Query.merge_conditions(project_statement, options[:conditions]) :conditions => Query.merge_conditions(project_statement, options[:conditions])
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message) raise StatementInvalid.new(e.message)
@@ -631,10 +608,12 @@ class Query < ActiveRecord::Base
when "t" when "t"
sql = date_range_clause(db_table, db_field, 0, 0) sql = date_range_clause(db_table, db_field, 0, 0)
when "w" when "w"
first_day_of_week = l(:general_first_day_of_week).to_i from = l(:general_first_day_of_week) == '7' ?
day_of_week = Date.today.cwday # week starts on sunday
days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
sql = date_range_clause(db_table, db_field, - days_ago, - days_ago + 6) # week starts on monday (Rails default)
Time.now.at_beginning_of_week
sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
when "~" when "~"
sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
when "!~" when "!~"
@@ -657,9 +636,6 @@ class Query < ActiveRecord::Base
options = { :type => :date, :order => 20 } options = { :type => :date, :order => 20 }
when "bool" when "bool"
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
when "user", "version"
next unless project
options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20}
else else
options = { :type => :string, :order => 20 } options = { :type => :string, :order => 20 }
end end
+10 -114
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -16,30 +16,17 @@
# 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 Repository < ActiveRecord::Base class Repository < ActiveRecord::Base
include Redmine::Ciphering
belongs_to :project belongs_to :project
has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
has_many :changes, :through => :changesets has_many :changes, :through => :changesets
serialize :extra_info
# Raw SQL to delete changesets and changes in the database # Raw SQL to delete changesets and changes in the database
# has_many :changesets, :dependent => :destroy is too slow for big repositories # has_many :changesets, :dependent => :destroy is too slow for big repositories
before_destroy :clear_changesets before_destroy :clear_changesets
validates_length_of :password, :maximum => 255, :allow_nil => true
# Checks if the SCM is enabled when creating a repository # Checks if the SCM is enabled when creating a repository
validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) } validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
def self.human_attribute_name(attribute_key_name)
attr_name = attribute_key_name
if attr_name == "log_encoding"
attr_name = "commit_logs_encoding"
end
super(attr_name)
end
# Removes leading and trailing whitespace # Removes leading and trailing whitespace
def url=(arg) def url=(arg)
write_attribute(:url, arg ? arg.to_s.strip : nil) write_attribute(:url, arg ? arg.to_s.strip : nil)
@@ -50,21 +37,8 @@ class Repository < ActiveRecord::Base
write_attribute(:root_url, arg ? arg.to_s.strip : nil) write_attribute(:root_url, arg ? arg.to_s.strip : nil)
end end
def password
read_ciphered_attribute(:password)
end
def password=(arg)
write_ciphered_attribute(:password, arg)
end
def scm_adapter
self.class.scm_adapter_class
end
def scm def scm
@scm ||= self.scm_adapter.new(url, root_url, @scm ||= self.scm_adapter.new url, root_url, login, password
login, password, path_encoding)
update_attribute(:root_url, @scm.root_url) if root_url.blank? update_attribute(:root_url, @scm.root_url) if root_url.blank?
@scm @scm
end end
@@ -73,17 +47,6 @@ class Repository < ActiveRecord::Base
self.class.scm_name self.class.scm_name
end end
def merge_extra_info(arg)
h = extra_info || {}
return h if arg.nil?
h.merge!(arg)
write_attribute(:extra_info, h)
end
def report_last_commit
true
end
def supports_cat? def supports_cat?
scm.supports_cat? scm.supports_cat?
end end
@@ -92,14 +55,6 @@ class Repository < ActiveRecord::Base
scm.supports_annotate? scm.supports_annotate?
end end
def supports_all_revisions?
true
end
def supports_directory_revisions?
false
end
def entry(path=nil, identifier=nil) def entry(path=nil, identifier=nil)
scm.entry(path, identifier) scm.entry(path, identifier)
end end
@@ -117,7 +72,7 @@ class Repository < ActiveRecord::Base
end end
def default_branch def default_branch
nil scm.default_branch
end end
def properties(path, identifier=nil) def properties(path, identifier=nil)
@@ -132,13 +87,6 @@ class Repository < ActiveRecord::Base
scm.diff(path, rev, rev_to) scm.diff(path, rev, rev_to)
end end
def diff_format_revisions(cs, cs_to, sep=':')
text = ""
text << cs_to.format_identifier + sep if cs_to
text << cs.format_identifier if cs
text
end
# Returns a path relative to the url of the repository # Returns a path relative to the url of the repository
def relative_path(path) def relative_path(path)
path path
@@ -147,8 +95,7 @@ class Repository < ActiveRecord::Base
# Finds and returns a revision with a number or the beginning of a hash # Finds and returns a revision with a number or the beginning of a hash
def find_changeset_by_name(name) def find_changeset_by_name(name)
return nil if name.blank? return nil if name.blank?
changesets.find(:first, :conditions => (name.match(/^\d*$/) ? changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
end end
def latest_changeset def latest_changeset
@@ -159,19 +106,14 @@ class Repository < ActiveRecord::Base
# Default behaviour is to search in cached changesets # Default behaviour is to search in cached changesets
def latest_changesets(path, rev, limit=10) def latest_changesets(path, rev, limit=10)
if path.blank? if path.blank?
changesets.find( changesets.find(:all, :include => :user,
:all,
:include => :user,
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
:limit => limit) :limit => limit)
else else
changes.find( changes.find(:all, :include => {:changeset => :user},
:all,
:include => {:changeset => :user},
:conditions => ["path = ?", path.with_leading_slash], :conditions => ["path = ?", path.with_leading_slash],
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
:limit => limit :limit => limit).collect(&:changeset)
).collect(&:changeset)
end end
end end
@@ -181,8 +123,7 @@ class Repository < ActiveRecord::Base
# Returns an array of committers usernames and associated user_id # Returns an array of committers usernames and associated user_id
def committers def committers
@committers ||= Changeset.connection.select_rows( @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
"SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
end end
# Maps committers username to a user ids # Maps committers username to a user ids
@@ -192,9 +133,7 @@ class Repository < ActiveRecord::Base
new_user_id = h[committer] new_user_id = h[committer]
if new_user_id && (new_user_id.to_i != user_id.to_i) if new_user_id && (new_user_id.to_i != user_id.to_i)
new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil) new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
Changeset.update_all( Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
"user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
["repository_id = ? AND committer = ?", id, committer])
end end
end end
@committers = nil @committers = nil
@@ -228,22 +167,13 @@ class Repository < ActiveRecord::Base
end end
end end
def repo_log_encoding
encoding = log_encoding.to_s.strip
encoding.blank? ? 'UTF-8' : encoding
end
# Fetches new changesets for all repositories of active projects # Fetches new changesets for all repositories of active projects
# Can be called periodically by an external script # Can be called periodically by an external script
# eg. ruby script/runner "Repository.fetch_changesets" # eg. ruby script/runner "Repository.fetch_changesets"
def self.fetch_changesets def self.fetch_changesets
Project.active.has_module(:repository).find(:all, :include => :repository).each do |project| Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
if project.repository if project.repository
begin
project.repository.fetch_changesets project.repository.fetch_changesets
rescue Redmine::Scm::Adapters::CommandFailed => e
logger.error "scm: error during fetching changesets: #{e.message}"
end
end end
end end
end end
@@ -268,40 +198,6 @@ class Repository < ActiveRecord::Base
nil nil
end end
def self.scm_adapter_class
nil
end
def self.scm_command
ret = ""
begin
ret = self.scm_adapter_class.client_command if self.scm_adapter_class
rescue Exception => e
logger.error "scm: error during get command: #{e.message}"
end
ret
end
def self.scm_version_string
ret = ""
begin
ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
rescue Exception => e
logger.error "scm: error during get version string: #{e.message}"
end
ret
end
def self.scm_available
ret = false
begin
ret = self.scm_adapter_class.client_available if self.scm_adapter_class
rescue Exception => e
logger.error "scm: error during get scm available: #{e.message}"
end
ret
end
private private
def before_save def before_save
+6 -19
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -19,17 +19,9 @@ require 'redmine/scm/adapters/bazaar_adapter'
class Repository::Bazaar < Repository class Repository::Bazaar < Repository
attr_protected :root_url attr_protected :root_url
validates_presence_of :url, :log_encoding validates_presence_of :url
def self.human_attribute_name(attribute_key_name) def scm_adapter
attr_name = attribute_key_name
if attr_name == "url"
attr_name = "path_to_repository"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::BazaarAdapter Redmine::Scm::Adapters::BazaarAdapter
end end
@@ -47,14 +39,9 @@ class Repository::Bazaar < Repository
full_path = File.join(root_url, e.path) full_path = File.join(root_url, e.path)
e.size = File.stat(full_path).size if File.file?(full_path) e.size = File.stat(full_path).size if File.file?(full_path)
end end
c = Change.find( c = Change.find(:first,
:first,
:include => :changeset, :include => :changeset,
:conditions => [ :conditions => ["#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", e.lastrev.revision, id],
"#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?",
e.lastrev.revision,
id
],
:order => "#{Changeset.table_name}.revision DESC") :order => "#{Changeset.table_name}.revision DESC")
if c if c
e.lastrev.identifier = c.changeset.revision e.lastrev.identifier = c.changeset.revision
+33 -77
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -19,19 +19,9 @@ require 'redmine/scm/adapters/cvs_adapter'
require 'digest/sha1' require 'digest/sha1'
class Repository::Cvs < Repository class Repository::Cvs < Repository
validates_presence_of :url, :root_url, :log_encoding validates_presence_of :url, :root_url
def self.human_attribute_name(attribute_key_name) def scm_adapter
attr_name = attribute_key_name
if attr_name == "root_url"
attr_name = "cvsroot"
elsif attr_name == "url"
attr_name = "cvs_module"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::CvsAdapter Redmine::Scm::Adapters::CvsAdapter
end end
@@ -45,23 +35,17 @@ class Repository::Cvs < Repository
end end
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
rev = nil rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
if ! identifier.nil?
rev = changesets.find_by_revision(identifier)
return nil if rev.nil?
end
entries = scm.entries(path, rev.nil? ? nil : rev.committed_on) entries = scm.entries(path, rev.nil? ? nil : rev.committed_on)
if entries if entries
entries.each() do |entry| entries.each() do |entry|
if ( ! entry.lastrev.nil? ) && ( ! entry.lastrev.revision.nil? ) unless entry.lastrev.nil? || entry.lastrev.identifier
change=changes.find_by_revision_and_path( change=changes.find_by_revision_and_path( entry.lastrev.revision, scm.with_leading_slash(entry.path) )
entry.lastrev.revision,
scm.with_leading_slash(entry.path) )
if change if change
entry.lastrev.identifier=change.changeset.revision entry.lastrev.identifier=change.changeset.revision
entry.lastrev.revision = change.changeset.revision
entry.lastrev.author=change.changeset.committer entry.lastrev.author=change.changeset.committer
# entry.lastrev.branch = change.branch entry.lastrev.revision=change.revision
entry.lastrev.branch=change.branch
end end
end end
end end
@@ -70,23 +54,10 @@ class Repository::Cvs < Repository
end end
def cat(path, identifier=nil) def cat(path, identifier=nil)
rev = nil rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
if ! identifier.nil?
rev = changesets.find_by_revision(identifier)
return nil if rev.nil?
end
scm.cat(path, rev.nil? ? nil : rev.committed_on) scm.cat(path, rev.nil? ? nil : rev.committed_on)
end end
def annotate(path, identifier=nil)
rev = nil
if ! identifier.nil?
rev = changesets.find_by_revision(identifier)
return nil if rev.nil?
end
scm.annotate(path, rev.nil? ? nil : rev.committed_on)
end
def diff(path, rev, rev_to) def diff(path, rev, rev_to)
#convert rev to revision. CVS can't handle changesets here #convert rev to revision. CVS can't handle changesets here
diff=[] diff=[]
@@ -95,11 +66,12 @@ class Repository::Cvs < Repository
changeset_to=changesets.find_by_revision(rev_to) changeset_to=changesets.find_by_revision(rev_to)
end end
changeset_from.changes.each() do |change_from| changeset_from.changes.each() do |change_from|
revision_from=nil revision_from=nil
revision_to=nil revision_to=nil
if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
revision_from = change_from.revision revision_from=change_from.revision if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
end
if revision_from if revision_from
if changeset_to if changeset_to
changeset_to.changes.each() do |change_to| changeset_to.changes.each() do |change_to|
@@ -118,50 +90,41 @@ class Repository::Cvs < Repository
def fetch_changesets def fetch_changesets
# some nifty bits to introduce a commit-id with cvs # some nifty bits to introduce a commit-id with cvs
# natively cvs doesn't provide any kind of changesets, # natively cvs doesn't provide any kind of changesets, there is only a revision per file.
# there is only a revision per file.
# we now take a guess using the author, the commitlog and the commit-date. # we now take a guess using the author, the commitlog and the commit-date.
# last one is the next step to take. the commit-date is not equal for all # last one is the next step to take. the commit-date is not equal for all
# commits in one changeset. cvs update the commit-date when the *,v file was touched. so # commits in one changeset. cvs update the commit-date when the *,v file was touched. so
# we use a small delta here, to merge all changes belonging to _one_ changeset # we use a small delta here, to merge all changes belonging to _one_ changeset
time_delta=10.seconds time_delta=10.seconds
fetch_since = latest_changeset ? latest_changeset.committed_on : nil fetch_since = latest_changeset ? latest_changeset.committed_on : nil
transaction do transaction do
tmp_rev_num = 1 tmp_rev_num = 1
scm.revisions('', fetch_since, nil, :log_encoding => repo_log_encoding) do |revision| scm.revisions('', fetch_since, nil, :with_paths => true) do |revision|
# only add the change to the database, if it doen't exists. the cvs log # only add the change to the database, if it doen't exists. the cvs log
# is not exclusive at all. # is not exclusive at all.
tmp_time = revision.time.clone unless changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
unless changes.find_by_path_and_revision( revision
scm.with_leading_slash(revision.paths[0][:path]), cs = changesets.find(:first, :conditions=>{
revision.paths[0][:revision] :committed_on=>revision.time-time_delta..revision.time+time_delta,
) :committer=>revision.author,
cmt = Changeset.normalize_comments(revision.message, repo_log_encoding) :comments=>Changeset.normalize_comments(revision.message)
author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding) })
cs = changesets.find(
:first,
:conditions => {
:committed_on => tmp_time - time_delta .. tmp_time + time_delta,
:committer => author_utf8,
:comments => cmt
}
)
# create a new changeset.... # create a new changeset....
unless cs unless cs
# we use a temporaray revision number here (just for inserting) # we use a temporaray revision number here (just for inserting)
# later on, we calculate a continous positive number # later on, we calculate a continous positive number
tmp_time2 = tmp_time.clone.gmtime latest = changesets.find(:first, :order => 'id DESC')
branch = revision.paths[0][:branch]
scmid = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S")
cs = Changeset.create(:repository => self, cs = Changeset.create(:repository => self,
:revision => "tmp#{tmp_rev_num}", :revision => "_#{tmp_rev_num}",
:scmid => scmid,
:committer => revision.author, :committer => revision.author,
:committed_on => tmp_time, :committed_on => revision.time,
:comments => revision.message) :comments => revision.message)
tmp_rev_num += 1 tmp_rev_num += 1
end end
#convert CVS-File-States to internal Action-abbrevations #convert CVS-File-States to internal Action-abbrevations
#default action is (M)odified #default action is (M)odified
action="M" action="M"
@@ -170,8 +133,8 @@ class Repository::Cvs < Repository
elsif revision.paths[0][:action]=="dead" elsif revision.paths[0][:action]=="dead"
action="D" #dead-state is similar to Delete action="D" #dead-state is similar to Delete
end end
Change.create(
:changeset => cs, Change.create(:changeset => cs,
:action => action, :action => action,
:path => scm.with_leading_slash(revision.paths[0][:path]), :path => scm.with_leading_slash(revision.paths[0][:path]),
:revision => revision.paths[0][:revision], :revision => revision.paths[0][:revision],
@@ -181,15 +144,10 @@ class Repository::Cvs < Repository
end end
# Renumber new changesets in chronological order # Renumber new changesets in chronological order
changesets.find( changesets.find(:all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE '_%'").each do |changeset|
:all,
:order => 'committed_on ASC, id ASC',
:conditions => "revision LIKE 'tmp%'"
).each do |changeset|
changeset.update_attribute :revision, next_revision_number changeset.update_attribute :revision, next_revision_number
end end
end # transaction end # transaction
@current_revision_number = nil
end end
private private
@@ -197,9 +155,7 @@ class Repository::Cvs < Repository
# Returns the next revision number to assign to a CVS changeset # Returns the next revision number to assign to a CVS changeset
def next_revision_number def next_revision_number
# Need to retrieve existing revision numbers to sort them as integers # Need to retrieve existing revision numbers to sort them as integers
sql = "SELECT revision FROM #{Changeset.table_name} " @current_revision_number ||= (connection.select_values("SELECT revision FROM #{Changeset.table_name} WHERE repository_id = #{id} AND revision NOT LIKE '_%'").collect(&:to_i).max || 0)
sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'"
@current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0)
@current_revision_number += 1 @current_revision_number += 1
end end
end end
+7 -24
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -18,17 +18,9 @@
require 'redmine/scm/adapters/darcs_adapter' require 'redmine/scm/adapters/darcs_adapter'
class Repository::Darcs < Repository class Repository::Darcs < Repository
validates_presence_of :url, :log_encoding validates_presence_of :url
def self.human_attribute_name(attribute_key_name) def scm_adapter
attr_name = attribute_key_name
if attr_name == "url"
attr_name = "path_to_repository"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::DarcsAdapter Redmine::Scm::Adapters::DarcsAdapter
end end
@@ -36,28 +28,18 @@ class Repository::Darcs < Repository
'Darcs' 'Darcs'
end end
def supports_directory_revisions?
true
end
def entry(path=nil, identifier=nil) def entry(path=nil, identifier=nil)
patch = identifier.nil? ? nil : changesets.find_by_revision(identifier) patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
scm.entry(path, patch.nil? ? nil : patch.scmid) scm.entry(path, patch.nil? ? nil : patch.scmid)
end end
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
patch = nil patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
if ! identifier.nil?
patch = changesets.find_by_revision(identifier)
return nil if patch.nil?
end
entries = scm.entries(path, patch.nil? ? nil : patch.scmid) entries = scm.entries(path, patch.nil? ? nil : patch.scmid)
if entries if entries
entries.each do |entry| entries.each do |entry|
# Search the DB for the entry's last change # Search the DB for the entry's last change
if entry.lastrev && !entry.lastrev.scmid.blank? changeset = changesets.find_by_scmid(entry.lastrev.scmid) if entry.lastrev && !entry.lastrev.scmid.blank?
changeset = changesets.find_by_scmid(entry.lastrev.scmid)
end
if changeset if changeset
entry.lastrev.identifier = changeset.revision entry.lastrev.identifier = changeset.revision
entry.lastrev.name = changeset.revision entry.lastrev.name = changeset.revision
@@ -101,6 +83,7 @@ class Repository::Darcs < Repository
:committer => revision.author, :committer => revision.author,
:committed_on => revision.time, :committed_on => revision.time,
:comments => revision.message) :comments => revision.message)
revision.paths.each do |change| revision.paths.each do |change|
changeset.create_change(change) changeset.create_change(change)
end end
+4 -15
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# FileSystem adapter # FileSystem adapter
# File written by Paul Rivier, at Demotera. # File written by Paul Rivier, at Demotera.
@@ -24,15 +24,7 @@ class Repository::Filesystem < Repository
attr_protected :root_url attr_protected :root_url
validates_presence_of :url validates_presence_of :url
def self.human_attribute_name(attribute_key_name) def scm_adapter
attr_name = attribute_key_name
if attr_name == "url"
attr_name = "root_directory"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::FilesystemAdapter Redmine::Scm::Adapters::FilesystemAdapter
end end
@@ -40,10 +32,6 @@ class Repository::Filesystem < Repository
'Filesystem' 'Filesystem'
end end
def supports_all_revisions?
false
end
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
scm.entries(path, identifier) scm.entries(path, identifier)
end end
@@ -51,4 +39,5 @@ class Repository::Filesystem < Repository
def fetch_changesets def fetch_changesets
nil nil
end end
end end
+34 -115
View File
@@ -1,7 +1,6 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com # Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com
#
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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
@@ -22,15 +21,7 @@ class Repository::Git < Repository
attr_protected :root_url attr_protected :root_url
validates_presence_of :url validates_presence_of :url
def self.human_attribute_name(attribute_key_name) def scm_adapter
attr_name = attribute_key_name
if attr_name == "url"
attr_name = "path_to_repository"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::GitAdapter Redmine::Scm::Adapters::GitAdapter
end end
@@ -38,25 +29,6 @@ class Repository::Git < Repository
'Git' 'Git'
end end
def report_last_commit
extra_report_last_commit
end
def extra_report_last_commit
return false if extra_info.nil?
v = extra_info["extra_report_last_commit"]
return false if v.nil?
v.to_s != '0'
end
def supports_directory_revisions?
true
end
def repo_log_encoding
'UTF-8'
end
# Returns the identifier for the given git changeset # Returns the identifier for the given git changeset
def self.changeset_identifier(changeset) def self.changeset_identifier(changeset)
changeset.scmid changeset.scmid
@@ -75,105 +47,52 @@ class Repository::Git < Repository
scm.tags scm.tags
end end
def default_branch
scm.default_branch
end
def find_changeset_by_name(name)
return nil if name.nil? || name.empty?
e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
return e if e
changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
end
def entries(path=nil, identifier=nil)
scm.entries(path,
identifier,
options = {:report_last_commit => extra_report_last_commit})
end
# With SCMs that have a sequential commit numbering,
# such as Subversion and Mercurial,
# Redmine is able to be clever and only fetch changesets
# going forward from the most recent one it knows about.
#
# However, Git does not have a sequential commit numbering.
#
# In order to fetch only new adding revisions,
# Redmine needs to parse revisions per branch.
# Branch "last_scmid" is for this requirement.
#
# In Git and Mercurial, revisions are not in date order. # In Git and Mercurial, revisions are not in date order.
# Redmine Mercurial fixed issues. # Mercurial fixed issues.
# * Redmine Takes Too Long On Large Mercurial Repository # * Redmine Takes Too Long On Large Mercurial Repository
# http://www.redmine.org/issues/3449 # http://www.redmine.org/issues/3449
# * Sorting for changesets might go wrong on Mercurial repos # * Sorting for changesets might go wrong on Mercurial repos
# http://www.redmine.org/issues/3567 # http://www.redmine.org/issues/3567
#
# Database revision column is text, so Redmine can not sort by revision. # Database revision column is text, so Redmine can not sort by revision.
# Mercurial has revision number, and revision number guarantees revision order. # Mercurial has revision number, and revision number guarantees revision order.
# Redmine Mercurial model stored revisions ordered by database id to database. # Mercurial adapter uses "hg log -r 0:tip --limit 10"
# So, Redmine Mercurial model can use correct ordering revisions.
#
# Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
# to get limited revisions from old to new. # to get limited revisions from old to new.
# And Mercurial model stored revisions ordered by database id in database.
# So, Mercurial can use correct order revisions.
#
# But, Git 1.7.3.4 does not support --reverse with -n or --skip. # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
# #
# With SCM's that have a sequential commit numbering, redmine is able to be
# clever and only fetch changesets going forward from the most recent one
# it knows about.
# However, with git, you never know if people have merged
# commits into the middle of the repository history, so we should parse
# the entire log.
#
# Since it's way too slow for large repositories,
# we only parse 1 week before the last known commit.
#
# The repository can still be fully reloaded by calling #clear_changesets # The repository can still be fully reloaded by calling #clear_changesets
# before fetching changesets (eg. for offline resync) # before fetching changesets (eg. for offline resync)
def fetch_changesets def fetch_changesets
scm_brs = branches c = changesets.find(:first, :order => 'committed_on DESC')
return if scm_brs.nil? || scm_brs.empty? since = (c ? c.committed_on - 7.days : nil)
h1 = extra_info || {}
h = h1.dup
h["branches"] ||= {}
h["db_consistent"] ||= {}
if changesets.count == 0
h["db_consistent"]["ordering"] = 1
merge_extra_info(h)
self.save
elsif ! h["db_consistent"].has_key?("ordering")
h["db_consistent"]["ordering"] = 0
merge_extra_info(h)
self.save
end
scm_brs.each do |br|
from_scmid = nil
from_scmid = h["branches"][br]["last_scmid"] if h["branches"][br]
h["branches"][br] ||= {}
scm.revisions('', from_scmid, br, {:reverse => true}) do |rev|
db_rev = find_changeset_by_name(rev.revision)
transaction do
if db_rev.nil?
save_revision(rev)
end
h["branches"][br]["last_scmid"] = rev.scmid
merge_extra_info(h)
self.save
end
end
end
end
def save_revision(rev) revisions = scm.revisions('', nil, nil, :all => true, :since => since)
changeset = Changeset.new( return if revisions.nil? || revisions.empty?
:repository => self,
:revision => rev.identifier, recent_changesets = changesets.find(:all, :conditions => ['committed_on >= ?', since])
:scmid => rev.scmid,
:committer => rev.author, # Clean out revisions that are no longer in git
:committed_on => rev.time, recent_changesets.each {|c| c.destroy unless revisions.detect {|r| r.scmid.to_s == c.scmid.to_s }}
:comments => rev.message
) # Subtract revisions that redmine already knows about
if changeset.save recent_revisions = recent_changesets.map{|c| c.scmid}
rev.paths.each do |file| revisions.reject!{|r| recent_revisions.include?(r.scmid)}
Change.create(
:changeset => changeset, # Save the remaining ones to the database
:action => file[:action], revisions.each{|r| r.save(self)} unless revisions.nil?
:path => file[:path])
end end
end
end
private :save_revision
def latest_changesets(path,rev,limit=10) def latest_changesets(path,rev,limit=10)
revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
+56 -98
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -24,17 +24,7 @@ class Repository::Mercurial < Repository
attr_protected :root_url attr_protected :root_url
validates_presence_of :url validates_presence_of :url
FETCH_AT_ONCE = 100 # number of changesets to fetch at once def scm_adapter
def self.human_attribute_name(attribute_key_name)
attr_name = attribute_key_name
if attr_name == "url"
attr_name = "path_to_repository"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::MercurialAdapter Redmine::Scm::Adapters::MercurialAdapter
end end
@@ -42,106 +32,74 @@ class Repository::Mercurial < Repository
'Mercurial' 'Mercurial'
end end
def supports_directory_revisions? def entries(path=nil, identifier=nil)
true entries=scm.entries(path, identifier)
if entries
entries.each do |entry|
next unless entry.is_file?
# Set the filesize unless browsing a specific revision
if identifier.nil?
full_path = File.join(root_url, entry.path)
entry.size = File.stat(full_path).size if File.file?(full_path)
end end
# Search the DB for the entry's last change
def repo_log_encoding change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
'UTF-8' if change
entry.lastrev.identifier = change.changeset.revision
entry.lastrev.name = change.changeset.revision
entry.lastrev.author = change.changeset.committer
entry.lastrev.revision = change.revision
end end
# Returns the readable identifier for the given mercurial changeset
def self.format_changeset_identifier(changeset)
"#{changeset.revision}:#{changeset.scmid}"
end end
# Returns the identifier for the given Mercurial changeset
def self.changeset_identifier(changeset)
changeset.scmid
end end
entries
def diff_format_revisions(cs, cs_to, sep=':')
super(cs, cs_to, ' ')
end
# Finds and returns a revision with a number or the beginning of a hash
def find_changeset_by_name(name)
return nil if name.nil? || name.empty?
if /[^\d]/ =~ name or name.to_s.size > 8
e = changesets.find(:first, :conditions => ['scmid = ?', name.to_s])
else
e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
end
return e if e
changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) # last ditch
end end
# Returns the latest changesets for +path+; sorted by revision number # Returns the latest changesets for +path+; sorted by revision number
#
# 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?
def latest_changesets(path, rev, limit=10) def latest_changesets(path, rev, limit=10)
changesets.find(:all, :include => :user, if path.blank?
:conditions => latest_changesets_cond(path, rev, limit), changesets.find(:all, :include => :user, :limit => limit, :order => "id DESC")
:limit => limit, :order => "#{Changeset.table_name}.id DESC") else
changes.find(:all, :include => {:changeset => :user},
:conditions => ["path = ?", path.with_leading_slash],
:order => "#{Changeset.table_name}.id DESC",
:limit => limit).collect(&:changeset)
end end
def latest_changesets_cond(path, rev, limit)
cond, args = [], []
if scm.branchmap.member? rev
# Mercurial named branch is *stable* in each revision.
# So, named branch can be stored in database.
# Mercurial provides *bookmark* which is equivalent with git branch.
# But, bookmark is not implemented.
cond << "#{Changeset.table_name}.scmid IN (?)"
# Revisions in root directory and sub directory are not equal.
# So, in order to get correct limit, we need to get all revisions.
# But, it is very heavy.
# Mercurial does not treat direcotry.
# So, "hg log DIR" is very heavy.
branch_limit = path.blank? ? limit : ( limit * 5 )
args << scm.nodes_in_branch(rev, :limit => branch_limit)
elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil
cond << "#{Changeset.table_name}.id <= ?"
args << last.id
end end
unless path.blank?
cond << "EXISTS (SELECT * FROM #{Change.table_name}
WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id
AND (#{Change.table_name}.path = ?
OR #{Change.table_name}.path LIKE ? ESCAPE ?))"
args << path.with_leading_slash
args << "#{path.with_leading_slash.gsub(/[%_\\]/) { |s| "\\#{s}" }}/%" << '\\'
end
[cond.join(' AND '), *args] unless cond.empty?
end
private :latest_changesets_cond
def fetch_changesets def fetch_changesets
return if scm.info.nil? scm_info = scm.info
scm_rev = scm.info.lastrev.revision.to_i if scm_info
db_rev = latest_changeset ? latest_changeset.revision.to_i : -1 # latest revision found in database
return unless db_rev < scm_rev # already up-to-date db_revision = latest_changeset ? latest_changeset.revision.to_i : -1
# latest revision in the repository
logger.debug "Fetching changesets for repository #{url}" if logger latest_revision = scm_info.lastrev
(db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i| return if latest_revision.nil?
scm_revision = latest_revision.identifier.to_i
if db_revision < scm_revision
logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
identifier_from = db_revision + 1
while (identifier_from <= scm_revision)
# loads changesets by batches of 100
identifier_to = [identifier_from + 99, scm_revision].min
revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true)
transaction do transaction do
scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re| revisions.each do |revision|
cs = Changeset.create(:repository => self, changeset = Changeset.create(:repository => self,
:revision => re.revision, :revision => revision.identifier,
:scmid => re.scmid, :scmid => revision.scmid,
:committer => re.author, :committer => revision.author,
:committed_on => re.time, :committed_on => revision.time,
:comments => re.message) :comments => revision.message)
re.paths.each { |e| cs.create_change(e) }
revision.paths.each do |change|
changeset.create_change(change)
end
end
end unless revisions.nil?
identifier_from = identifier_to + 1
end end
end end
end end
self
end end
end end
+3 -11
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -22,7 +22,7 @@ class Repository::Subversion < Repository
validates_presence_of :url validates_presence_of :url
validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i
def self.scm_adapter_class def scm_adapter
Redmine::Scm::Adapters::SubversionAdapter Redmine::Scm::Adapters::SubversionAdapter
end end
@@ -30,14 +30,6 @@ class Repository::Subversion < Repository
'Subversion' 'Subversion'
end end
def supports_directory_revisions?
true
end
def repo_log_encoding
'UTF-8'
end
def latest_changesets(path, rev, limit=10) def latest_changesets(path, rev, limit=10)
revisions = scm.revisions(path, rev, nil, :limit => limit) revisions = scm.revisions(path, rev, nil, :limit => limit)
revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : [] revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : []
+3 -19
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -20,12 +20,6 @@ class Role < ActiveRecord::Base
BUILTIN_NON_MEMBER = 1 BUILTIN_NON_MEMBER = 1
BUILTIN_ANONYMOUS = 2 BUILTIN_ANONYMOUS = 2
ISSUES_VISIBILITY_OPTIONS = [
['all', :label_issues_visibility_all],
['default', :label_issues_visibility_public],
['own', :label_issues_visibility_own]
]
named_scope :givable, { :conditions => "builtin = 0", :order => 'position' } named_scope :givable, { :conditions => "builtin = 0", :order => 'position' }
named_scope :builtin, lambda { |*args| named_scope :builtin, lambda { |*args|
compare = 'not' if args.first == true compare = 'not' if args.first == true
@@ -49,9 +43,7 @@ class Role < ActiveRecord::Base
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_inclusion_of :issues_visibility, validates_format_of :name, :with => /^[\w\s\'\-]*$/i
:in => ISSUES_VISIBILITY_OPTIONS.collect(&:first),
:if => lambda {|role| role.respond_to?(:issues_visibility)}
def permissions def permissions
read_attribute(:permissions) || [] read_attribute(:permissions) || []
@@ -93,14 +85,6 @@ class Role < ActiveRecord::Base
name name
end end
def name
case builtin
when 1; l(:label_role_non_member, :default => read_attribute(:name))
when 2; l(:label_role_anonymous, :default => read_attribute(:name))
else; read_attribute(:name)
end
end
# Return true if the role is a builtin role # Return true if the role is a builtin role
def builtin? def builtin?
self.builtin != 0 self.builtin != 0
+2 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -65,7 +65,6 @@ class Setting < ActiveRecord::Base
UTF-16LE UTF-16LE
EUC-JP EUC-JP
Shift_JIS Shift_JIS
CP932
GB18030 GB18030
GBK GBK
ISCII91 ISCII91
+2 -9
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
@@ -39,11 +39,6 @@ class TimeEntry < ActiveRecord::Base
validates_numericality_of :hours, :allow_nil => true, :message => :invalid validates_numericality_of :hours, :allow_nil => true, :message => :invalid
validates_length_of :comments, :maximum => 255, :allow_nil => true validates_length_of :comments, :maximum => 255, :allow_nil => true
named_scope :visible, lambda {|*args| {
:include => :project,
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args)
}}
def after_initialize def after_initialize
if new_record? && self.activity.nil? if new_record? && self.activity.nil?
if default_activity = TimeEntryActivity.default if default_activity = TimeEntryActivity.default
@@ -84,9 +79,7 @@ class TimeEntry < ActiveRecord::Base
(usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project) (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
end end
# TODO: remove this method in 1.3.0
def self.visible_by(usr) def self.visible_by(usr)
ActiveSupport::Deprecation.warn "TimeEntry.visible_by is deprecated and will be removed in Redmine 1.3.0. Use the visible scope instead."
with_scope(:find => { :conditions => Project.allowed_to_condition(usr, :view_time_entries) }) do with_scope(:find => { :conditions => Project.allowed_to_condition(usr, :view_time_entries) }) do
yield yield
end end
+1 -2
View File
@@ -31,8 +31,7 @@ class Tracker < ActiveRecord::Base
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
def to_s; name end def to_s; name end
+15 -110
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -48,8 +48,8 @@ class User < Principal
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
has_many :changesets, :dependent => :nullify has_many :changesets, :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'" has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
has_one :api_token, :class_name => 'Token', :conditions => "action='api'" has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
belongs_to :auth_source belongs_to :auth_source
# Active non-anonymous users scope # Active non-anonymous users scope
@@ -68,23 +68,13 @@ class User < Principal
# Login must contain lettres, numbers, underscores only # Login must contain lettres, numbers, underscores only
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
validates_length_of :login, :maximum => 30 validates_length_of :login, :maximum => 30
validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
validates_length_of :firstname, :lastname, :maximum => 30 validates_length_of :firstname, :lastname, :maximum => 30
validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
validates_length_of :mail, :maximum => 60, :allow_nil => true validates_length_of :mail, :maximum => 60, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true validates_confirmation_of :password, :allow_nil => true
validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
before_destroy :remove_references_before_destroy
named_scope :in_group, lambda {|group|
group_id = group.is_a?(Group) ? group.id : group.to_i
{ :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
}
named_scope :not_in_group, lambda {|group|
group_id = group.is_a?(Group) ? group.id : group.to_i
{ :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
}
def before_create def before_create
self.mail_notification = Setting.default_notification_option if self.mail_notification.blank? self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
true true
@@ -92,14 +82,11 @@ class User < Principal
def before_save def before_save
# update hashed_password if password was set # update hashed_password if password was set
if self.password && self.auth_source_id.blank? self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
salt_password(password)
end
end end
def reload(*args) def reload(*args)
@name = nil @name = nil
@projects_by_role = nil
super super
end end
@@ -133,7 +120,7 @@ class User < Principal
return nil unless user.auth_source.authenticate(login, password) return nil unless user.auth_source.authenticate(login, password)
else else
# authentication with local password # authentication with local password
return nil unless user.check_password?(password) return nil unless User.hash_password(password) == user.hashed_password
end end
else else
# user is not yet registered, try to authenticate with available sources # user is not yet registered, try to authenticate with available sources
@@ -212,22 +199,14 @@ class User < Principal
update_attribute(:status, STATUS_LOCKED) update_attribute(:status, STATUS_LOCKED)
end end
# Returns true if +clear_password+ is the correct user's password, otherwise false
def check_password?(clear_password) def check_password?(clear_password)
if auth_source_id.present? if auth_source_id.present?
auth_source.authenticate(self.login, clear_password) auth_source.authenticate(self.login, clear_password)
else else
User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password User.hash_password(clear_password) == self.hashed_password
end end
end end
# Generates a random salt and computes hashed_password for +clear_password+
# The hashed password is stored in the following form: SHA1(salt + SHA1(password))
def salt_password(clear_password)
self.salt = User.generate_salt
self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
end
# Does the backend storage allow this user to change their password? # Does the backend storage allow this user to change their password?
def change_password_allowed? def change_password_allowed?
return true if auth_source_id.blank? return true if auth_source_id.blank?
@@ -371,33 +350,16 @@ class User < Principal
!roles_for_project(project).detect {|role| role.member?}.nil? !roles_for_project(project).detect {|role| role.member?}.nil?
end end
# Returns a hash of user's projects grouped by roles
def projects_by_role
return @projects_by_role if @projects_by_role
@projects_by_role = Hash.new {|h,k| h[k]=[]}
memberships.each do |membership|
membership.roles.each do |role|
@projects_by_role[role] << membership.project if membership.project
end
end
@projects_by_role.each do |role, projects|
projects.uniq!
end
@projects_by_role
end
# Return true if the user is allowed to do the specified action on a specific context # Return true if the user is allowed to do the specified action on a specific context
# Action can be: # Action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project) # * a permission Symbol (eg. :edit_project)
# Context can be: # Context can be:
# * a project : returns true if user is allowed to do the specified action on this project # * a project : returns true if user is allowed to do the specified action on this project
# * an array of projects : returns true if user is allowed on every project # * a group of projects : returns true if user is allowed on every project
# * nil with options[:global] set : check if user has at least one role allowed for this action, # * nil with options[:global] set : check if user has at least one role allowed for this action,
# or falls back to Non Member / Anonymous permissions depending if the user is logged # or falls back to Non Member / Anonymous permissions depending if the user is logged
def allowed_to?(action, context, options={}, &block) def allowed_to?(action, context, options={})
if context && context.is_a?(Project) if context && context.is_a?(Project)
# No action allowed on archived projects # No action allowed on archived projects
return false unless context.active? return false unless context.active?
@@ -408,15 +370,12 @@ class User < Principal
roles = roles_for_project(context) roles = roles_for_project(context)
return false unless roles return false unless roles
roles.detect {|role| roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
(context.is_public? || role.member?) &&
role.allowed_to?(action) &&
(block_given? ? yield(role, self) : true)
}
elsif context && context.is_a?(Array) elsif context && context.is_a?(Array)
# Authorize if user is authorized on every element of the array # Authorize if user is authorized on every element of the array
context.map do |project| context.map do |project|
allowed_to?(action, project, options, &block) allowed_to?(action,project,options)
end.inject do |memo,allowed| end.inject do |memo,allowed|
memo && allowed memo && allowed
end end
@@ -426,11 +385,7 @@ class User < Principal
# authorize if user has at least one role that has this permission # authorize if user has at least one role that has this permission
roles = memberships.collect {|m| m.roles}.flatten.uniq roles = memberships.collect {|m| m.roles}.flatten.uniq
roles << (self.logged? ? Role.non_member : Role.anonymous) roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
roles.detect {|role|
role.allowed_to?(action) &&
(block_given? ? yield(role, self) : true)
}
else else
false false
end end
@@ -438,8 +393,8 @@ class User < Principal
# Is the user allowed to do the specified action on any project? # Is the user allowed to do the specified action on any project?
# See allowed_to? for the actions and valid options. # See allowed_to? for the actions and valid options.
def allowed_to_globally?(action, options, &block) def allowed_to_globally?(action, options)
allowed_to?(action, nil, options.reverse_merge(:global => true), &block) allowed_to?(action, nil, options.reverse_merge(:global => true))
end end
safe_attributes 'login', safe_attributes 'login',
@@ -518,20 +473,6 @@ class User < Principal
anonymous_user anonymous_user
end end
# Salts all existing unsalted passwords
# It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
# This method is used in the SaltPasswords migration and is to be kept as is
def self.salt_unsalted_passwords!
transaction do
User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
next if user.hashed_password.blank?
salt = User.generate_salt
hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
end
end
end
protected protected
def validate def validate
@@ -543,41 +484,10 @@ class User < Principal
private private
# Removes references that are not handled by associations
# Things that are not deleted are reassociated with the anonymous user
def remove_references_before_destroy
return if self.id.nil?
substitute = User.anonymous
Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
# Remove private queries and keep public ones
Query.delete_all ['user_id = ? AND is_public = ?', id, false]
Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
Token.delete_all ['user_id = ?', id]
Watcher.delete_all ['user_id = ?', id]
WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
end
# Return password digest # Return password digest
def self.hash_password(clear_password) def self.hash_password(clear_password)
Digest::SHA1.hexdigest(clear_password || "") Digest::SHA1.hexdigest(clear_password || "")
end end
# Returns a 128bits random salt as a hex string (32 chars long)
def self.generate_salt
ActiveSupport::SecureRandom.hex(16)
end
end end
class AnonymousUser < User class AnonymousUser < User
@@ -598,9 +508,4 @@ class AnonymousUser < User
def mail; nil end def mail; nil end
def time_zone; nil end def time_zone; nil end
def rss_key; nil end def rss_key; nil end
# Anonymous user can not be destroyed
def destroy
false
end
end end
-3
View File
@@ -51,7 +51,4 @@ class UserPreference < ActiveRecord::Base
def comments_sorting; self[:comments_sorting] end def comments_sorting; self[:comments_sorting] end
def comments_sorting=(order); self[:comments_sorting]=order end def comments_sorting=(order); self[:comments_sorting]=order end
def warn_on_leaving_unsaved; self[:warn_on_leaving_unsaved] || '1'; end
def warn_on_leaving_unsaved=(value); self[:warn_on_leaving_unsaved]=value; end
end end
-1
View File
@@ -33,7 +33,6 @@ class Version < ActiveRecord::Base
validates_inclusion_of :status, :in => VERSION_STATUSES validates_inclusion_of :status, :in => VERSION_STATUSES
validates_inclusion_of :sharing, :in => VERSION_SHARINGS validates_inclusion_of :sharing, :in => VERSION_SHARINGS
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
named_scope :open, :conditions => {:status => 'open'} named_scope :open, :conditions => {:status => 'open'}
named_scope :visible, lambda {|*args| { :include => :project, named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } } :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
+1 -10
View File
@@ -44,26 +44,17 @@ class Wiki < ActiveRecord::Base
# find the page with the given title # find the page with the given title
def find_page(title, options = {}) def find_page(title, options = {})
@page_found_with_redirect = false
title = start_page if title.blank? title = start_page if title.blank?
title = Wiki.titleize(title) title = Wiki.titleize(title)
page = pages.first(:conditions => ["LOWER(title) = LOWER(?)", title]) page = pages.first(:conditions => ["LOWER(title) = LOWER(?)", title])
if !page && !(options[:with_redirect] == false) if !page && !(options[:with_redirect] == false)
# search for a redirect # search for a redirect
redirect = redirects.first(:conditions => ["LOWER(title) = LOWER(?)", title]) redirect = redirects.first(:conditions => ["LOWER(title) = LOWER(?)", title])
if redirect page = find_page(redirect.redirects_to, :with_redirect => false) if redirect
page = find_page(redirect.redirects_to, :with_redirect => false)
@page_found_with_redirect = true
end
end end
page page
end end
# Returns true if the last page was found with a redirect
def page_found_with_redirect?
@page_found_with_redirect
end
# Finds a page by title # Finds a page by title
# The given string can be of one of the forms: "title" or "project:title" # The given string can be of one of the forms: "title" or "project:title"
# Examples: # Examples:
+2 -2
View File
@@ -1,5 +1,5 @@
# RedMine - project management software # redMine - project management software
# Copyright (C) 2006-2011 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
+10 -29
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 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
@@ -30,9 +30,8 @@ class WikiPage < ActiveRecord::Base
:datetime => :created_on, :datetime => :created_on,
:url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}} :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}}
acts_as_searchable :columns => ['title', "#{WikiContent.table_name}.text"], acts_as_searchable :columns => ['title', 'text'],
:include => [{:wiki => :project}, :content], :include => [{:wiki => :project}, :content],
:permission => :view_wiki_pages,
:project_key => "#{Wiki.table_name}.project_id" :project_key => "#{Wiki.table_name}.project_id"
attr_accessor :redirect_existing_links attr_accessor :redirect_existing_links
@@ -42,12 +41,6 @@ class WikiPage < ActiveRecord::Base
validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
validates_associated :content validates_associated :content
# eager load information about last updates, without loading text
named_scope :with_updated_on, {
: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"
}
# Wiki pages that are protected by default # Wiki pages that are protected by default
DEFAULT_PROTECTED_PAGES = %w(sidebar) DEFAULT_PROTECTED_PAGES = %w(sidebar)
@@ -128,18 +121,6 @@ class WikiPage < ActiveRecord::Base
content.text if content content.text if content
end end
def updated_on
unless @updated_on
if time = read_attribute(:updated_on)
# content updated_on was eager loaded with the page
@updated_on = Time.parse(time) rescue nil
else
@updated_on = content && content.updated_on
end
end
@updated_on
end
# Returns true if usr is allowed to edit the page, otherwise false # Returns true if usr is allowed to edit the page, otherwise false
def editable_by?(usr) def editable_by?(usr)
!protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project) !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
@@ -168,13 +149,17 @@ class WikiPage < ActiveRecord::Base
end end
end end
class WikiDiff < Redmine::Helpers::Diff class WikiDiff
attr_reader :content_to, :content_from attr_reader :diff, :words, :content_to, :content_from
def initialize(content_to, content_from) def initialize(content_to, content_from)
@content_to = content_to @content_to = content_to
@content_from = content_from @content_from = content_from
super(content_to.text, content_from.text) @words = content_to.text.split(/(\s+)/)
@words = @words.select {|word| word != ' '}
words_from = content_from.text.split(/(\s+)/)
words_from = words_from.select {|word| word != ' '}
@diff = words_from.diff @words
end end
end end
@@ -212,10 +197,6 @@ class WikiAnnotate
break unless @lines.detect { |line| line[0].nil? } break unless @lines.detect { |line| line[0].nil? }
current = current.previous current = current.previous
end end
@lines.each { |line| @lines.each { |line| line[0] ||= current.version }
line[0] ||= current.version
# if the last known version is > 1 (eg. history was cleared), we don't know the author
line[1] ||= current.author if current.version == 1
}
end end
end end
+2 -2
View File
@@ -89,8 +89,8 @@ class Workflow < ActiveRecord::Base
else else
transaction do transaction do
delete_all :tracker_id => target_tracker.id, :role_id => target_role.id delete_all :tracker_id => target_tracker.id, :role_id => target_role.id
connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee)" + connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id)" +
" SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee" + " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id" +
" FROM #{Workflow.table_name}" + " FROM #{Workflow.table_name}" +
" WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}"
end end
+8 -6
View File
@@ -21,14 +21,16 @@
<%= content_tag('p', l(:label_no_data), :class => 'nodata') if @events_by_day.empty? %> <%= content_tag('p', l(:label_no_data), :class => 'nodata') if @events_by_day.empty? %>
<div style="float:left;"> <div style="float:left;">
<%= link_to_content_update('&#171; ' + l(:label_previous), <%= link_to_remote(('&#171; ' + l(:label_previous)),
params.merge(:from => @date_to - @days - 1), {:update => "content", :url => params.merge(:from => @date_to - @days - 1), :method => :get, :complete => 'window.scrollTo(0,0)'},
:title => l(:label_date_from_to, :start => format_date(@date_to - 2*@days), :end => format_date(@date_to - @days - 1))) %> {:href => url_for(params.merge(:from => @date_to - @days - 1)),
:title => l(:label_date_from_to, :start => format_date(@date_to - 2*@days), :end => format_date(@date_to - @days - 1))}) %>
</div> </div>
<div style="float:right;"> <div style="float:right;">
<%= link_to_content_update(l(:label_next) + ' &#187;', <%= link_to_remote((l(:label_next) + ' &#187;'),
params.merge(:from => @date_to + @days - 1), {:update => "content", :url => params.merge(:from => @date_to + @days - 1), :method => :get, :complete => 'window.scrollTo(0,0)'},
:title => l(:label_date_from_to, :start => format_date(@date_to), :end => format_date(@date_to + @days - 1))) unless @date_to >= Date.today %> {:href => url_for(params.merge(:from => @date_to + @days - 1)),
:title => l(:label_date_from_to, :start => format_date(@date_to), :end => format_date(@date_to + @days - 1))}) unless @date_to >= Date.today %>
</div> </div>
&nbsp; &nbsp;
<% other_formats_links do |f| %> <% other_formats_links do |f| %>
+1 -2
View File
@@ -6,8 +6,7 @@
<% @checklist.each do |label, result| %> <% @checklist.each do |label, result| %>
<tr class="<%= cycle 'odd', 'even' %>"> <tr class="<%= cycle 'odd', 'even' %>">
<td><%= l(label) %></td> <td><%= l(label) %></td>
<td width="30px"><%= image_tag((result ? 'true.png' : 'exclamation.png'), <td width="30px"><%= image_tag((result ? 'true.png' : 'exclamation.png'), :style => "vertical-align:bottom;") %></td>
:style => "vertical-align:bottom;") %></td>
</tr> </tr>
<% end %> <% end %>
</table> </table>
-1
View File
@@ -11,7 +11,6 @@
<label><%= l(:label_project) %>:</label> <label><%= l(:label_project) %>:</label>
<%= text_field_tag 'name', params[:name], :size => 30 %> <%= text_field_tag 'name', params[:name], :size => 30 %>
<%= submit_tag l(:button_apply), :class => "small", :name => nil %> <%= submit_tag l(:button_apply), :class => "small", :name => nil %>
<%= link_to l(:button_clear), {:controller => 'admin', :action => 'projects'}, :class => 'icon icon-reload' %>
</fieldset> </fieldset>
<% end %> <% end %>
&nbsp; &nbsp;
+17 -8
View File
@@ -1,16 +1,16 @@
<h2><%= @query.new_record? ? l(:label_calendar) : h(@query.name) %></h2> <h2><%= l(:label_calendar) %></h2>
<% form_tag({:controller => 'calendars', :action => 'show', :project_id => @project}, :method => :get, :id => 'query_form') do %> <% form_tag(calendar_path, :method => :put, :id => 'query_form') do %>
<%= hidden_field_tag 'set_filter', '1' %> <%= hidden_field_tag('project_id', @project.to_param) if @project%>
<fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>"> <fieldset id="filters" class="collapsible">
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend> <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
<div style="<%= @query.new_record? ? "" : "display: none;" %>"> <div>
<%= render :partial => 'queries/filters', :locals => {:query => @query} %> <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
</div> </div>
</fieldset> </fieldset>
<p style="float:right;"> <p style="float:right;">
<%= link_to_previous_month(@year, @month) %> | <%= link_to_next_month(@year, @month) %> <%= link_to_previous_month(@year, @month, :project => @project) %> | <%= link_to_next_month(@year, @month, :project => @project) %>
</p> </p>
<p class="buttons"> <p class="buttons">
@@ -19,8 +19,17 @@
<%= label_tag('year', l(:label_year)) %> <%= label_tag('year', l(:label_year)) %>
<%= select_year(@year, :prefix => "year", :discard_type => true) %> <%= select_year(@year, :prefix => "year", :discard_type => true) %>
<%= link_to_function l(:button_apply), '$("query_form").submit()', :class => 'icon icon-checked' %> <%= link_to_remote l(:button_apply),
<%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 }, :class => 'icon icon-reload' %> { :url => { :set_filter => (@query.new_record? ? 1 : nil) },
:update => "content",
:with => "Form.serialize('query_form')"
}, :class => 'icon icon-checked' %>
<%= link_to_remote l(:button_clear),
{ :url => { :project_id => @project, :set_filter => (@query.new_record? ? 1 : nil) },
:method => :put,
:update => "content",
}, :class => 'icon icon-reload' if @query.new_record? %>
</p> </p>
<% end %> <% end %>
+30 -20
View File
@@ -1,56 +1,66 @@
<% diff = Redmine::UnifiedDiff.new(diff, :type => diff_type, :max_lines => Setting.diff_max_lines_displayed.to_i) -%> <% diff = Redmine::UnifiedDiff.new(diff, :type => diff_type, :max_lines => Setting.diff_max_lines_displayed.to_i) -%>
<% diff.each do |table_file| -%> <% diff.each do |table_file| -%>
<div class="autoscroll"> <div class="autoscroll">
<% if diff.diff_type == 'sbs' -%> <% if diff_type == 'sbs' -%>
<table class="filecontent"> <table class="filecontent">
<thead> <thead>
<tr><th colspan="4" class="filename"><%=to_utf8 table_file.file_name %></th></tr> <tr><th colspan="4" class="filename"><%= table_file.file_name %></th></tr>
</thead> </thead>
<tbody> <tbody>
<% table_file.each_line do |spacing, line| -%> <% prev_line_left, prev_line_right = nil, nil -%>
<% if spacing -%> <% table_file.keys.sort.each do |key| -%>
<% if prev_line_left && prev_line_right && (table_file[key].nb_line_left != prev_line_left+1) && (table_file[key].nb_line_right != prev_line_right+1) -%>
<tr class="spacing"> <tr class="spacing">
<th class="line-num">...</th><td></td><th class="line-num">...</th><td></td> <th class="line-num">...</th><td></td><th class="line-num">...</th><td></td>
</tr>
<% end -%> <% end -%>
<tr> <tr>
<th class="line-num"><%= line.nb_line_left %></th> <th class="line-num"><%= table_file[key].nb_line_left %></th>
<td class="line-code <%= line.type_diff_left %>"> <td class="line-code <%= table_file[key].type_diff_left %>">
<pre><%=to_utf8 line.html_line_left %></pre> <pre><%=to_utf8 table_file[key].line_left %></pre>
</td> </td>
<th class="line-num"><%= line.nb_line_right %></th> <th class="line-num"><%= table_file[key].nb_line_right %></th>
<td class="line-code <%= line.type_diff_right %>"> <td class="line-code <%= table_file[key].type_diff_right %>">
<pre><%=to_utf8 line.html_line_right %></pre> <pre><%=to_utf8 table_file[key].line_right %></pre>
</td> </td>
</tr> </tr>
<% prev_line_left, prev_line_right = table_file[key].nb_line_left.to_i, table_file[key].nb_line_right.to_i -%>
<% end -%> <% end -%>
</tbody> </tbody>
</table> </table>
<% else -%> <% else -%>
<table class="filecontent"> <table class="filecontent syntaxhl">
<thead> <thead>
<tr><th colspan="3" class="filename"><%=to_utf8 table_file.file_name %></th></tr> <tr><th colspan="3" class="filename"><%= table_file.file_name %></th></tr>
</thead> </thead>
<tbody> <tbody>
<% table_file.each_line do |spacing, line| %> <% prev_line_left, prev_line_right = nil, nil -%>
<% if spacing -%> <% table_file.keys.sort.each do |key, line| %>
<% if prev_line_left && prev_line_right && (table_file[key].nb_line_left != prev_line_left+1) && (table_file[key].nb_line_right != prev_line_right+1) -%>
<tr class="spacing"> <tr class="spacing">
<th class="line-num">...</th><th class="line-num">...</th><td></td> <th class="line-num">...</th><th class="line-num">...</th><td></td>
</tr> </tr>
<% end -%> <% end -%>
<tr> <tr>
<th class="line-num"><%= line.nb_line_left %></th> <th class="line-num"><%= table_file[key].nb_line_left %></th>
<th class="line-num"><%= line.nb_line_right %></th> <th class="line-num"><%= table_file[key].nb_line_right %></th>
<td class="line-code <%= line.type_diff %>"> <% if table_file[key].line_left.empty? -%>
<pre><%=to_utf8 line.html_line %></pre> <td class="line-code <%= table_file[key].type_diff_right %>">
<pre><%=to_utf8 table_file[key].line_right %></pre>
</td> </td>
<% else -%>
<td class="line-code <%= table_file[key].type_diff_left %>">
<pre><%=to_utf8 table_file[key].line_left %></pre>
</td>
<% end -%>
</tr> </tr>
<% prev_line_left = table_file[key].nb_line_left.to_i if table_file[key].nb_line_left.to_i > 0 -%>
<% prev_line_right = table_file[key].nb_line_right.to_i if table_file[key].nb_line_right.to_i > 0 -%>
<% end -%> <% end -%>
</tbody> </tbody>
</table> </table>
<% end -%> <% end -%>
</div> </div>
<% end -%> <% end -%>
+1 -1
View File
@@ -115,7 +115,7 @@
<li><%= context_menu_link l(:button_move), new_issue_move_path(:ids => @issues.collect(&:id)), <li><%= context_menu_link l(:button_move), new_issue_move_path(:ids => @issues.collect(&:id)),
:class => 'icon-move', :disabled => !@can[:move] %></li> :class => 'icon-move', :disabled => !@can[:move] %></li>
<li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id), :back_url => @back}, <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id), :back_url => @back},
:method => :post, :confirm => issues_destroy_confirmation_message(@issues), :class => 'icon-del', :disabled => !@can[:delete] %></li> :method => :post, :confirm => l(:text_issues_destroy_confirmation), :class => 'icon-del', :disabled => !@can[:delete] %></li>
<%= call_hook(:view_issues_context_menu_end, {:issues => @issues, :can => @can, :back => @back }) %> <%= call_hook(:view_issues_context_menu_end, {:issues => @issues, :can => @can, :back => @back }) %>
</ul> </ul>
@@ -1,33 +0,0 @@
<ul>
<% if !@time_entry.nil? -%>
<li><%= context_menu_link l(:button_edit), {:controller => 'timelog', :action => 'edit', :id => @time_entry},
:class => 'icon-edit', :disabled => !@can[:edit] %></li>
<% else %>
<li><%= context_menu_link l(:button_edit), {:controller => 'timelog', :action => 'bulk_edit', :ids => @time_entries.collect(&:id)},
:class => 'icon-edit', :disabled => !@can[:edit] %></li>
<% end %>
<%= call_hook(:view_time_entries_context_menu_start, {:time_entries => @time_entries, :can => @can, :back => @back }) %>
<% if @activities.present? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_activity) %></a>
<ul>
<% @activities.each do |u| -%>
<li><%= context_menu_link u.name, {:controller => 'timelog', :action => 'bulk_edit', :ids => @time_entries.collect(&:id), :time_entry => {'activity_id' => u}, :back_url => @back}, :method => :post,
:selected => (@time_entry && u == @time_entry.activity), :disabled => !@can[:update] %></li>
<% end -%>
<li><%= context_menu_link l(:label_nobody), {:controller => 'timelog', :action => 'bulk_edit', :ids => @time_entries.collect(&:id), :time_entry => {'activity_id' => 'none'}, :back_url => @back}, :method => :post,
:selected => (@time_entry && @time_entry.activity.nil?), :disabled => !@can[:update] %></li>
</ul>
</li>
<% end %>
<%= call_hook(:view_time_entries_context_menu_end, {:time_entries => @time_entries, :can => @can, :back => @back }) %>
<li>
<%= context_menu_link l(:button_delete),
{:controller => 'timelog', :action => 'destroy', :ids => @time_entries.collect(&:id), :back_url => @back},
:method => :delete, :confirm => l(:text_time_entries_destroy_confirmation), :class => 'icon-del', :disabled => !@can[:delete] %>
</li>
</ul>
+10 -18
View File
@@ -18,41 +18,33 @@ function toggle_custom_field_format() {
Element.hide(p_length.parentNode); Element.hide(p_length.parentNode);
Element.hide(p_regexp.parentNode); Element.hide(p_regexp.parentNode);
if (p_searchable) Element.show(p_searchable.parentNode); if (p_searchable) Element.show(p_searchable.parentNode);
Element.show(p_values.parentNode); Element.show(p_values);
break; break;
case "bool": case "bool":
p_default.setAttribute('type','checkbox'); p_default.setAttribute('type','checkbox');
Element.hide(p_length.parentNode); Element.hide(p_length.parentNode);
Element.hide(p_regexp.parentNode); Element.hide(p_regexp.parentNode);
if (p_searchable) Element.hide(p_searchable.parentNode); if (p_searchable) Element.hide(p_searchable.parentNode);
Element.hide(p_values.parentNode); Element.hide(p_values);
break; break;
case "date": case "date":
Element.hide(p_length.parentNode); Element.hide(p_length.parentNode);
Element.hide(p_regexp.parentNode); Element.hide(p_regexp.parentNode);
if (p_searchable) Element.hide(p_searchable.parentNode); if (p_searchable) Element.hide(p_searchable.parentNode);
Element.hide(p_values.parentNode); Element.hide(p_values);
break; break;
case "float": case "float":
case "int": case "int":
Element.show(p_length.parentNode); Element.show(p_length.parentNode);
Element.show(p_regexp.parentNode); Element.show(p_regexp.parentNode);
if (p_searchable) Element.hide(p_searchable.parentNode); if (p_searchable) Element.hide(p_searchable.parentNode);
Element.hide(p_values.parentNode); Element.hide(p_values);
break;
case "user":
case "version":
Element.hide(p_length.parentNode);
Element.hide(p_regexp.parentNode);
if (p_searchable) Element.hide(p_searchable.parentNode);
Element.hide(p_values.parentNode);
Element.hide(p_default.parentNode);
break; break;
default: default:
Element.show(p_length.parentNode); Element.show(p_length.parentNode);
Element.show(p_regexp.parentNode); Element.show(p_regexp.parentNode);
if (p_searchable) Element.show(p_searchable.parentNode); if (p_searchable) Element.show(p_searchable.parentNode);
Element.hide(p_values.parentNode); Element.hide(p_values);
break; break;
} }
} }
@@ -62,16 +54,16 @@ function toggle_custom_field_format() {
<div class="box"> <div class="box">
<p><%= f.text_field :name, :required => true %></p> <p><%= f.text_field :name, :required => true %></p>
<p><%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :onchange => "toggle_custom_field_format();", <p><%= f.select :field_format, custom_field_formats_for_select, {}, :onchange => "toggle_custom_field_format();",
:disabled => !@custom_field.new_record? %></p> :disabled => !@custom_field.new_record? %></p>
<p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label> <p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
<%= f.text_field :min_length, :size => 5, :no_label => true %> - <%= f.text_field :min_length, :size => 5, :no_label => true %> -
<%= f.text_field :max_length, :size => 5, :no_label => true %><br>(<%=l(:text_min_max_length_info)%>)</p> <%= f.text_field :max_length, :size => 5, :no_label => true %><br>(<%=l(:text_min_max_length_info)%>)</p>
<p><%= f.text_field :regexp, :size => 50 %><br>(<%=l(:text_regexp_info)%>)</p> <p><%= f.text_field :regexp, :size => 50 %><br>(<%=l(:text_regexp_info)%>)</p>
<p> <p id="custom_field_possible_values"><%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"),
<%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %> :cols => 20,
<br /><em><%= l(:text_custom_field_possible_values_info) %></em> :rows => 15 %>
</p> <br /><em><%= l(:text_custom_field_possible_values_info) %></em></p>
<p><%= @custom_field.field_format == 'bool' ? f.check_box(:default_value) : f.text_field(:default_value) %></p> <p><%= @custom_field.field_format == 'bool' ? f.check_box(:default_value) : f.text_field(:default_value) %></p>
<%= call_hook(:view_custom_fields_form_upper_box, :custom_field => @custom_field, :form => f) %> <%= call_hook(:view_custom_fields_form_upper_box, :custom_field => @custom_field, :form => f) %>
</div> </div>
+20 -11
View File
@@ -1,11 +1,11 @@
<% @gantt.view = self %> <% @gantt.view = self %>
<h2><%= @query.new_record? ? l(:label_gantt) : h(@query.name) %></h2> <h2><%= l(:label_gantt) %></h2>
<% form_tag({:controller => 'gantts', :action => 'show', :project_id => @project, :month => params[:month], :year => params[:year], :months => params[:months]}, :method => :get, :id => 'query_form') do %> <% form_tag(gantt_path(:month => params[:month], :year => params[:year], :months => params[:months]), :method => :put, :id => 'query_form') do %>
<%= hidden_field_tag 'set_filter', '1' %> <%= hidden_field_tag('project_id', @project.to_param) if @project%>
<fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>"> <fieldset id="filters" class="collapsible">
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend> <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
<div style="<%= @query.new_record? ? "" : "display: none;" %>"> <div>
<%= render :partial => 'queries/filters', :locals => {:query => @query} %> <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
</div> </div>
</fieldset> </fieldset>
@@ -22,8 +22,17 @@
<%= select_year(@gantt.year_from, :prefix => "year", :discard_type => true) %> <%= select_year(@gantt.year_from, :prefix => "year", :discard_type => true) %>
<%= hidden_field_tag 'zoom', @gantt.zoom %> <%= hidden_field_tag 'zoom', @gantt.zoom %>
<%= link_to_function l(:button_apply), '$("query_form").submit()', :class => 'icon icon-checked' %> <%= link_to_remote l(:button_apply),
<%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 }, :class => 'icon icon-reload' %> { :url => { :set_filter => (@query.new_record? ? 1 : nil) },
:update => "content",
:with => "Form.serialize('query_form')"
}, :class => 'icon icon-checked' %>
<%= link_to_remote l(:button_clear),
{ :url => { :project_id => @project, :set_filter => (@query.new_record? ? 1 : nil) },
:method => :put,
:update => "content",
}, :class => 'icon icon-reload' if @query.new_record? %>
</p> </p>
<% end %> <% end %>
@@ -169,14 +178,14 @@ if Date.today >= @gantt.date_from and Date.today <= @gantt.date_to %>
<table width="100%"> <table width="100%">
<tr> <tr>
<td align="left"><%= link_to_content_update('&#171; ' + l(:label_previous), params.merge(@gantt.params_previous)) %></td> <td align="left"><%= link_to_remote ('&#171; ' + l(:label_previous)), {:url => @gantt.params_previous, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'}, {:href => url_for(@gantt.params_previous)} %></td>
<td align="right"><%= link_to_content_update(l(:label_next) + ' &#187;', params.merge(@gantt.params_next)) %></td> <td align="right"><%= link_to_remote (l(:label_next) + ' &#187;'), {:url => @gantt.params_next, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'}, {:href => url_for(@gantt.params_next)} %></td>
</tr> </tr>
</table> </table>
<% other_formats_links do |f| %> <% other_formats_links do |f| %>
<%= f.link_to 'PDF', :url => params.merge(@gantt.params) %> <%= f.link_to 'PDF', :url => @gantt.params %>
<%= f.link_to('PNG', :url => params.merge(@gantt.params)) if @gantt.respond_to?('to_image') %> <%= f.link_to('PNG', :url => @gantt.params) if @gantt.respond_to?('to_image') %>
<% end %> <% end %>
<% end # query.valid? %> <% end # query.valid? %>

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