Compare commits

..
179 Commits
Author SHA1 Message Date
Jean-Philippe Lang 7e6d1b6c35 Version set to 1.2.3.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8178 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 14:49:48 +00:00
Jean-Philippe Lang 5c4b618bce CHANGELOG updated.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8177 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 14:49:10 +00:00
Jean-Philippe Lang 2f87b3bae3 Merged r7920, r7921, r7922 and r7924 from trunk (#9405).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8158 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 12:04:47 +00:00
Jean-Philippe Lang 1848fcd91e Merged r8103 from trunk (#9737).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8157 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 11:56:13 +00:00
Jean-Philippe Lang 40a14cafbc Merged r7809 from trunk (#9567).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8120 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 21:58:58 +00:00
Jean-Philippe Lang f252108f30 Merged r7834 from trunk (#9566).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8119 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 21:57:55 +00:00
Jean-Philippe Lang 6d57380712 Merged r7780 from trunk (#9552).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8118 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 21:56:49 +00:00
Jean-Philippe Lang 00d1b230e0 Merged r7814 from trunk (#9577).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8117 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 21:54:05 +00:00
Jean-Philippe Lang d19a0b70e0 Merged r7971 from trunk (#9682).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8116 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 21:52:31 +00:00
Jean-Philippe Lang 535b5ccf2b Merged r7919 from trunk (#9448).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8115 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 21:49:52 +00:00
Toshi MARUYAMA a441667b8d Merged r8106 from trunk
remove trailing white-spaces from calendar-hr.js

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

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8107 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 01:21:06 +00:00
Toshi MARUYAMA c8c48a7816 Merged r8002 from trunk
Bulgarian translation updated by Ivan Cenov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8083 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-04 22:36:12 +00:00
Jean-Philippe Lang fa894328bb Merged r7983 and r7984 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@8000 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-29 21:17:41 +00:00
Jean-Philippe Lang 63e2e52dd3 Merged r7782 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7999 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-29 21:14:24 +00:00
Jean-Philippe Lang ff75a959e0 Merged r7779 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7998 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-29 21:12:58 +00:00
Jean-Philippe Lang 5d1388db02 Merged r7985 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7997 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-29 21:11:54 +00:00
Jean-Philippe Lang 3e4d1fb04f Backported r7982 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7996 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-29 21:10:45 +00:00
Jean-Philippe Lang 2858979b69 Merged r7956 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7995 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-29 21:08:06 +00:00
Etienne Massip be09ba039b Merged r7837 from trunk (#9597).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7838 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-18 22:30:32 +00:00
Toshi MARUYAMA a5adecb40c Merge r7797 from trunk
Simplified Chinese "text_git_repository_note" translation updated by Steven Wong.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7801 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-13 06:38:41 +00:00
Toshi MARUYAMA f328373ffd Merge r7796 from trunk
Simplified Chinese translation for 1.2.2 updated by Steven Wong.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7800 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-13 06:38:08 +00:00
Toshi MARUYAMA 367ad46911 Merge r7795 from trunk
Simplified Chinese translation changed by Steven Wong.

* field_due_date
* permission_rename_wiki_pages
* button_rename

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7799 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-13 06:37:33 +00:00
Toshi MARUYAMA 909cb9ab78 Merge r6699 from trunk
remove trailing white-spaces from config/locales/zh.yml

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7793 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-13 03:46:53 +00:00
Toshi MARUYAMA 8c2869f521 Merge r6698 from trunk
Ruby 1.9: fix parsing error of zh.yml with psych yaml library

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7792 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-13 03:46:05 +00:00
Jean-Philippe Lang 20e54f46b0 Merged r7776 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7777 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-11 12:53:35 +00:00
Jean-Philippe Lang 88e918f64d Merged r7774 from trunk (#8615).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7775 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-11 12:44:13 +00:00
Jean-Philippe Lang b6f0d9da8a Merged r7772 from trunk (#8615).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7773 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-11 12:33:15 +00:00
Jean-Philippe Lang d6bf26ace7 Merged r6308 from trunk (#8884).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7770 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-11 11:44:10 +00:00
Jean-Philippe Lang 21910aa428 Merged r6311 from trunk (#8880).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7769 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-11 11:40:56 +00:00
Jean-Philippe Lang f22ff1f1c2 Merged r6298 from trunk (#8865).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7768 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-11 11:37:55 +00:00
Jean-Philippe Lang 83a2283b7d Merged r6300 and r6302 from trunk (#8836).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7767 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-11 11:36:52 +00:00
Jean-Philippe Lang 03b2162e6c Merged r6324 and r6325 from trunk (#8651).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7766 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-11 11:35:03 +00:00
Jean-Philippe Lang c605477da5 Merged r6314 from trunk (#8633).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7765 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-11 11:31:49 +00:00
Toshi MARUYAMA 35ec8e3570 Merged r7731 from trunk
Spanish translation updated by Ismael G Marin C.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7732 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-05 06:57:52 +00:00
Toshi MARUYAMA 6e68bf8ab4 merge r7707 from trunk
add unit test of escaping image urls

Contributed by Holger Just.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7709 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-02 07:41:12 +00:00
Toshi MARUYAMA d181a2221a merge r6759 from trunk
remove trailing white-spaces from test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7708 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-02 07:40:49 +00:00
Toshi MARUYAMA e4034bc74b backed out previous 1.2-stable dummy commit
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7703 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-30 03:29:14 +00:00
Toshi MARUYAMA 310d4c52ce dummy commit to run test on CI server
Tests fail on CI Server, but I don't know the reason.
http://www.redmine.org/builds/build_1.2-stable-1.8.7-sqlite3_82.html

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7702 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-30 03:28:46 +00:00
Toshi MARUYAMA 1d1b3fc77d Merged r7699 from trunk
Dutch translation updated by Pieter Nicolai

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7700 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-30 00:05:29 +00:00
Toshi MARUYAMA 5e58434567 Merged r7660 from trunk
scm: git: fix typo of comments about fetching revisions

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7661 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-27 01:37:09 +00:00
Toshi MARUYAMA 8da22803be Merged r7658 from trunk
scm: git: recovery and improve comments of fetching from 1.1
about harmful influence that git does not have the revision number.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7659 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-27 00:41:37 +00:00
Toshi MARUYAMA 83ab216d70 Merged r7650 from trunk
scm: mercurial: drop supporting below Mercurial 1.1

On November 1st 2011, Mercurial 2.0 will be released.

On Mercurial 1.1.2, unit lib test fails with following error.

<pre>
Traceback (most recent call last):
  File "/WEB-DOWN/hg-repo/hg-crew/hg", line 20, in <module>
    mercurial.dispatch.run()
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 20, in run
    sys.exit(dispatch(sys.argv[1:]))
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 29, in dispatch
    return _runcatch(u, args)
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 45, in _runcatch
    return _dispatch(ui, args)
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 367, in _dispatch
    ret = _runcommand(ui, options, cmd, d)
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 416, in _runcommand
    return checkargs()
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 376, in checkargs
    return cmdfunc()
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 361, in <lambda>
    d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/util.py", line 715, in check
    return func(*args, **kwargs)
  File "/REDMINE-1/hg-workdir/redmine-bb-all/lib/redmine/scm/adapters/mercurial/redminehelper.py", line 149, in rhlog
    if hg.util.version() >= '1.6':
AttributeError: 'module' object has no attribute 'version'

  1) Error:
test_nodes_in_branch(MercurialAdapterTest):
Redmine::Scm::Adapters::MercurialAdapter::HgCommandAborted: hg exited with non-zero status: 1
    lib/redmine/scm/adapters/mercurial_adapter.rb:306:in `hg'
    lib/redmine/scm/adapters/mercurial_adapter.rb:234:in `nodes_in_branch'
    test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb:311:in `test_nodes_in_branch'
    test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb:304:in `each'
    test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb:304:in `test_nodes_in_branch'
</pre>

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7651 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-25 07:20:14 +00:00
Toshi MARUYAMA 4104741383 Merged r7643 from trunk
scm: mercurial: skip failing unit model tests on below Mercurial 1.5

Tests of non ASCII nor alphabetic nor numeric *named branch* fails on below Mercurial 1.5.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7647 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-24 11:29:38 +00:00
Toshi MARUYAMA 6dac4a499f Merged r7642 from trunk
scm: mercurial: skip failing unit lib tests on below Mercurial 1.5

Tests of non ASCII nor alphabetic nor numeric *named branch* fails on below Mercurial 1.5.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7646 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-24 11:28:51 +00:00
Toshi MARUYAMA e0a8442b09 Merged r7641 from trunk
scm: mercurial: switch rev parameter of extension rhlog() if above Mercurial 1.6 or not

On Mercurial 1.5, following error raises.

<pre>
hg --config extensions.redminehelper=lib/redmine/scm/adapters/mercurial/redminehelper.py \
  --rhbranch default --from default --to 0

abort: unknown revision '"default"'!
</pre>

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7645 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-24 11:28:19 +00:00
Toshi MARUYAMA 6dd11c9558 Merged r7640 from trunk
scm: mercurial: fix extension cmdtable on Mercurial 1.5

Before Mercurial revision "40c06bbf58be":http://www.selenic.com/repo/hg-stable/rev/40c06bbf58be ,
following error raises.

<pre>
Traceback (most recent call last):
  File "/WEB-DOWN/hg-repo/hg-crew/hg", line 27, in <module>
    mercurial.dispatch.run()
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 16, in run
    sys.exit(dispatch(sys.argv[1:]))
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 30, in dispatch
    return _runcatch(u, args)
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 47, in _runcatch
    return _dispatch(ui, args)
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 398, in _dispatch
    cmd, func, args, options, cmdoptions = _parse(lui, args)
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/dispatch.py", line 277, in _parse
    args = fancyopts.fancyopts(args, c, cmdoptions, True)
  File "/WEB-DOWN/hg-repo/hg-crew/mercurial/fancyopts.py", line 62, in fancyopts
    for short, name, default, comment in options:
ValueError: too many values to unpack

  1) Error:
test_nodes_in_branch(MercurialAdapterTest):
Redmine::Scm::Adapters::MercurialAdapter::HgCommandAborted: hg exited with non-zero status: 1
    lib/redmine/scm/adapters/mercurial_adapter.rb:306:in `hg'
    lib/redmine/scm/adapters/mercurial_adapter.rb:234:in `nodes_in_branch'
    test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb:311:in `test_nodes_in_branch'
    test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb:304:in `each'
    test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb:304:in `test_nodes_in_branch'
</pre>

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7644 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-24 11:27:38 +00:00
Toshi MARUYAMA 1608ca3d98 Merged r7579 from trunk.
Restrict anonymous read access with Redmine.pm

Redmine.pm now also checks for public projects whether the anonymous
user has the browse_repository right for a read operation.

Contributed by Holger Just.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7580 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-04 21:52:08 +00:00
Etienne Massip 0ef89ee4ea Merged r7570 from trunk (#9245).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7578 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-04 19:29:00 +00:00
Toshi MARUYAMA 4813672728 Merged r7571 from trunk.
Fix typo of fix_issue() at Changeset model.

Contributed by Sylvain Utard.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7572 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-04 10:55:32 +00:00
Etienne Massip ade5409310 Merged r7568 from trunk (#9308).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7569 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-03 20:19:44 +00:00
Etienne Massip 605f1745ff Merged #7563 from trunk (#7215).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7564 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-02 17:28:33 +00:00
Etienne Massip 8158a26ee8 Merged #7560 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7561 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-02 15:35:39 +00:00
Etienne Massip 4682acf040 Merged r7558 from trunk (#3276).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7559 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-10-02 14:52:43 +00:00
Etienne Massip 3955f81b3f Merged r7538 from trunk (#8411).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7539 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-26 17:47:50 +00:00
Etienne Massip 16f56a4677 Merged r7458 from trunk (#7613).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7482 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-23 17:23:22 +00:00
Toshi MARUYAMA f77b83451b Merged r7468 from trunk.
Norwegian translation of trunk r5957 and 1.2-stable r7455 updated.

Contributed by Lars Erik Gullerud.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7471 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-23 10:45:28 +00:00
Toshi MARUYAMA 2f2a93c6ef Merged r7451 and r7454 from trunk.
Slovenian translation updated.

Contributed by Nejc Vidmar.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7455 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-22 14:06:18 +00:00
Toshi MARUYAMA 5c282222a0 Backed out r7442.
Merged r7439 from trunk.

Slovenian translation of trunk r5957 and 1.2-stable r7434 updated.

Contributed by Nejc Vidmar.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7449 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-22 12:28:30 +00:00
Toshi MARUYAMA 61b4f41539 Merged r7439 from trunk.
Slovenian translation of trunk r5957 and 1.2-stable r7434 updated.

Contributed by Nejc Vidmar.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7442 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-22 01:52:04 +00:00
Toshi MARUYAMA 73e27a48e0 Merged r7432 from trunk.
fix typos in lib/redmine/hook.rb.

Contributed by Igor Zubkov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7434 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-21 09:57:26 +00:00
Toshi MARUYAMA 51d7c568b0 Merged r7407 from trunk.
remove trailing white-spaces from lib/redmine/hook.rb

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7433 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-21 09:56:52 +00:00
Toshi MARUYAMA 51d772de70 Merged r7068 from trunk.
fix typo in imap examples at lib/tasks/email.rake.

Contributed by Nathan L Smith.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7069 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-07 01:07:43 +00:00
Toshi MARUYAMA 86845b1989 Merged r7060 from trunk.
add "ruby test/unit/issue_test.rb" for an each test at doc/RUNNING_TESTS.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7061 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-05 11:45:16 +00:00
Toshi MARUYAMA 723d60a1ce Merge r7033 from trunk.
remove duplicate "test_index_should_not_list_issues_when_module_disabled" from functional issues controller test.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7038 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-02 01:30:33 +00:00
Toshi MARUYAMA b128fcaf0d back out r7036.
I mistook merged revision of the revision comment.
r7035 is wrong. r7033 is correct.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7037 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-02 01:29:54 +00:00
Toshi MARUYAMA 73dd0c49dd Merge r7035 from trunk.
remove duplicate "test_index_should_not_list_issues_when_module_disabled" from functional issues controller test.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@7036 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-09-02 01:15:12 +00:00
Toshi MARUYAMA cc7274f38c Merge r6915 from trunk.
fix grammatical error of field_warn_on_leaving_unsaved in pt-BR.yml.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6916 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-31 14:59:35 +00:00
Toshi MARUYAMA d0f3b8fa4d Merge r6600 from trunk.
Hungarian translation for 1.2.1 updated by Csaba Molnár.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6601 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-24 12:56:36 +00:00
Toshi MARUYAMA 2302955bc5 Merge r6021 from trunk (#8825).
scm: fix JRuby tests fail in unit changeset following test.

* test_invalid_utf8_sequences_in_paths_should_be_replaced

Following Subversion functional tests fail on Japanese Windows + JRuby 1.6.2 (ruby-1.8.7-p330).

* test_directory_diff
* test_revision_diff

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6453 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-17 11:14:03 +00:00
Toshi MARUYAMA 90f83db148 Merged r6005 from trunk.
scm: git: skip non UTF-8 path encoding test of unit model test in JRuby.

Git, Mercurial and CVS path encodings are binary.
Subversion supports URL encoding for path.
Redmine Mercurial adapter and extension use URL encoding.
Git accepts only binary path in command line parameter.
So, there is no way to use binary command line parameter in JRuby.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6452 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-17 08:16:23 +00:00
Toshi MARUYAMA b6dbf26c86 Merged r6004 from trunk.
scm: git: skip non UTF-8 path encoding test of unit adapter test in JRuby.

Git, Mercurial and CVS path encodings are binary.
Subversion supports URL encoding for path.
Redmine Mercurial adapter and extension use URL encoding.
Git accepts only binary path in command line parameter.
So, there is no way to use binary command line parameter in JRuby.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6451 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-17 08:16:02 +00:00
Toshi MARUYAMA 8c7e0e279c Merged r6003 from trunk.
scm: git: skip non UTF-8 path encoding test of functional test in JRuby.

Git, Mercurial and CVS path encodings are binary.
Subversion supports URL encoding for path.
Redmine Mercurial adapter and extension use URL encoding.
Git accepts only binary path in command line parameter.
So, there is no way to use binary command line parameter in JRuby.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6450 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-17 08:15:37 +00:00
Toshi MARUYAMA 6f15634e12 Merged r6447 from trunk.
scm: add comment that configuration of SCM executable command does not work if contains spaces path to configuration.yml.example.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6449 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-16 00:18:14 +00:00
Toshi MARUYAMA 7e837aa34d Merged r6418 from trunk.
remove trailing white-spaces from config/configuration.yml.example.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6448 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-16 00:17:56 +00:00
Toshi MARUYAMA 6c89118bac Merged r6441 from trunk.
PDF: remove unused vendor/plugins/rfpdf/lib/barcode/*.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6443 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-12 09:57:07 +00:00
Toshi MARUYAMA 260c07eb73 Merged r6435 from trunk.
PDF: remove unused sjis.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6442 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-12 08:29:47 +00:00
Toshi MARUYAMA 7914381cdd Merged r6434 from trunk.
PDF: remove unused zapfdingbats.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6440 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-12 07:25:48 +00:00
Toshi MARUYAMA 65ce05326a Merged r6433 from trunk.
PDF: remove unused symbol.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6439 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-12 07:25:06 +00:00
Toshi MARUYAMA 112c7a1b15 Merged r6432 from trunk.
PDF: remove unused courier.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6438 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-12 07:24:27 +00:00
Toshi MARUYAMA 23a31dfe27 Merged r6431 from trunk.
PDF: remove unused times*.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6437 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-12 07:23:43 +00:00
Toshi MARUYAMA 0dc50cb0b1 Merged r6430 from trunk.
PDF: remove unused vera*.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6436 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-12 07:23:00 +00:00
Toshi MARUYAMA 88163849b4 Merged r6427 from trunk.
PDF: remove unused vera fonts.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6429 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-12 05:27:03 +00:00
Toshi MARUYAMA 058f36cc6f Merged r6426 from trunk.
PDF: remove unused FreeMono fonts.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6428 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-12 05:26:17 +00:00
Toshi MARUYAMA 11cf38a1a8 Merged r6402 from trunk.
PDF: remove unused DejaVu fonts except DejaVuSans-BoldOblique.

FreeSans has freesansbi.rb. But there is no dejavusansbi.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6408 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-06 00:02:17 +00:00
Toshi MARUYAMA 37d7a9d62d Merged r6401 from trunk.
PDF: remove unused DejaVuSans-ExtraLight font.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6407 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-06 00:01:47 +00:00
Toshi MARUYAMA 1b299369bf Merged r6403 from trunk.
Italian translation for 1.2 updated.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6405 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-08-05 11:24:32 +00:00
Toshi MARUYAMA cbe42633c9 Merged r6282 from trunk.
scm: bazaar: use "shell_quote_command" method at adapter for JRuby + Windows command name.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6294 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 23:48:38 +00:00
Toshi MARUYAMA 25657168b1 Merged r6281 from trunk.
scm: git: use self.class.sq_bin for command name at adpter scm_cmd().

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6293 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 23:48:06 +00:00
Toshi MARUYAMA 4852b38efc Merged r6280 from trunk.
scm: git: use "shell_quote_command" method at adapter for JRuby + Windows command name.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6292 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 23:47:34 +00:00
Toshi MARUYAMA b882993d6a Merged r6279 from trunk.
scm: mercurial: use self.class.sq_bin for command name at adpter scm_cmd().

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6291 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 23:47:08 +00:00
Toshi MARUYAMA b6c46aa19d Merged r6278 from trunk.
scm: mercurial: use "shell_quote_command" method at adapter for JRuby + Windows command name.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6290 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 23:46:35 +00:00
Toshi MARUYAMA 0903db52d9 Merged r6277 from trunk.
scm: cvs: use self.class.sq_bin for command name at adpter scm_cmd().

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6289 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 23:46:01 +00:00
Toshi MARUYAMA 93d256c4a8 Merged r6276 from trunk.
scm: cvs: use "shell_quote_command" method at adapter for JRuby + Windows command name.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6288 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 23:45:26 +00:00
Toshi MARUYAMA 64a68b86c6 Merged r6275 from trunk.
scm: darcs: use "shell_quote_command" method at adapter for JRuby + Windows command name.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6287 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 23:44:44 +00:00
Toshi MARUYAMA 1d79bbce44 Merged r6274 from trunk.
scm: subversion: use "shell_quote_command" method at adapter for JRuby + Windows command name.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6286 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 23:44:07 +00:00
Toshi MARUYAMA 87211bcb75 Merged r6272 from trunk.
scm: add new method "shell_quote_command" at abstract adapter.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6285 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 23:43:22 +00:00
Toshi MARUYAMA 68e97858cb Merge(backport) r6230 from trunk (#8825).
scm: catch all exceptions at adapter shellout() to fork scm command.

If scm command does not exist,
Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException in production environment.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6273 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-15 11:53:39 +00:00
Jean-Philippe Lang f5a6b0cf93 Updates for 1.2.1 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6261 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-11 11:45:14 +00:00
Toshi MARUYAMA 3cf0bd713d Merged r6247 from trunk.
scm: mercurial: add functional test of destroying invalid repository.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6255 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-11 02:08:47 +00:00
Toshi MARUYAMA 36bce52f63 Merged r6246 from trunk.
scm: mercurial: do nothing in fetching if info is nil.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6254 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-11 02:08:23 +00:00
Toshi MARUYAMA 94f5c73746 Merged r6245 from trunk.
scm: mercurial: catch exception and return nil during getting info.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6253 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-11 02:08:03 +00:00
Toshi MARUYAMA 8b6fd63a59 Merged r6243 from trunk.
scm: mercurial: add functional test of destroying valid repository.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6252 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-11 02:07:44 +00:00
Toshi MARUYAMA ba6b822e97 Merged r6242 from trunk.
scm: mercurial: add instance variable @project at functional test.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6251 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-11 02:07:19 +00:00
Toshi MARUYAMA b44044a76a Merge(backport) r6130 from trunk (#8777).
scm: return nil at model default_branch and override at git model.

Redmine Git and Mercurial adapter support *branches*.
Mercurial default branch is *dafault*.
But, it is popular to show all revisions on the top page for Mercurial GUI.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6244 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-11 00:24:22 +00:00
Jean-Philippe Lang eb3a4587c9 Merged r6208 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6209 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-09 18:49:13 +00:00
Jean-Philippe Lang a8550ba8a6 Merged r6206 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6207 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-09 12:01:56 +00:00
Jean-Philippe Lang 0527e8cedf Merged r6203 and r6204 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6205 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-09 11:42:12 +00:00
Jean-Philippe Lang 2a799e8367 Merged r6201 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6202 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-09 11:26:35 +00:00
Jean-Philippe Lang 3e9ad22fad Merged r6199 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6200 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-09 10:51:57 +00:00
Jean-Philippe Lang a5bcdf6d2c Backported r6197 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6198 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-09 09:11:13 +00:00
Jean-Philippe Lang 3dcd5d746a Merged r6187 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6188 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-06 19:06:03 +00:00
Jean-Philippe Lang 971aa889f9 Merged r6163, r6171, r6172, r6174 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6175 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-03 15:58:02 +00:00
Jean-Philippe Lang cdc1dfb1e7 Merged r6170 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6173 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-03 15:37:01 +00:00
Jean-Philippe Lang 30255b8054 Merged r6073 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6162 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-03 10:18:18 +00:00
Jean-Philippe Lang 97b697a678 Merged r6126 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6161 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-02 17:43:08 +00:00
Jean-Philippe Lang 8e87cd6f77 Merged r6147 and r6149 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6160 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-02 17:29:54 +00:00
Jean-Philippe Lang 65bee7efa3 Merged r6148 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6159 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-02 17:28:33 +00:00
Jean-Philippe Lang 2325831d81 Merged r5991 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6158 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-02 17:28:00 +00:00
Jean-Philippe Lang c7f8ea6a37 Merged r5969 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6157 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-02 17:25:51 +00:00
Toshi MARUYAMA edc4f6d54b PDF: add dummy 'fill' parameter at rfpdf CJK Write method (#8737).
'fill' parameter is already added in trunk by r6131, r6132, r6133.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6156 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-02 15:44:02 +00:00
Toshi MARUYAMA bf0046b5af PDF: fix ArgumentError MultiCell() in CJK (#8737).
add dummy ln parameter at rfpdf CJK MultiCell method.
ln parameter has already added in trunk by r6131, r6132, r6133.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6155 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-02 14:59:30 +00:00
Toshi MARUYAMA 3de4afe53a Merged r6146 from trunk.
PDF: add revision number/id of associated revisions on issue PDF.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6153 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-02 13:15:54 +00:00
Toshi MARUYAMA d15912eb6a Merge(backport) r6136 from trunk (#8737).
PDF: use RDMMultiCell for drawing lines of issue description.

Contributed by Jun NAITOH.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6145 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-02 04:48:07 +00:00
Toshi MARUYAMA 656e651c33 Merge(backport) r6135 from trunk (#8737).
PDF: add 'ln' parameter for drawing lines at RDMMultiCell().

Contributed by Jun NAITOH.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6144 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-02 04:47:27 +00:00
Toshi MARUYAMA 9543e0a103 Merged r6138 from trunk.
PDF: code clean up lib/redmine/export/pdf.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6143 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-02 04:46:47 +00:00
Toshi MARUYAMA 441f2f216f Merged r6139 from trunk.
Swedish translation updated by Nicklas Holm.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6140 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-07-01 09:02:37 +00:00
Toshi MARUYAMA 15228c9958 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.2-stable@6128 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-27 23:19:41 +00:00
Jean-Baptiste Barth 5f33a09320 Merged r6124 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6125 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-26 12:51:09 +00:00
Jean-Baptiste Barth 7d77c3544a Merged r6098 from trunk.
Added Project#enable_module! and Project#disable_module!

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6106 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-21 03:35:36 +00:00
Jean-Baptiste Barth 102c9afadf Revert r6104 "Merged r6098 from trunk."
I messed things up with a cherry-pick on master, I'll re-apply it after.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6105 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-21 03:16:49 +00:00
Jean-Baptiste Barth 16ba6bcb77 Merged r6098 from trunk.
Added Project#enable_module! and Project#disable_module!

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6104 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-20 19:39:59 +00:00
Toshi MARUYAMA df37e5cfab Merged r6101 from trunk.
Russian translation update by SERGEY ERSHOV.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6102 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-20 08:54:23 +00:00
Jean-Baptiste Barth f58b86bbb8 Merged r6099 from trunk.
Added a test to ensure 'Project' column can be removed on issues list

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6100 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-20 01:32:18 +00:00
Toshi MARUYAMA 95f68ba6d9 Merged r6096 from trunk.
pt-BR translation update by Enderson Maia.

Better translation to keep a pattern in translations.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6097 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-18 10:17:16 +00:00
Toshi MARUYAMA 171c4b2747 Merged r6094 from trunk.
German translation updated by Jens Martsch.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6095 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-17 07:13:51 +00:00
Toshi MARUYAMA 860a6ce78d Merged r6068 from trunk.
pt-BR translation update by Rodrigo Rosenfeld Rosas.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6069 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-11 21:15:05 +00:00
Toshi MARUYAMA 26d42c26fa Merged r6064 from trunk.
translate field_path_to_repository in pt-BR.yml.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6067 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-11 01:34:32 +00:00
Toshi MARUYAMA c69c434609 Merged r6063 from trunk.
pt-BR translation update by Rodrigo Rosenfeld Rosas.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6066 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-11 01:33:51 +00:00
Toshi MARUYAMA 9381d30ae8 Merged r6062 from trunk.
pt-BR translation update by Enderson Maia.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6065 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-11 01:33:07 +00:00
Toshi MARUYAMA 136bf86307 Merged r6042 from trunk.
scm: mercurial: fix unit adapter annotate test fails on Windows Mercurial 1.8.4+29-e597ef52a7c2.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6046 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-10 08:33:23 +00:00
Toshi MARUYAMA 07f42c53d0 Merged r6041 from trunk.
scm: mercurial: add functional test of annotate file which does not exist in *tip* is not found.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6045 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-10 08:32:42 +00:00
Toshi MARUYAMA 22e143f0b3 Merged r6035 from trunk.
PDF: use DejaVuSans fonts in Vietnamese.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6037 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-10 02:15:47 +00:00
Toshi MARUYAMA 1701de9123 Merged r6034 from trunk.
PDF: back out r6018.

change Vietnamese vi.yml general_pdf_encoding from CP1258 to UTF-8.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6036 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-10 02:15:30 +00:00
Toshi MARUYAMA 0ce6793181 Merged r6031 from trunk.
PDF: fix font name in vendor/plugins/rfpdf/lib/fonts/dejavusansi.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6033 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-09 23:51:42 +00:00
Toshi MARUYAMA bacc44feab Merged r6030 from trunk.
PDF: Import DejaVu fonts.

http://rfpdf.googlecode.com/files/DejaVu.zip
Sep. 2007
fils size 3,685,721
md5sum: 54274d105b74f817d649035fc1541d8a

rename files.
* DejaVuSans.rb  to dejavusans.rb
* DejaVuSansb.rb to dejavusansb.rb
* DejaVuSansi.rb to dejavusansi.rb

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6032 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-09 23:44:34 +00:00
Toshi MARUYAMA 4f9a1cd614 Merged r6028 from trunk.
Spanish translation updated by Jorge López.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6029 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-09 08:06:24 +00:00
Toshi MARUYAMA 44ed963a46 Merged r6024 from trunk.
PDF: replace all non ASCII characters to '?' if Iconv error raise in JRuby.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6027 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-09 05:29:23 +00:00
Toshi MARUYAMA 113316cf1e Merged r6023 from trunk.
PDF: use SJIS instead of CP932 at unit pdf test test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_ja in JRuby.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6026 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-09 03:51:23 +00:00
Toshi MARUYAMA 17ed97d580 Merged r6022 from trunk.
PDF: use SJIS instead of CP932 at unit pdf test test_rdm_pdf_iconv_cannot_convert_ja_cp932 in JRuby.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6025 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-09 03:50:35 +00:00
Toshi MARUYAMA 19c52bc671 Merged r6018 from trunk.
PDF: change Vietnamese vi.yml general_pdf_encoding from UTF-8 to CP1258.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6019 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-09 00:04:11 +00:00
Toshi MARUYAMA d15de8ee12 Merged r6013 from trunk.
PDF: use SJIS instead of CP932 at unit pdf test_fix_text_encoding_nil in JRuby.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6017 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-08 23:53:42 +00:00
Toshi MARUYAMA 006c82267e Merged r6012 from trunk.
PDF: change Shift_JIS to SHIFT_JIS in lib/redmine/export/pdf.rb for Japanese ja.yml general_pdf_encoding.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6016 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-08 23:53:24 +00:00
Toshi MARUYAMA 887e7c6781 Merged r6011 from trunk.
PDF: add Japanese comment of ja.yml general_pdf_encoding *CP932* and *SJIS* in JRuby and CRuby (#8565, #61).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6015 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-08 23:53:03 +00:00
Toshi MARUYAMA 6280d9e9b2 Merged r6010 from trunk.
PDF: add *SJIS* and *Shift_JIS* in lib/redmine/export/pdf.rb for Japanese ja.yml general_pdf_encoding.

JRuby 1.6.2 (ruby-1.8.7-p330) does not support CP932.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6014 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-08 23:52:43 +00:00
Toshi MARUYAMA 3996d5f42e Merged r6007 from trunk.
scm: add log message of config/configuration.yml if scm command raise Errno::ENOENT exception.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6009 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-08 13:24:38 +00:00
Toshi MARUYAMA 579abc1802 Merged r6006 from trunk.
scm: code clean up abstract adapter.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6008 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-08 13:23:56 +00:00
Toshi MARUYAMA dc2efa503a Merged r5999 from trunk.
scm: catch all exceptions to get scm command version in repository model.

If scm command does not exist,
Linux jruby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException in production environment.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@6000 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-03 11:41:34 +00:00
Toshi MARUYAMA 0e6cdeb860 Merged r5992 and r5997 from trunk.
Turkish translation updated by Burak Yiğit Kaya.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5998 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-02 08:26:02 +00:00
Toshi MARUYAMA 3b9ef033bf Backout r5994 Turkish translations.
Tests on CI server fails.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5996 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-02 06:29:15 +00:00
Toshi MARUYAMA 2ae54a0fc1 Merged r5993 from trunk.
change Turkish general_csv_encoding from ISO-8859-1 to ISO-8859-9 by Burak Yiğit Kaya.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5995 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-01 22:55:23 +00:00
Toshi MARUYAMA a87e6e1265 Merged r5992 from trunk.
Turkish translation updated by Burak Yiğit Kaya.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5994 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-01 22:54:58 +00:00
Toshi MARUYAMA 61b1146f9d Merged r5986 from trunk.
German translation updated by Jens Martsch.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5987 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-06-01 08:09:23 +00:00
Toshi MARUYAMA c509c744fd Merged r5970 from trunk.
Traditional Chinese translation updated by ChunChang Lo.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5971 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-31 03:48:27 +00:00
Jean-Philippe Lang 1be21f2413 Merged r5965 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5967 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-30 16:40:52 +00:00
Jean-Philippe Lang eaf430b630 Merged r5964 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5966 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-30 16:40:17 +00:00
Toshi MARUYAMA d2789248f7 Merged r5962 from trunk.
Bulgarian translation updated by Ivan Cenov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5963 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-29 14:54:06 +00:00
Jean-Philippe Lang f08f09a9d3 Merged r5950 to r5960 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5961 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-29 12:06:05 +00:00
Toshi MARUYAMA 2c2760d041 Merged r5887 from trunk.
scm: replace a tab to two spaces in app/views/settings/_repositories.rhtml.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5959 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-29 11:32:16 +00:00
Toshi MARUYAMA 218e2f32ee Merged r5886 from trunk.
scm: remove trailing white-spaces from app/views/settings/_repositories.rhtml.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5958 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-29 11:31:48 +00:00
Jean-Philippe Lang f2b01aeb4d Merged r5948 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5949 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-29 08:15:18 +00:00
Jean-Philippe Lang fabf54a368 Merged r5944 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5947 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-29 07:47:25 +00:00
Jean-Philippe Lang 6b07f86883 Merged r5881 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5946 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-29 07:24:15 +00:00
Jean-Philippe Lang 36d2ea9a6c Merged r5880 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5945 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-29 07:18:05 +00:00
Toshi MARUYAMA 2dca48be53 Merged r5940 from trunk.
remove trailing white-spaces from rake redmine:email:test task source.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5943 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-28 03:34:47 +00:00
Toshi MARUYAMA 7678aa9445 Merged r5939 from trunk.
handle a nil user in rake redmine:email:test task.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5942 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-28 03:34:23 +00:00
Toshi MARUYAMA 9c355ee6c3 Merged r5938 from trunk.
use rake task parameter style message if redmine:email:test parameter is blank.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5941 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-28 03:33:55 +00:00
Jean-Philippe Lang 723030e40a Set version to stable.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5879 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-22 09:20:14 +00:00
Jean-Philippe Lang e6ed112d9d 1.2-stable branch added.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.2-stable@5878 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-05-22 09:17:45 +00:00
2044 changed files with 84055 additions and 98628 deletions
-17
View File
@@ -1,13 +1,10 @@
/.project /.project
/.loadpath /.loadpath
/.powrc
/.rvmrc
/config/additional_environment.rb /config/additional_environment.rb
/config/configuration.yml /config/configuration.yml
/config/database.yml /config/database.yml
/config/email.yml /config/email.yml
/config/initializers/session_store.rb /config/initializers/session_store.rb
/config/initializers/secret_token.rb
/coverage /coverage
/db/*.db /db/*.db
/db/*.sqlite3 /db/*.sqlite3
@@ -17,26 +14,12 @@
/lib/redmine/scm/adapters/mercurial/redminehelper.pyo /lib/redmine/scm/adapters/mercurial/redminehelper.pyo
/log/*.log* /log/*.log*
/log/mongrel_debug /log/mongrel_debug
/plugins/*
!/plugins/README
/public/dispatch.* /public/dispatch.*
/public/plugin_assets /public/plugin_assets
/public/themes/*
!/public/themes/alternate
!/public/themes/classic
!/public/themes/README
/tmp/* /tmp/*
/tmp/cache/* /tmp/cache/*
/tmp/pdf/*
/tmp/sessions/* /tmp/sessions/*
/tmp/sockets/* /tmp/sockets/*
/tmp/test/* /tmp/test/*
/tmp/thumbnails/*
/vendor/cache
/vendor/rails /vendor/rails
*.rbc *.rbc
/.bundle
/Gemfile.lock
/Gemfile.local
-12
View File
@@ -2,14 +2,11 @@ syntax: glob
.project .project
.loadpath .loadpath
.powrc
.rvmrc
config/additional_environment.rb config/additional_environment.rb
config/configuration.yml config/configuration.yml
config/database.yml config/database.yml
config/email.yml config/email.yml
config/initializers/session_store.rb config/initializers/session_store.rb
config/initializers/secret_token.rb
coverage coverage
db/*.db db/*.db
db/*.sqlite3 db/*.sqlite3
@@ -23,19 +20,10 @@ public/dispatch.*
public/plugin_assets public/plugin_assets
tmp/* tmp/*
tmp/cache/* tmp/cache/*
tmp/pdf/*
tmp/sessions/* tmp/sessions/*
tmp/sockets/* tmp/sockets/*
tmp/test/* tmp/test/*
tmp/thumbnails/*
vendor/cache
vendor/rails vendor/rails
*.rbc *.rbc
.svn/ .svn/
.git/ .git/
.bundle
Gemfile.lock
Gemfile.local
-8
View File
@@ -1,8 +0,0 @@
**Do not send a pull requst to this github repository**.
For more detail, please see [official website] wiki [Contribute].
[official website]: http://www.redmine.org
[Contribute]: http://www.redmine.org/projects/redmine/wiki/Contribute
-100
View File
@@ -1,100 +0,0 @@
source 'https://rubygems.org'
gem "rails", "3.2.13"
gem "jquery-rails", "~> 2.0.2"
gem "i18n", "~> 0.6.0"
gem "coderay", "~> 1.1.0"
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
gem "builder", "3.0.0"
# Optional gem for LDAP authentication
group :ldap do
gem "net-ldap", "~> 0.3.1"
end
# Optional gem for OpenID authentication
group :openid do
gem "ruby-openid", "~> 2.3.0", :require => "openid"
gem "rack-openid"
end
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
platforms :mri, :mingw do
group :rmagick do
# RMagick 2 supports ruby 1.9
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support
# different requirements for the same gem on different platforms
gem "rmagick", ">= 2.0.0"
end
end
platforms :jruby do
# jruby-openssl is bundled with JRuby 1.7.0
gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0'
gem "activerecord-jdbc-adapter", "~> 1.2.6"
end
# Include database gems for the adapters found in the database
# configuration file
require 'erb'
require 'yaml'
database_file = File.join(File.dirname(__FILE__), "config/database.yml")
if File.exist?(database_file)
database_config = YAML::load(ERB.new(IO.read(database_file)).result)
adapters = database_config.values.map {|c| c['adapter']}.compact.uniq
if adapters.any?
adapters.each do |adapter|
case adapter
when 'mysql2'
gem "mysql2", "~> 0.3.11", :platforms => [:mri, :mingw]
gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
when 'mysql'
gem "mysql", "~> 2.8.1", :platforms => [:mri, :mingw]
gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
when /postgresql/
gem "pg", ">= 0.11.0", :platforms => [:mri, :mingw]
gem "activerecord-jdbcpostgresql-adapter", :platforms => :jruby
when /sqlite3/
gem "sqlite3", :platforms => [:mri, :mingw]
gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
when /sqlserver/
gem "tiny_tds", "~> 0.5.1", :platforms => [:mri, :mingw]
gem "activerecord-sqlserver-adapter", :platforms => [:mri, :mingw]
else
warn("Unknown database adapter `#{adapter}` found in config/database.yml, use Gemfile.local to load your own database gems")
end
end
else
warn("No adapter found in config/database.yml, please configure it first")
end
else
warn("Please configure your config/database.yml first")
end
group :development do
gem "rdoc", ">= 2.4.2"
gem "yard"
end
group :test do
gem "shoulda", "~> 3.3.2"
gem "mocha", ">= 0.14", :require => 'mocha/api'
if RUBY_VERSION >= '1.9.3'
gem "capybara", "~> 2.1.0"
gem "selenium-webdriver"
gem "database_cleaner"
end
end
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
if File.exists?(local_gemfile)
puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v`
instance_eval File.read(local_gemfile)
end
# Load plugins' Gemfiles
Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file|
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
#TODO: switch to "eval_gemfile file" when bundler >= 1.2.0 will be required (rails 4)
instance_eval File.read(file), file
end
+1 -1
View File
@@ -2,4 +2,4 @@
Redmine is a flexible project management web application written using Ruby on Rails framework. Redmine is a flexible project management web application written using Ruby on Rails framework.
More details can be found in the doc directory or on the official website http://www.redmine.org More details can be found at in the doc directory or on the official website http://www.redmine.org
+7 -4
View File
@@ -1,7 +1,10 @@
#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake, # Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. # for example lib/tasks/switchtower.rake, and they will automatically be available to Rake.
require File.expand_path('../config/application', __FILE__) require(File.join(File.dirname(__FILE__), 'config', 'boot'))
RedmineApp::Application.load_tasks require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'tasks/rails'
+55 -114
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 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
@@ -20,53 +20,36 @@ class AccountController < ApplicationController
include CustomFieldsHelper include CustomFieldsHelper
# prevents login action to be filtered by check_if_login_required application scope filter # prevents login action to be filtered by check_if_login_required application scope filter
skip_before_filter :check_if_login_required, :check_password_change skip_before_filter :check_if_login_required
# Login request and validation # Login request and validation
def login def login
if request.get? if request.get?
if User.current.logged? logout_user
redirect_to home_url
end
else else
authenticate_user authenticate_user
end end
rescue AuthSourceException => e
logger.error "An error occured when authenticating #{params[:username]}: #{e.message}"
render_error :message => e.message
end end
# Log out current user and redirect to welcome page # Log out current user and redirect to welcome page
def logout def logout
if User.current.anonymous? logout_user
redirect_to home_url redirect_to home_url
elsif request.post?
logout_user
redirect_to home_url
end
# display the logout form
end end
# Lets user choose a new password # Enable user to choose a new password
def lost_password def lost_password
(redirect_to(home_url); return) unless Setting.lost_password? redirect_to(home_url) && return unless Setting.lost_password?
if params[:token] if params[:token]
@token = Token.find_token("recovery", params[:token].to_s) @token = Token.find_by_action_and_value("recovery", params[:token])
if @token.nil? || @token.expired? redirect_to(home_url) && return unless @token and !@token.expired?
redirect_to home_url
return
end
@user = @token.user @user = @token.user
unless @user && @user.active?
redirect_to home_url
return
end
if request.post? if request.post?
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
if @user.save if @user.save
@token.destroy @token.destroy
flash[:notice] = l(:notice_account_password_updated) flash[:notice] = l(:notice_account_password_updated)
redirect_to signin_path redirect_to :action => 'login'
return return
end end
end end
@@ -74,27 +57,17 @@ class AccountController < ApplicationController
return return
else else
if request.post? if request.post?
user = User.find_by_mail(params[:mail].to_s) user = User.find_by_mail(params[:mail])
# user not found # user not found in db
unless user (flash.now[:error] = l(:notice_account_unknown_email); return) unless user
flash.now[:error] = l(:notice_account_unknown_email) # user uses an external authentification
return (flash.now[:error] = l(:notice_can_t_change_password); return) if user.auth_source_id
end
unless user.active?
handle_inactive_user(user, lost_password_path)
return
end
# user cannot change its password
unless user.change_password_allowed?
flash.now[:error] = l(:notice_can_t_change_password)
return
end
# create a new token for password recovery # create a new token for password recovery
token = Token.new(:user => user, :action => "recovery") token = Token.new(:user => user, :action => "recovery")
if token.save if token.save
Mailer.lost_password(token).deliver Mailer.deliver_lost_password(token)
flash[:notice] = l(:notice_account_lost_email_sent) flash[:notice] = l(:notice_account_lost_email_sent)
redirect_to signin_path redirect_to :action => 'login'
return return
end end
end end
@@ -103,14 +76,12 @@ class AccountController < ApplicationController
# User self-registration # User self-registration
def register def register
(redirect_to(home_url); return) unless Setting.self_registration? || session[:auth_source_registration] redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
if request.get? if request.get?
session[:auth_source_registration] = nil session[:auth_source_registration] = nil
@user = User.new(:language => current_language.to_s) @user = User.new(:language => Setting.default_language)
else else
user_params = params[:user] || {} @user = User.new(params[:user])
@user = User.new
@user.safe_attributes = user_params
@user.admin = false @user.admin = false
@user.register @user.register
if session[:auth_source_registration] if session[:auth_source_registration]
@@ -121,13 +92,11 @@ class AccountController < ApplicationController
session[:auth_source_registration] = nil session[:auth_source_registration] = nil
self.logged_user = @user self.logged_user = @user
flash[:notice] = l(:notice_account_activated) flash[:notice] = l(:notice_account_activated)
redirect_to my_account_path redirect_to :controller => 'my', :action => 'account'
end end
else else
@user.login = params[:user][:login] @user.login = params[:user][:login]
unless user_params[:identity_url].present? && user_params[:password].blank? && user_params[:password_confirmation].blank? @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
@user.password, @user.password_confirmation = user_params[:password], user_params[:password_confirmation]
end
case Setting.self_registration case Setting.self_registration
when '1' when '1'
@@ -143,34 +112,29 @@ class AccountController < ApplicationController
# Token based account activation # Token based account activation
def activate def activate
(redirect_to(home_url); return) unless Setting.self_registration? && params[:token].present? redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
token = Token.find_token('register', params[:token].to_s) token = Token.find_by_action_and_value('register', params[:token])
(redirect_to(home_url); return) unless token and !token.expired? redirect_to(home_url) && return unless token and !token.expired?
user = token.user user = token.user
(redirect_to(home_url); return) unless user.registered? redirect_to(home_url) && return unless user.registered?
user.activate user.activate
if user.save if user.save
token.destroy token.destroy
flash[:notice] = l(:notice_account_activated) flash[:notice] = l(:notice_account_activated)
end end
redirect_to signin_path redirect_to :action => 'login'
end
# Sends a new account activation email
def activation_email
if session[:registered_user_id] && Setting.self_registration == '1'
user_id = session.delete(:registered_user_id).to_i
user = User.find_by_id(user_id)
if user && user.registered?
register_by_email_activation(user)
return
end
end
redirect_to(home_url)
end end
private private
def logout_user
if User.current.logged?
cookies.delete :autologin
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
self.logged_user = nil
end
end
def authenticate_user def authenticate_user
if Setting.openid? && using_open_id? if Setting.openid? && using_open_id?
open_id_authenticate(params[:openid_url]) open_id_authenticate(params[:openid_url])
@@ -180,7 +144,7 @@ class AccountController < ApplicationController
end end
def password_authentication def password_authentication
user = User.try_to_login(params[:username], params[:password], false) user = User.try_to_login(params[:username], params[:password])
if user.nil? if user.nil?
invalid_credentials invalid_credentials
@@ -188,31 +152,26 @@ class AccountController < ApplicationController
onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id }) onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id })
else else
# Valid user # Valid user
if user.active? successful_authentication(user)
successful_authentication(user)
else
handle_inactive_user(user)
end
end end
end end
def open_id_authenticate(openid_url) def open_id_authenticate(openid_url)
back_url = signin_url(:autologin => params[:autologin]) authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url) do |result, identity_url, registration|
authenticate_with_open_id(
openid_url, :required => [:nickname, :fullname, :email],
:return_to => back_url, :method => :post
) do |result, identity_url, registration|
if result.successful? if result.successful?
user = User.find_or_initialize_by_identity_url(identity_url) user = User.find_or_initialize_by_identity_url(identity_url)
if user.new_record? if user.new_record?
# Self-registration off # Self-registration off
(redirect_to(home_url); return) unless Setting.self_registration? redirect_to(home_url) && return unless Setting.self_registration?
# Create on the fly # Create on the fly
user.login = registration['nickname'] unless registration['nickname'].nil? user.login = registration['nickname'] unless registration['nickname'].nil?
user.mail = registration['email'] unless registration['email'].nil? user.mail = registration['email'] unless registration['email'].nil?
user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil? user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
user.random_password user.random_password
user.register user.register
case Setting.self_registration case Setting.self_registration
when '1' when '1'
register_by_email_activation(user) do register_by_email_activation(user) do
@@ -232,7 +191,7 @@ class AccountController < ApplicationController
if user.active? if user.active?
successful_authentication(user) successful_authentication(user)
else else
handle_inactive_user(user) account_pending
end end
end end
end end
@@ -240,7 +199,6 @@ class AccountController < ApplicationController
end end
def successful_authentication(user) def successful_authentication(user)
logger.info "Successful authentication for '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}"
# Valid user # Valid user
self.logged_user = user self.logged_user = user
# generate a key and set cookie if autologin # generate a key and set cookie if autologin
@@ -248,11 +206,12 @@ class AccountController < ApplicationController
set_autologin_cookie(user) set_autologin_cookie(user)
end end
call_hook(:controller_account_success_authentication_after, {:user => user }) call_hook(:controller_account_success_authentication_after, {:user => user })
redirect_back_or_default my_page_path redirect_back_or_default :controller => 'my', :action => 'page'
end end
def set_autologin_cookie(user) def set_autologin_cookie(user)
token = Token.create(:user => user, :action => 'autologin') token = Token.create(:user => user, :action => 'autologin')
cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin'
cookie_options = { cookie_options = {
:value => token.value, :value => token.value,
:expires => 1.year.from_now, :expires => 1.year.from_now,
@@ -260,7 +219,7 @@ class AccountController < ApplicationController
:secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false), :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
:httponly => true :httponly => true
} }
cookies[autologin_cookie_name] = cookie_options cookies[cookie_name] = cookie_options
end end
# Onthefly creation failed, display the registration form to fill/fix attributes # Onthefly creation failed, display the registration form to fill/fix attributes
@@ -281,9 +240,9 @@ class AccountController < ApplicationController
def register_by_email_activation(user, &block) def register_by_email_activation(user, &block)
token = Token.new(:user => user, :action => "register") token = Token.new(:user => user, :action => "register")
if user.save and token.save if user.save and token.save
Mailer.register(token).deliver Mailer.deliver_register(token)
flash[:notice] = l(:notice_account_register_done, :email => user.mail) flash[:notice] = l(:notice_account_register_done)
redirect_to signin_path redirect_to :action => 'login'
else else
yield if block_given? yield if block_given?
end end
@@ -299,7 +258,7 @@ class AccountController < ApplicationController
if user.save if user.save
self.logged_user = user self.logged_user = user
flash[:notice] = l(:notice_account_activated) flash[:notice] = l(:notice_account_activated)
redirect_to my_account_path redirect_to :controller => 'my', :action => 'account'
else else
yield if block_given? yield if block_given?
end end
@@ -311,33 +270,15 @@ class AccountController < ApplicationController
def register_manually_by_administrator(user, &block) def register_manually_by_administrator(user, &block)
if user.save if user.save
# Sends an email to the administrators # Sends an email to the administrators
Mailer.account_activation_request(user).deliver Mailer.deliver_account_activation_request(user)
account_pending(user) account_pending
else else
yield if block_given? yield if block_given?
end end
end end
def handle_inactive_user(user, redirect_path=signin_path) def account_pending
if user.registered? flash[:notice] = l(:notice_account_pending)
account_pending(user, redirect_path) redirect_to :action => 'login'
else
account_locked(user, redirect_path)
end
end
def account_pending(user, redirect_path=signin_path)
if Setting.self_registration == '1'
flash[:error] = l(:notice_account_not_activated_yet, :url => activation_email_path)
session[:registered_user_id] = user.id
else
flash[:error] = l(:notice_account_pending)
end
redirect_to redirect_path
end
def account_locked(user, redirect_path=signin_path)
flash[:error] = l(:notice_account_locked)
redirect_to redirect_path
end end
end end
+3 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -40,10 +40,10 @@ class ActivitiesController < ApplicationController
events = @activity.events(@date_from, @date_to) events = @activity.events(@date_from, @date_to)
if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, events.size, User.current, current_language]) if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, User.current, current_language])
respond_to do |format| respond_to do |format|
format.html { format.html {
@events_by_day = events.group_by {|event| User.current.time_to_date(event.event_datetime)} @events_by_day = events.group_by(&:event_date)
render :layout => false if request.xhr? render :layout => false if request.xhr?
} }
format.atom { format.atom {
+18 -17
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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,6 @@
class AdminController < ApplicationController class AdminController < ApplicationController
layout 'admin' layout 'admin'
menu_item :projects, :only => :projects
menu_item :plugins, :only => :plugins
menu_item :info, :only => :info
before_filter :require_admin before_filter :require_admin
helper :sort helper :sort
include SortHelper include SortHelper
@@ -30,11 +26,14 @@ class AdminController < ApplicationController
end end
def projects def projects
@status = params[:status] || 1 @status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
scope = Project.status(@status).order('lft') unless params[:name].blank?
scope = scope.like(params[:name]) if params[:name].present? name = "%#{params[:name].strip.downcase}%"
@projects = scope.all c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name]
end
@projects = Project.find :all, :order => 'lft',
:conditions => c.conditions
render :action => "projects", :layout => false if request.xhr? render :action => "projects", :layout => false if request.xhr?
end end
@@ -54,7 +53,7 @@ class AdminController < ApplicationController
flash[:error] = l(:error_can_t_load_default_data, e.message) flash[:error] = l(:error_can_t_load_default_data, e.message)
end end
end end
redirect_to admin_path redirect_to :action => 'index'
end end
def test_email def test_email
@@ -62,21 +61,23 @@ class AdminController < ApplicationController
# Force ActionMailer to raise delivery errors so we can catch it # Force ActionMailer to raise delivery errors so we can catch it
ActionMailer::Base.raise_delivery_errors = true ActionMailer::Base.raise_delivery_errors = true
begin begin
@test = Mailer.test_email(User.current).deliver @test = Mailer.deliver_test(User.current)
flash[:notice] = l(:notice_email_sent, User.current.mail) flash[:notice] = l(:notice_email_sent, User.current.mail)
rescue Exception => e rescue Exception => e
flash[:error] = l(:notice_email_error, Redmine::CodesetUtil.replace_invalid_utf8(e.message)) flash[:error] = l(:notice_email_error, e.message)
end end
ActionMailer::Base.raise_delivery_errors = raise_delivery_errors ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
redirect_to settings_path(:tab => 'notifications') redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications'
end end
def info def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name @db_adapter_name = ActiveRecord::Base.connection.adapter_name
@checklist = [ @checklist = [
[:text_default_administrator_account_changed, User.default_admin_account_changed?], [:text_default_administrator_account_changed,
User.find(:first,
:conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
[:text_file_repository_writable, File.writable?(Attachment.storage_path)], [:text_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)], [:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
[:text_rmagick_available, Object.const_defined?(:Magick)] [:text_rmagick_available, Object.const_defined?(:Magick)]
] ]
end end
+125 -223
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,65 +22,35 @@ class Unauthorized < Exception; end
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Redmine::I18n include Redmine::I18n
include Redmine::Pagination
include RoutesHelper
helper :routes
class_attribute :accept_api_auth_actions
class_attribute :accept_rss_auth_actions
class_attribute :model_object
layout 'base' layout 'base'
exempt_from_layout 'builder', 'rsb'
protect_from_forgery # Remove broken cookie after upgrade from 0.8.x (#4292)
def handle_unverified_request # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
super # TODO: remove it when Rails is fixed
cookies.delete(autologin_cookie_name) before_filter :delete_broken_cookies
def delete_broken_cookies
if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
cookies.delete '_redmine_session'
redirect_to home_path
return false
end
end end
before_filter :session_expiration, :user_setup, :check_if_login_required, :check_password_change, :set_localization before_filter :user_setup, :check_if_login_required, :set_localization
filter_parameter_logging :password
protect_from_forgery
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
rescue_from ::Unauthorized, :with => :deny_access rescue_from ::Unauthorized, :with => :deny_access
rescue_from ::ActionView::MissingTemplate, :with => :missing_template
include Redmine::Search::Controller include Redmine::Search::Controller
include Redmine::MenuManager::MenuController include Redmine::MenuManager::MenuController
helper Redmine::MenuManager::MenuHelper helper Redmine::MenuManager::MenuHelper
def session_expiration Redmine::Scm::Base.all.each do |scm|
if session[:user_id] require_dependency "repository/#{scm.underscore}"
if session_expired? && !try_to_autologin
reset_session
flash[:error] = l(:error_session_expired)
redirect_to signin_url
else
session[:atime] = Time.now.utc.to_i
end
end
end
def session_expired?
if Setting.session_lifetime?
unless session[:ctime] && (Time.now.utc.to_i - session[:ctime].to_i <= Setting.session_lifetime.to_i * 60)
return true
end
end
if Setting.session_timeout?
unless session[:atime] && (Time.now.utc.to_i - session[:atime].to_i <= Setting.session_timeout.to_i * 60)
return true
end
end
false
end
def start_user_session(user)
session[:user_id] = user.id
session[:ctime] = Time.now.utc.to_i
session[:atime] = Time.now.utc.to_i
if user.must_change_password?
session[:pwd] = '1'
end
end end
def user_setup def user_setup
@@ -88,65 +58,32 @@ class ApplicationController < ActionController::Base
Setting.check_cache Setting.check_cache
# Find the current user # Find the current user
User.current = find_current_user User.current = find_current_user
logger.info(" Current user: " + (User.current.logged? ? "#{User.current.login} (id=#{User.current.id})" : "anonymous")) if logger
end end
# Returns the current user or nil if no user is logged in # Returns the current user or nil if no user is logged in
# and starts a session if needed # and starts a session if needed
def find_current_user def find_current_user
user = nil if session[:user_id]
unless api_request? # existing session
if session[:user_id] (User.active.find(session[:user_id]) rescue nil)
# existing session elsif cookies[:autologin] && Setting.autologin?
user = (User.active.find(session[:user_id]) rescue nil) # auto-login feature starts a new session
elsif autologin_user = try_to_autologin user = User.try_to_autologin(cookies[:autologin])
user = autologin_user session[:user_id] = user.id if user
elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth? user
# RSS key authentication does not start a session elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
user = User.find_by_rss_key(params[:key]) # RSS key authentication does not start a session
end User.find_by_rss_key(params[:key])
end elsif Setting.rest_api_enabled? && accept_api_auth?
if user.nil? && Setting.rest_api_enabled? && accept_api_auth?
if (key = api_key_from_request) if (key = api_key_from_request)
# Use API key # Use API key
user = User.find_by_api_key(key) User.find_by_api_key(key)
else else
# HTTP Basic, either username/password or API key/random # HTTP Basic, either username/password or API key/random
authenticate_with_http_basic do |username, password| authenticate_with_http_basic do |username, password|
user = User.try_to_login(username, password) || User.find_by_api_key(username) User.try_to_login(username, password) || User.find_by_api_key(username)
end
if user && user.must_change_password?
render_error :message => 'You must change your password', :status => 403
return
end end
end end
# Switch user if requested by an admin user
if user && user.admin? && (username = api_switch_user_from_request)
su = User.find_by_login(username)
if su && su.active?
logger.info(" User switched by: #{user.login} (id=#{user.id})") if logger
user = su
else
render_error :message => 'Invalid X-Redmine-Switch-User header', :status => 412
end
end
end
user
end
def autologin_cookie_name
Redmine::Configuration['autologin_cookie_name'].presence || 'autologin'
end
def try_to_autologin
if cookies[autologin_cookie_name] && Setting.autologin?
# auto-login feature starts a new session
user = User.try_to_autologin(cookies[autologin_cookie_name])
if user
reset_session
start_user_session(user)
end
user
end end
end end
@@ -155,21 +92,12 @@ class ApplicationController < ActionController::Base
reset_session reset_session
if user && user.is_a?(User) if user && user.is_a?(User)
User.current = user User.current = user
start_user_session(user) session[:user_id] = user.id
else else
User.current = User.anonymous User.current = User.anonymous
end end
end end
# Logs out current user
def logout_user
if User.current.logged?
cookies.delete(autologin_cookie_name)
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
self.logged_user = nil
end
end
# check if login is globally required to access the application # check if login is globally required to access the application
def check_if_login_required def check_if_login_required
# no check needed if user is already logged in # no check needed if user is already logged in
@@ -177,16 +105,6 @@ class ApplicationController < ActionController::Base
require_login if Setting.login_required? require_login if Setting.login_required?
end end
def check_password_change
if session[:pwd]
if User.current.must_change_password?
redirect_to my_password_path
else
session.delete(:pwd)
end
end
end
def set_localization def set_localization
lang = nil lang = nil
if User.current.logged? if User.current.logged?
@@ -212,13 +130,7 @@ class ApplicationController < ActionController::Base
url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id]) url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
end end
respond_to do |format| respond_to do |format|
format.html { format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
if request.xhr?
head :unauthorized
else
redirect_to :controller => "account", :action => "login", :back_url => url
end
}
format.atom { redirect_to :controller => "account", :action => "login", :back_url => url } format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
@@ -290,10 +202,12 @@ class ApplicationController < ActionController::Base
render_404 unless @object.present? render_404 unless @object.present?
@project = @object.project @project = @object.project
rescue ActiveRecord::RecordNotFound
render_404
end end
def find_model_object def find_model_object
model = self.class.model_object model = self.class.read_inheritable_attribute('model_object')
if model if model
@object = model.find(params[:id]) @object = model.find(params[:id])
self.instance_variable_set('@' + controller_name.singularize, @object) if @object self.instance_variable_set('@' + controller_name.singularize, @object) if @object
@@ -303,51 +217,40 @@ class ApplicationController < ActionController::Base
end end
def self.model_object(model) def self.model_object(model)
self.model_object = model write_inheritable_attribute('model_object', model)
end end
# Find the issue whose id is the :id parameter # Filter for bulk issue operations
# Raises a Unauthorized exception if the issue is not visible
def find_issue
# Issue.visible.find(...) can not be used to redirect user to the login form
# if the issue actually exists but requires authentication
@issue = Issue.find(params[:id])
raise Unauthorized unless @issue.visible?
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
# Find issues with a single :id param or :ids array param
# Raises a Unauthorized exception if one of the issues is not visible
def find_issues def find_issues
@issues = Issue.where(:id => (params[:id] || params[:ids])).preload(:project, :status, :tracker, :priority, :author, :assigned_to, :relations_to).to_a @issues = Issue.find_all_by_id(params[:id] || params[:ids])
raise ActiveRecord::RecordNotFound if @issues.empty? raise ActiveRecord::RecordNotFound if @issues.empty?
raise Unauthorized unless @issues.all?(&:visible?) if @issues.detect {|issue| !issue.visible?}
deny_access
return
end
@projects = @issues.collect(&:project).compact.uniq @projects = @issues.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1 @project = @projects.first if @projects.size == 1
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def find_attachments # Check if project is unique before bulk operations
if (attachments = params[:attachments]).present? def check_project_uniqueness
att = attachments.values.collect do |attachment| unless @project
Attachment.find_by_token( attachment[:token] ) if attachment[:token].present? # TODO: let users bulk edit/move/destroy issues from different projects
end render_error 'Can not bulk edit/move/destroy issues from different projects'
att.compact! return false
end end
@attachments = att || []
end end
# make sure that the user is a member of the project (or admin) if project is private # make sure that the user is a member of the project (or admin) if project is private
# used as a before_filter for actions that do not require any particular permission on the project # used as a before_filter for actions that do not require any particular permission on the project
def check_project_privacy def check_project_privacy
if @project && !@project.archived? if @project && @project.active?
if @project.visible? if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
true true
else else
deny_access User.current.logged? ? render_403 : require_login
end end
else else
@project = nil @project = nil
@@ -357,16 +260,12 @@ class ApplicationController < ActionController::Base
end end
def back_url def back_url
url = params[:back_url] params[:back_url] || request.env['HTTP_REFERER']
if url.nil? && referer = request.env['HTTP_REFERER']
url = CGI.unescape(referer.to_s)
end
url
end end
def redirect_back_or_default(default) def redirect_back_or_default(default)
back_url = params[:back_url].to_s back_url = CGI.unescape(params[:back_url].to_s)
if back_url.present? if !back_url.blank?
begin begin
uri = URI.parse(back_url) uri = URI.parse(back_url)
# do not redirect user to another host or to the login or register page # do not redirect user to another host or to the login or register page
@@ -375,7 +274,6 @@ class ApplicationController < ActionController::Base
return return
end end
rescue URI::InvalidURIError rescue URI::InvalidURIError
logger.warn("Could not redirect to invalid URL #{back_url}")
# redirect to default # redirect to default
end end
end end
@@ -383,19 +281,6 @@ class ApplicationController < ActionController::Base
false false
end end
# Redirects to the request referer if present, redirects to args or call block otherwise.
def redirect_to_referer_or(*args, &block)
redirect_to :back
rescue ::ActionController::RedirectBackError
if args.any?
redirect_to *args
elsif block_given?
block.call
else
raise "#redirect_to_referer_or takes arguments or a block"
end
end
def render_403(options={}) def render_403(options={})
@project = nil @project = nil
render_error({:message => :notice_not_authorized, :status => 403}.merge(options)) render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
@@ -419,27 +304,10 @@ class ApplicationController < ActionController::Base
format.html { format.html {
render :template => 'common/error', :layout => use_layout, :status => @status render :template => 'common/error', :layout => use_layout, :status => @status
} }
format.any { head @status } format.atom { head @status }
end format.xml { head @status }
end format.js { head @status }
format.json { head @status }
# Handler for ActionView::MissingTemplate exception
def missing_template
logger.warn "Missing template, responding with 404"
@project = nil
render_404
end
# Filter for actions that provide an API response
# but have no HTML representation for non admin users
def require_admin_or_api_request
return true if api_request?
if User.current.admin?
true
elsif User.current.logged?
render_error(:status => 406)
else
deny_access
end end
end end
@@ -462,15 +330,26 @@ class ApplicationController < ActionController::Base
@items.sort! {|x,y| y.event_datetime <=> x.event_datetime } @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
@items = @items.slice(0, Setting.feeds_limit.to_i) @items = @items.slice(0, Setting.feeds_limit.to_i)
@title = options[:title] || Setting.app_title @title = options[:title] || Setting.app_title
render :template => "common/feed", :formats => [:atom], :layout => false, render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
:content_type => 'application/atom+xml' end
# TODO: remove in Redmine 1.4
def self.accept_key_auth(*actions)
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
accept_rss_auth(*actions)
end
# TODO: remove in Redmine 1.4
def accept_key_auth_actions
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
self.class.accept_rss_auth
end end
def self.accept_rss_auth(*actions) def self.accept_rss_auth(*actions)
if actions.any? if actions.any?
self.accept_rss_auth_actions = actions write_inheritable_attribute('accept_rss_auth_actions', actions)
else else
self.accept_rss_auth_actions || [] read_inheritable_attribute('accept_rss_auth_actions') || []
end end
end end
@@ -480,9 +359,9 @@ class ApplicationController < ActionController::Base
def self.accept_api_auth(*actions) def self.accept_api_auth(*actions)
if actions.any? if actions.any?
self.accept_api_auth_actions = actions write_inheritable_attribute('accept_api_auth_actions', actions)
else else
self.accept_api_auth_actions || [] read_inheritable_attribute('accept_api_auth_actions') || []
end end
end end
@@ -562,22 +441,32 @@ class ApplicationController < ActionController::Base
# Returns the API key present in the request # Returns the API key present in the request
def api_key_from_request def api_key_from_request
if params[:key].present? if params[:key].present?
params[:key].to_s params[:key]
elsif request.headers["X-Redmine-API-Key"].present? elsif request.headers["X-Redmine-API-Key"].present?
request.headers["X-Redmine-API-Key"].to_s request.headers["X-Redmine-API-Key"]
end end
end end
# Returns the API 'switch user' value if present
def api_switch_user_from_request
request.headers["X-Redmine-Switch-User"].to_s.presence
end
# Renders a warning flash if obj has unsaved attachments # Renders a warning flash if obj has unsaved attachments
def render_attachment_warning_if_needed(obj) def render_attachment_warning_if_needed(obj)
flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present? flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
end end
# Sets the `flash` notice or error based the number of issues that did not save
#
# @param [Array, Issue] issues all of the saved and unsaved Issues
# @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
if unsaved_issue_ids.empty?
flash[:notice] = l(:notice_successful_update) unless issues.empty?
else
flash[:error] = l(:notice_failed_to_save_issues,
:count => unsaved_issue_ids.size,
:total => issues.size,
:ids => '#' + unsaved_issue_ids.join(', #'))
end
end
# Rescues an invalid query statement. Just in case... # Rescues an invalid query statement. Just in case...
def query_statement_invalid(exception) def query_statement_invalid(exception)
logger.error "Query::StatementInvalid: #{exception.message}" if logger logger.error "Query::StatementInvalid: #{exception.message}" if logger
@@ -586,30 +475,43 @@ class ApplicationController < ActionController::Base
render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator." render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
end end
# Renders a 200 response for successfull updates or deletions via the API # Converts the errors on an ActiveRecord object into a common JSON format
def render_api_ok def object_errors_to_json(object)
render_api_head :ok object.errors.collect do |attribute, error|
end { attribute => error }
end.to_json
# Renders a head API response
def render_api_head(status)
# #head would return a response body with one space
render :text => '', :status => status, :layout => nil
end end
# Renders API response on validation failure # Renders API response on validation failure
def render_validation_errors(objects) def render_validation_errors(object)
if objects.is_a?(Array) options = { :status => :unprocessable_entity, :layout => false }
@error_messages = objects.map {|object| object.errors.full_messages}.flatten options.merge!(case params[:format]
else when 'xml'; { :xml => object.errors }
@error_messages = objects.errors.full_messages when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance
end else
render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil raise "Unknown format #{params[:format]} in #render_validation_errors"
end
)
render options
end end
# Overrides #_include_layout? so that #render with no arguments # Overrides #default_template so that the api template
# is used automatically if it exists
def default_template(action_name = self.action_name)
if api_request?
begin
return self.view_paths.find_template(default_template_name(action_name), 'api')
rescue ::ActionView::MissingTemplate
# the api template was not found
# fallback to the default behaviour
end
end
super
end
# Overrides #pick_layout so that #render with no arguments
# doesn't use the layout for api requests # doesn't use the layout for api requests
def _include_layout?(*args) def pick_layout(*args)
api_request? ? false : super api_request? ? nil : super
end end
end end
+22 -89
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,34 +16,21 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AttachmentsController < ApplicationController class AttachmentsController < ApplicationController
before_filter :find_project, :except => :upload before_filter :find_project
before_filter :file_readable, :read_authorize, :only => [:show, :download, :thumbnail] before_filter :file_readable, :read_authorize, :except => :destroy
before_filter :delete_authorize, :only => :destroy before_filter :delete_authorize, :only => :destroy
before_filter :authorize_global, :only => :upload
accept_api_auth :show, :download, :upload verify :method => :post, :only => :destroy
def show def show
respond_to do |format| if @attachment.is_diff?
format.html { @diff = File.new(@attachment.diskfile, "rb").read
if @attachment.is_diff? render :action => 'diff'
@diff = File.new(@attachment.diskfile, "rb").read elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' @content = File.new(@attachment.diskfile, "rb").read
@diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) render :action => 'file'
# Save diff type as user preference else
if User.current.logged? && @diff_type != User.current.pref[:diff_type] download
User.current.pref[:diff_type] = @diff_type
User.current.preference.save
end
render :action => 'diff'
elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
@content = File.new(@attachment.diskfile, "rb").read
render :action => 'file'
else
download
end
}
format.api
end end
end end
@@ -52,68 +39,19 @@ class AttachmentsController < ApplicationController
@attachment.increment_download @attachment.increment_download
end end
if stale?(:etag => @attachment.digest) # images are sent inline
# images are sent inline send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename), :type => detect_content_type(@attachment),
:type => detect_content_type(@attachment), :disposition => (@attachment.image? ? 'inline' : 'attachment')
:disposition => (@attachment.image? ? 'inline' : 'attachment')
end
end
def thumbnail
if @attachment.thumbnailable? && thumbnail = @attachment.thumbnail(:size => params[:size])
if stale?(:etag => thumbnail)
send_file thumbnail,
:filename => filename_for_content_disposition(@attachment.filename),
:type => detect_content_type(@attachment),
:disposition => 'inline'
end
else
# No thumbnail for the attachment or thumbnail could not be created
render :nothing => true, :status => 404
end
end
def upload
# Make sure that API users get used to set this content type
# as it won't trigger Rails' automatic parsing of the request body for parameters
unless request.content_type == 'application/octet-stream'
render :nothing => true, :status => 406
return
end
@attachment = Attachment.new(:file => request.raw_post)
@attachment.author = User.current
@attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
saved = @attachment.save
respond_to do |format|
format.js
format.api {
if saved
render :action => 'upload', :status => :created
else
render_validation_errors(@attachment)
end
}
end
end end
def destroy def destroy
if @attachment.container.respond_to?(:init_journal) # Make sure association callbacks are called
@attachment.container.init_journal(User.current) @attachment.container.attachments.delete(@attachment)
end redirect_to :back
if @attachment.container rescue ::ActionController::RedirectBackError
# Make sure association callbacks are called redirect_to :controller => 'projects', :action => 'show', :id => @project
@attachment.container.attachments.delete(@attachment)
else
@attachment.destroy
end
respond_to do |format|
format.html { redirect_to_referer_or project_path(@project) }
format.js
end
end end
private private
@@ -128,12 +66,7 @@ private
# Checks that the file exists and is readable # Checks that the file exists and is readable
def file_readable def file_readable
if @attachment.readable? @attachment.readable? ? true : render_404
true
else
logger.error "Cannot send attachment, #{@attachment.diskfile} does not exist or is unreadable."
render_404
end
end end
def read_authorize def read_authorize
+29 -38
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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,80 +17,71 @@
class AuthSourcesController < ApplicationController class AuthSourcesController < ApplicationController
layout 'admin' layout 'admin'
menu_item :ldap_authentication
before_filter :require_admin before_filter :require_admin
before_filter :find_auth_source, :only => [:edit, :update, :test_connection, :destroy]
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :template => :index }
def index def index
@auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 25 @auth_source_pages, @auth_sources = paginate auth_source_class.name.tableize, :per_page => 10
render "auth_sources/index"
end end
def new def new
klass_name = params[:type] || 'AuthSourceLdap' @auth_source = auth_source_class.new
@auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source]) render 'auth_sources/new'
render_404 unless @auth_source
end end
def create def create
@auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source]) @auth_source = auth_source_class.new(params[:auth_source])
if @auth_source.save if @auth_source.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to auth_sources_path redirect_to :action => 'index'
else else
render :action => 'new' render 'auth_sources/new'
end end
end end
def edit def edit
@auth_source = AuthSource.find(params[:id])
render 'auth_sources/edit'
end end
def update def update
@auth_source = AuthSource.find(params[:id])
if @auth_source.update_attributes(params[:auth_source]) if @auth_source.update_attributes(params[:auth_source])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to auth_sources_path redirect_to :action => 'index'
else else
render :action => 'edit' render 'auth_sources/edit'
end end
end end
def test_connection def test_connection
@auth_method = AuthSource.find(params[:id])
begin begin
@auth_source.test_connection @auth_method.test_connection
flash[:notice] = l(:notice_successful_connection) flash[:notice] = l(:notice_successful_connection)
rescue Exception => e rescue => text
flash[:error] = l(:error_unable_to_connect, e.message) flash[:error] = l(:error_unable_to_connect, text.message)
end end
redirect_to auth_sources_path redirect_to :action => 'index'
end end
def destroy def destroy
unless @auth_source.users.exists? @auth_source = AuthSource.find(params[:id])
unless @auth_source.users.find(:first)
@auth_source.destroy @auth_source.destroy
flash[:notice] = l(:notice_successful_delete) flash[:notice] = l(:notice_successful_delete)
end end
redirect_to auth_sources_path redirect_to :action => 'index'
end end
def autocomplete_for_new_user protected
results = AuthSource.search(params[:term])
render :json => results.map {|result| { def auth_source_class
'value' => result[:login], AuthSource
'label' => "#{result[:login]} (#{result[:firstname]} #{result[:lastname]})",
'login' => result[:login].to_s,
'firstname' => result[:firstname].to_s,
'lastname' => result[:lastname].to_s,
'mail' => result[:mail].to_s,
'auth_source_id' => result[:auth_source_id].to_s
}}
end
private
def find_auth_source
@auth_source = AuthSource.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end end
end end
+11 -28
View File
@@ -1,44 +1,27 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AutoCompletesController < ApplicationController class AutoCompletesController < ApplicationController
before_filter :find_project before_filter :find_project
def issues def issues
@issues = [] @issues = []
q = (params[:q] || params[:term]).to_s.strip q = params[:q].to_s
if q.present? query = (params[:scope] == "all" && Setting.cross_project_issue_relations?) ? Issue : @project.issues
scope = (params[:scope] == "all" || @project.nil? ? Issue : @project.issues).visible if q.match(/^\d+$/)
if q.match(/\A#?(\d+)\z/) @issues << query.visible.find_by_id(q.to_i)
@issues << scope.find_by_id($1.to_i)
end
@issues += scope.where("LOWER(#{Issue.table_name}.subject) LIKE LOWER(?)", "%#{q}%").order("#{Issue.table_name}.id DESC").limit(10).all
@issues.compact!
end end
unless q.blank?
@issues += query.visible.find(:all, :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%"], :limit => 10)
end
@issues.compact!
render :layout => false render :layout => false
end end
private private
def find_project def find_project
if params[:project_id].present? project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
@project = Project.find(params[:project_id]) @project = Project.find(project_id)
end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
end end
+30 -38
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,15 +17,18 @@
class BoardsController < ApplicationController class BoardsController < ApplicationController
default_search_scope :messages default_search_scope :messages
before_filter :find_project_by_project_id, :find_board_if_available, :authorize before_filter :find_project, :find_board_if_available, :authorize
accept_rss_auth :index, :show accept_rss_auth :index, :show
helper :messages
include MessagesHelper
helper :sort helper :sort
include SortHelper include SortHelper
helper :watchers helper :watchers
include WatchersHelper
def index def index
@boards = @project.boards.includes(:project, :last_message => :author).all @boards = @project.boards
# show the board if there is only one # show the board if there is only one
if @boards.size == 1 if @boards.size == 1
@board = @boards.first @board = @boards.first
@@ -37,59 +40,42 @@ class BoardsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { format.html {
sort_init 'updated_on', 'desc' sort_init 'updated_on', 'desc'
sort_update 'created_on' => "#{Message.table_name}.created_on", sort_update 'created_on' => "#{Message.table_name}.created_on",
'replies' => "#{Message.table_name}.replies_count", 'replies' => "#{Message.table_name}.replies_count",
'updated_on' => "COALESCE(last_replies_messages.created_on, #{Message.table_name}.created_on)" 'updated_on' => "#{Message.table_name}.updated_on"
@topic_count = @board.topics.count @topic_count = @board.topics.count
@topic_pages = Paginator.new @topic_count, per_page_option, params['page'] @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
@topics = @board.topics. @topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '),
reorder("#{Message.table_name}.sticky DESC"). :include => [:author, {:last_reply => :author}],
includes(:last_reply). :limit => @topic_pages.items_per_page,
limit(@topic_pages.per_page). :offset => @topic_pages.current.offset
offset(@topic_pages.offset). @message = Message.new
order(sort_clause).
preload(:author, {:last_reply => :author}).
all
@message = Message.new(:board => @board)
render :action => 'show', :layout => !request.xhr? render :action => 'show', :layout => !request.xhr?
} }
format.atom { format.atom {
@messages = @board.messages. @messages = @board.messages.find :all, :order => 'created_on DESC',
reorder('created_on DESC'). :include => [:author, :board],
includes(:author, :board). :limit => Setting.feeds_limit.to_i
limit(Setting.feeds_limit.to_i).
all
render_feed(@messages, :title => "#{@project}: #{@board}") render_feed(@messages, :title => "#{@project}: #{@board}")
} }
end end
end end
def new verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
@board = @project.boards.build
@board.safe_attributes = params[:board]
end
def create def new
@board = @project.boards.build @board = Board.new(params[:board])
@board.safe_attributes = params[:board] @board.project = @project
if @board.save if request.post? && @board.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to_settings_in_projects redirect_to_settings_in_projects
else
render :action => 'new'
end end
end end
def edit def edit
end if request.post? && @board.update_attributes(params[:board])
def update
@board.safe_attributes = params[:board]
if @board.save
redirect_to_settings_in_projects redirect_to_settings_in_projects
else
render :action => 'edit'
end end
end end
@@ -100,7 +86,13 @@ class BoardsController < ApplicationController
private private
def redirect_to_settings_in_projects def redirect_to_settings_in_projects
redirect_to settings_project_path(@project, :tab => 'boards') redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end end
def find_board_if_available def find_board_if_available
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+6 -23
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CommentsController < ApplicationController class CommentsController < ApplicationController
default_search_scope :news default_search_scope :news
model_object News model_object News
@@ -22,22 +5,21 @@ class CommentsController < ApplicationController
before_filter :find_project_from_association before_filter :find_project_from_association
before_filter :authorize before_filter :authorize
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
raise Unauthorized unless @news.commentable? @comment = Comment.new(params[:comment])
@comment = Comment.new
@comment.safe_attributes = params[:comment]
@comment.author = User.current @comment.author = User.current
if @news.comments << @comment if @news.comments << @comment
flash[:notice] = l(:label_comment_added) flash[:notice] = l(:label_comment_added)
end end
redirect_to news_path(@news) redirect_to :controller => 'news', :action => 'show', :id => @news
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
@news.comments.find(params[:comment_id]).destroy @news.comments.find(params[:comment_id]).destroy
redirect_to news_path(@news) redirect_to :controller => 'news', :action => 'show', :id => @news
end end
private private
@@ -50,4 +32,5 @@ class CommentsController < ApplicationController
@comment = nil @comment = nil
@news @news
end end
end end
+19 -48
View File
@@ -1,33 +1,22 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ContextMenusController < ApplicationController class ContextMenusController < ApplicationController
helper :watchers helper :watchers
helper :issues helper :issues
before_filter :find_issues, :only => :issues
def issues def issues
@issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project)
if (@issues.size == 1) if (@issues.size == 1)
@issue = @issues.first @issue = @issues.first
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
else
@allowed_statuses = @issues.map do |i|
i.new_statuses_allowed_to(User.current)
end.inject do |memo,s|
memo & s
end
end end
@issue_ids = @issues.map(&:id).sort @projects = @issues.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1
@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
@can = {:edit => User.current.allowed_to?(:edit_issues, @projects), @can = {:edit => User.current.allowed_to?(:edit_issues, @projects),
:log_time => (@project && User.current.allowed_to?(:log_time, @project)), :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
@@ -37,43 +26,25 @@ class ContextMenusController < ApplicationController
:delete => User.current.allowed_to?(:delete_issues, @projects) :delete => User.current.allowed_to?(:delete_issues, @projects)
} }
if @project if @project
if @issue @assignables = @project.assignable_users
@assignables = @issue.assignable_users @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
else
@assignables = @project.assignable_users
end
@trackers = @project.trackers @trackers = @project.trackers
else else
#when multiple projects, we only keep the intersection of each set #when multiple projects, we only keep the intersection of each set
@assignables = @projects.map(&:assignable_users).reduce(:&) @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
@trackers = @projects.map(&:trackers).reduce(:&) @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
end end
@versions = @projects.map {|p| p.shared_versions.open}.reduce(:&)
@priorities = IssuePriority.active.reverse @priorities = IssuePriority.all.reverse
@statuses = IssueStatus.find(:all, :order => 'position')
@back = back_url @back = back_url
@options_by_custom_field = {}
if @can[:edit]
custom_fields = @issues.map(&:available_custom_fields).reduce(:&).select do |f|
%w(bool list user version).include?(f.field_format) && !f.multiple?
end
custom_fields.each do |field|
values = field.possible_values_options(@projects)
if values.any?
@options_by_custom_field[field] = values
end
end
end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
render :layout => false render :layout => false
end end
def time_entries def time_entries
@time_entries = TimeEntry.where(:id => params[:ids]).preload(:project).to_a @time_entries = TimeEntry.all(
(render_404; return) unless @time_entries.present? :conditions => {:id => params[:ids]}, :include => :project)
@projects = @time_entries.collect(&:project).compact.uniq @projects = @time_entries.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1 @project = @projects.first if @projects.size == 1
@activities = TimeEntryActivity.shared.active @activities = TimeEntryActivity.shared.active
+22 -47
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 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
@@ -19,71 +19,46 @@ class CustomFieldsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin
before_filter :build_new_custom_field, :only => [:new, :create]
before_filter :find_custom_field, :only => [:edit, :update, :destroy]
accept_api_auth :index
def index def index
respond_to do |format| @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
format.html { @tab = params[:tab] || 'IssueCustomField'
@custom_fields_by_type = CustomField.all.group_by {|f| f.class.name }
@tab = params[:tab] || 'IssueCustomField'
}
format.api {
@custom_fields = CustomField.all
}
end
end end
def new def new
end @custom_field = begin
if params[:type].to_s.match(/.+CustomField$/)
params[:type].to_s.constantize.new(params[:custom_field])
end
rescue
end
(redirect_to(:action => 'index'); return) unless @custom_field.is_a?(CustomField)
def create if request.post? and @custom_field.save
if @custom_field.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field) call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
redirect_to custom_fields_path(:tab => @custom_field.class.name) redirect_to :action => 'index', :tab => @custom_field.class.name
else else
render :action => 'new' @trackers = Tracker.find(:all, :order => 'position')
end end
end end
def edit def edit
end @custom_field = CustomField.find(params[:id])
if request.post? and @custom_field.update_attributes(params[:custom_field])
def update
if @custom_field.update_attributes(params[:custom_field])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field) call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
redirect_to custom_fields_path(:tab => @custom_field.class.name) redirect_to :action => 'index', :tab => @custom_field.class.name
else else
render :action => 'edit' @trackers = Tracker.find(:all, :order => 'position')
end end
end end
def destroy def destroy
begin @custom_field = CustomField.find(params[:id]).destroy
@custom_field.destroy redirect_to :action => 'index', :tab => @custom_field.class.name
rescue rescue
flash[:error] = l(:error_can_not_delete_custom_field) flash[:error] = l(:error_can_not_delete_custom_field)
end redirect_to :action => 'index'
redirect_to custom_fields_path(:tab => @custom_field.class.name)
end
private
def build_new_custom_field
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
if @custom_field.nil?
render_404
else
@custom_field.default_value = nil
end
end
def find_custom_field
@custom_field = CustomField.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end end
end end
+25 -33
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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,16 +18,16 @@
class DocumentsController < ApplicationController class DocumentsController < ApplicationController
default_search_scope :documents default_search_scope :documents
model_object Document model_object Document
before_filter :find_project_by_project_id, :only => [:index, :new, :create] before_filter :find_project, :only => [:index, :new]
before_filter :find_model_object, :except => [:index, :new, :create] before_filter :find_model_object, :except => [:index, :new]
before_filter :find_project_from_association, :except => [:index, :new, :create] before_filter :find_project_from_association, :except => [:index, :new]
before_filter :authorize before_filter :authorize
helper :attachments helper :attachments
def index def index
@sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category' @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
documents = @project.documents.includes(:attachments, :category).all documents = @project.documents.find :all, :include => [:attachments, :category]
case @sort_by case @sort_by
when 'date' when 'date'
@grouped = documents.group_by {|d| d.updated_on.to_date } @grouped = documents.group_by {|d| d.updated_on.to_date }
@@ -43,52 +43,44 @@ class DocumentsController < ApplicationController
end end
def show def show
@attachments = @document.attachments.all @attachments = @document.attachments.find(:all, :order => "created_on DESC")
end end
def new def new
@document = @project.documents.build @document = @project.documents.build(params[:document])
@document.safe_attributes = params[:document] if request.post? and @document.save
end attachments = Attachment.attach_files(@document, params[:attachments])
def create
@document = @project.documents.build
@document.safe_attributes = params[:document]
@document.save_attachments(params[:attachments])
if @document.save
render_attachment_warning_if_needed(@document) render_attachment_warning_if_needed(@document)
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to project_documents_path(@project) redirect_to :action => 'index', :project_id => @project
else
render :action => 'new'
end end
end end
def edit def edit
end @categories = DocumentCategory.all
if request.post? and @document.update_attributes(params[:document])
def update
@document.safe_attributes = params[:document]
if request.put? and @document.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to document_path(@document) redirect_to :action => 'show', :id => @document
else
render :action => 'edit'
end end
end end
def destroy def destroy
@document.destroy if request.delete? @document.destroy
redirect_to project_documents_path(@project) redirect_to :controller => 'documents', :action => 'index', :project_id => @project
end end
def add_attachment def add_attachment
attachments = Attachment.attach_files(@document, params[:attachments]) attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document) render_attachment_warning_if_needed(@document)
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added') Mailer.deliver_attachments_added(attachments[:files]) if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
Mailer.attachments_added(attachments[:files]).deliver redirect_to :action => 'show', :id => @document
end end
redirect_to document_path(@document)
private
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end end
end end
+39 -44
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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
@@ -18,79 +18,74 @@
class EnumerationsController < ApplicationController class EnumerationsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin, :except => :index before_filter :require_admin
before_filter :require_admin_or_api_request, :only => :index
before_filter :build_new_enumeration, :only => [:new, :create]
before_filter :find_enumeration, :only => [:edit, :update, :destroy]
accept_api_auth :index
helper :custom_fields helper :custom_fields
include CustomFieldsHelper
def index def index
respond_to do |format| list
format.html render :action => 'list'
format.api { end
@klass = Enumeration.get_subclass(params[:type])
if @klass # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
@enumerations = @klass.shared.sorted.all verify :method => :post, :only => [ :destroy, :create, :update ],
else :redirect_to => { :action => :list }
render_404
end def list
}
end
end end
def new def new
begin
@enumeration = params[:type].constantize.new
rescue NameError
@enumeration = Enumeration.new
end
end end
def create def create
if request.post? && @enumeration.save @enumeration = Enumeration.new(params[:enumeration])
@enumeration.type = params[:enumeration][:type]
if @enumeration.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to enumerations_path redirect_to :action => 'list', :type => @enumeration.type
else else
render :action => 'new' render :action => 'new'
end end
end end
def edit def edit
@enumeration = Enumeration.find(params[:id])
end end
def update def update
if request.put? && @enumeration.update_attributes(params[:enumeration]) @enumeration = Enumeration.find(params[:id])
@enumeration.type = params[:enumeration][:type] if params[:enumeration][:type]
if @enumeration.update_attributes(params[:enumeration])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to enumerations_path redirect_to :action => 'list', :type => @enumeration.type
else else
render :action => 'edit' render :action => 'edit'
end end
end end
def destroy def destroy
@enumeration = Enumeration.find(params[:id])
if !@enumeration.in_use? if !@enumeration.in_use?
# No associated objects # No associated objects
@enumeration.destroy @enumeration.destroy
redirect_to enumerations_path redirect_to :action => 'index'
return
elsif params[:reassign_to_id].present? && (reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id].to_i))
@enumeration.destroy(reassign_to)
redirect_to enumerations_path
return return
elsif params[:reassign_to_id]
if reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id])
@enumeration.destroy(reassign_to)
redirect_to :action => 'index'
return
end
end end
@enumerations = @enumeration.class.system.all - [@enumeration] @enumerations = @enumeration.class.find(:all) - [@enumeration]
end #rescue
# flash[:error] = 'Unable to delete enumeration'
private # redirect_to :action => 'index'
def build_new_enumeration
class_name = params[:enumeration] && params[:enumeration][:type] || params[:type]
@enumeration = Enumeration.new_subclass_instance(class_name, params[:enumeration])
if @enumeration.nil?
render_404
end
end
def find_enumeration
@enumeration = Enumeration.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end end
end end
+3 -20
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class FilesController < ApplicationController class FilesController < ApplicationController
menu_item :files menu_item :files
@@ -31,8 +14,8 @@ class FilesController < ApplicationController
'size' => "#{Attachment.table_name}.filesize", 'size' => "#{Attachment.table_name}.filesize",
'downloads' => "#{Attachment.table_name}.downloads" 'downloads' => "#{Attachment.table_name}.downloads"
@containers = [ Project.includes(:attachments).reorder(sort_clause).find(@project.id)] @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
@containers += @project.versions.includes(:attachments).reorder(sort_clause).all.sort.reverse @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
render :layout => !request.xhr? render :layout => !request.xhr?
end end
@@ -46,7 +29,7 @@ class FilesController < ApplicationController
render_attachment_warning_if_needed(container) render_attachment_warning_if_needed(container)
if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added') if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
Mailer.attachments_added(attachments[:files]).deliver Mailer.deliver_attachments_added(attachments[:files])
end end
redirect_to project_files_path(@project) redirect_to project_files_path(@project)
end end
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+78 -49
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 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
@@ -19,123 +19,152 @@ class GroupsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin
before_filter :find_group, :except => [:index, :new, :create]
accept_api_auth :index, :show, :create, :update, :destroy, :add_users, :remove_user
helper :custom_fields helper :custom_fields
# GET /groups
# GET /groups.xml
def index def index
@groups = Group.sorted.all @groups = Group.find(:all, :order => 'lastname')
respond_to do |format| respond_to do |format|
format.html format.html # index.html.erb
format.api format.xml { render :xml => @groups }
end end
end end
# GET /groups/1
# GET /groups/1.xml
def show def show
@group = Group.find(params[:id])
respond_to do |format| respond_to do |format|
format.html format.html # show.html.erb
format.api format.xml { render :xml => @group }
end end
end end
# GET /groups/new
# GET /groups/new.xml
def new def new
@group = Group.new @group = Group.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @group }
end
end end
# GET /groups/1/edit
def edit
@group = Group.find(params[:id], :include => :projects)
end
# POST /groups
# POST /groups.xml
def create def create
@group = Group.new @group = Group.new(params[:group])
@group.safe_attributes = params[:group]
respond_to do |format| respond_to do |format|
if @group.save if @group.save
format.html { flash[:notice] = l(:notice_successful_create)
flash[:notice] = l(:notice_successful_create) format.html { redirect_to(groups_path) }
redirect_to(params[:continue] ? new_group_path : groups_path) format.xml { render :xml => @group, :status => :created, :location => @group }
}
format.api { render :action => 'show', :status => :created, :location => group_url(@group) }
else else
format.html { render :action => "new" } format.html { render :action => "new" }
format.api { render_validation_errors(@group) } format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
end end
end end
end end
def edit # PUT /groups/1
end # PUT /groups/1.xml
def update def update
@group.safe_attributes = params[:group] @group = Group.find(params[:id])
respond_to do |format| respond_to do |format|
if @group.save if @group.update_attributes(params[:group])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
format.html { redirect_to(groups_path) } format.html { redirect_to(groups_path) }
format.api { render_api_ok } format.xml { head :ok }
else else
format.html { render :action => "edit" } format.html { render :action => "edit" }
format.api { render_validation_errors(@group) } format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
end end
end end
end end
# DELETE /groups/1
# DELETE /groups/1.xml
def destroy def destroy
@group = Group.find(params[:id])
@group.destroy @group.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to(groups_path) } format.html { redirect_to(groups_url) }
format.api { render_api_ok } format.xml { head :ok }
end end
end end
def add_users def add_users
@users = User.find_all_by_id(params[:user_id] || params[:user_ids]) @group = Group.find(params[:id])
@group.users << @users if request.post? users = User.find_all_by_id(params[:user_ids])
@group.users << users if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'users') } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js format.js {
format.api { render_api_ok } render(:update) {|page|
page.replace_html "tab-content-users", :partial => 'groups/users'
users.each {|user| page.visual_effect(:highlight, "user-#{user.id}") }
}
}
end end
end end
def remove_user def remove_user
@group.users.delete(User.find(params[:user_id])) if request.delete? @group = Group.find(params[:id])
@group.users.delete(User.find(params[:user_id])) if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'users') } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js format.js { render(:update) {|page| page.replace_html "tab-content-users", :partial => 'groups/users'} }
format.api { render_api_ok }
end end
end end
def autocomplete_for_user def autocomplete_for_user
respond_to do |format| @group = Group.find(params[:id])
format.js @users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100)
end render :layout => false
end end
def edit_membership def edit_membership
@group = Group.find(params[:id])
@membership = Member.edit_membership(params[:membership_id], params[:membership], @group) @membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
@membership.save if request.post? @membership.save if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'memberships') } if @membership.valid?
format.js format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
format.js {
render(:update) {|page|
page.replace_html "tab-content-memberships", :partial => 'groups/memberships'
page.visual_effect(:highlight, "member-#{@membership.id}")
}
}
else
format.js {
render(:update) {|page|
page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
}
}
end
end end
end end
def destroy_membership def destroy_membership
@group = Group.find(params[:id])
Member.find(params[:membership_id]).destroy if request.post? Member.find(params[:membership_id]).destroy if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_group_path(@group, :tab => 'memberships') } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
format.js format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'groups/memberships'} }
end end
end end
private
def find_group
@group = Group.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+45 -76
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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
@@ -18,105 +18,74 @@
class IssueCategoriesController < ApplicationController class IssueCategoriesController < ApplicationController
menu_item :settings menu_item :settings
model_object IssueCategory model_object IssueCategory
before_filter :find_model_object, :except => [:index, :new, :create] before_filter :find_model_object, :except => :new
before_filter :find_project_from_association, :except => [:index, :new, :create] before_filter :find_project_from_association, :except => :new
before_filter :find_project_by_project_id, :only => [:index, :new, :create] before_filter :find_project, :only => :new
before_filter :authorize before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
def index verify :method => :post, :only => :destroy
respond_to do |format|
format.html { redirect_to_settings_in_projects }
format.api { @categories = @project.issue_categories.all }
end
end
def show
respond_to do |format|
format.html { redirect_to_settings_in_projects }
format.api
end
end
def new def new
@category = @project.issue_categories.build @category = @project.issue_categories.build(params[:category])
@category.safe_attributes = params[:issue_category] if request.post?
if @category.save
respond_to do |format| respond_to do |format|
format.html format.html do
format.js flash[:notice] = l(:notice_successful_create)
end redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
end end
format.js do
def create # IE doesn't support the replace_html rjs method for select box options
@category = @project.issue_categories.build render(:update) {|page| page.replace "issue_category_id",
@category.safe_attributes = params[:issue_category] content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
if @category.save }
respond_to do |format| end
format.html do end
flash[:notice] = l(:notice_successful_create) else
redirect_to_settings_in_projects respond_to do |format|
format.html
format.js do
render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
end
end end
format.js
format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) }
end
else
respond_to do |format|
format.html { render :action => 'new'}
format.js { render :action => 'new'}
format.api { render_validation_errors(@category) }
end end
end end
end end
def edit def edit
end if request.post? and @category.update_attributes(params[:category])
flash[:notice] = l(:notice_successful_update)
def update redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
@category.safe_attributes = params[:issue_category]
if @category.save
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to_settings_in_projects
}
format.api { render_api_ok }
end
else
respond_to do |format|
format.html { render :action => 'edit' }
format.api { render_validation_errors(@category) }
end
end end
end end
def destroy def destroy
@issue_count = @category.issues.size @issue_count = @category.issues.size
if @issue_count == 0 || params[:todo] || api_request? if @issue_count == 0
reassign_to = nil # No issue assigned to this category
if params[:reassign_to_id] && (params[:todo] == 'reassign' || params[:todo].blank?) @category.destroy
reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
end return
elsif params[:todo]
reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) if params[:todo] == 'reassign'
@category.destroy(reassign_to) @category.destroy(reassign_to)
respond_to do |format| redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
format.html { redirect_to_settings_in_projects }
format.api { render_api_ok }
end
return return
end end
@categories = @project.issue_categories - [@category] @categories = @project.issue_categories - [@category]
end end
private private
def redirect_to_settings_in_projects
redirect_to settings_project_path(@project, :tab => 'categories')
end
# Wrap ApplicationController's find_model_object method to set # Wrap ApplicationController's find_model_object method to set
# @category instead of just @issue_category # @category instead of just @issue_category
def find_model_object def find_model_object
super super
@category = @object @category = @object
end end
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+68
View File
@@ -0,0 +1,68 @@
class IssueMovesController < ApplicationController
default_search_scope :issues
before_filter :find_issues, :check_project_uniqueness
before_filter :authorize
def new
prepare_for_issue_move
render :layout => false if request.xhr?
end
def create
prepare_for_issue_move
if request.post?
new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
unsaved_issue_ids = []
moved_issues = []
@issues.each do |issue|
issue.reload
issue.init_journal(User.current)
issue.current_journal.notes = @notes if @notes.present?
call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy })
if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params)})
moved_issues << r
else
unsaved_issue_ids << issue.id
end
end
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
if params[:follow]
if @issues.size == 1 && moved_issues.size == 1
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
else
redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
end
else
redirect_to :controller => 'issues', :action => 'index', :project_id => @project
end
return
end
end
private
def prepare_for_issue_move
@issues.sort!
@copy = params[:copy_options] && params[:copy_options][:copy]
@allowed_projects = Issue.allowed_target_projects_on_move
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
@target_project ||= @project
@trackers = @target_project.trackers
@available_statuses = Workflow.available_statuses(@project)
@notes = params[:notes]
@notes ||= ''
end
def extract_changed_attributes_for_move(params)
changed_attributes = {}
[:assigned_to_id, :status_id, :start_date, :due_date, :priority_id].each do |valid_attribute|
unless params[valid_attribute].blank?
changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
end
end
changed_attributes
end
end
+25 -49
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 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,60 +16,42 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueRelationsController < ApplicationController class IssueRelationsController < ApplicationController
before_filter :find_issue, :find_project_from_association, :authorize, :only => [:index, :create] before_filter :find_issue, :find_project_from_association, :authorize
before_filter :find_relation, :except => [:index, :create]
accept_api_auth :index, :show, :create, :destroy def new
def index
@relations = @issue.relations
respond_to do |format|
format.html { render :nothing => true }
format.api
end
end
def show
raise Unauthorized unless @relation.visible?
respond_to do |format|
format.html { render :nothing => true }
format.api
end
end
def create
@relation = IssueRelation.new(params[:relation]) @relation = IssueRelation.new(params[:relation])
@relation.issue_from = @issue @relation.issue_from = @issue
if params[:relation] && m = params[:relation][:issue_to_id].to_s.strip.match(/^#?(\d+)$/) if params[:relation] && m = params[:relation][:issue_to_id].to_s.match(/^#?(\d+)$/)
@relation.issue_to = Issue.visible.find_by_id(m[1].to_i) @relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
end end
saved = @relation.save @relation.save if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to issue_path(@issue) } format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js { format.js do
@relations = @issue.reload.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
} render :update do |page|
format.api { page.replace_html "relations", :partial => 'issues/relations'
if saved if @relation.errors.empty?
render :action => 'show', :status => :created, :location => relation_url(@relation) page << "$('relation_delay').value = ''"
else page << "$('relation_issue_to_id').value = ''"
render_validation_errors(@relation) end
end end
} end
end end
end end
def destroy def destroy
raise Unauthorized unless @relation.deletable? relation = IssueRelation.find(params[:id])
@relation.destroy if request.post? && @issue.relations.include?(relation)
relation.destroy
@issue.reload
end
respond_to do |format| respond_to do |format|
format.html { redirect_to issue_path(@relation.issue_from) } format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js format.js {
format.api { render_api_ok } @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'}
}
end end
end end
@@ -79,10 +61,4 @@ private
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def find_relation
@relation = IssueRelation.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+14 -20
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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
@@ -18,20 +18,14 @@
class IssueStatusesController < ApplicationController class IssueStatusesController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin, :except => :index before_filter :require_admin
before_filter :require_admin_or_api_request, :only => :index
accept_api_auth :index verify :method => :post, :only => [ :destroy, :create, :update, :move, :update_issue_done_ratio ],
:redirect_to => { :action => :index }
def index def index
respond_to do |format| @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
format.html { render :action => "index", :layout => false if request.xhr?
@issue_status_pages, @issue_statuses = paginate IssueStatus.sorted, :per_page => 25
render :action => "index", :layout => false if request.xhr?
}
format.api {
@issue_statuses = IssueStatus.all(:order => 'position')
}
end
end end
def new def new
@@ -42,7 +36,7 @@ class IssueStatusesController < ApplicationController
@issue_status = IssueStatus.new(params[:issue_status]) @issue_status = IssueStatus.new(params[:issue_status])
if @issue_status.save if @issue_status.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to issue_statuses_path redirect_to :action => 'index'
else else
render :action => 'new' render :action => 'new'
end end
@@ -56,7 +50,7 @@ class IssueStatusesController < ApplicationController
@issue_status = IssueStatus.find(params[:id]) @issue_status = IssueStatus.find(params[:id])
if @issue_status.update_attributes(params[:issue_status]) if @issue_status.update_attributes(params[:issue_status])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to issue_statuses_path redirect_to :action => 'index'
else else
render :action => 'edit' render :action => 'edit'
end end
@@ -64,18 +58,18 @@ class IssueStatusesController < ApplicationController
def destroy def destroy
IssueStatus.find(params[:id]).destroy IssueStatus.find(params[:id]).destroy
redirect_to issue_statuses_path redirect_to :action => 'index'
rescue rescue
flash[:error] = l(:error_unable_delete_issue_status) flash[:error] = l(:error_unable_delete_issue_status)
redirect_to issue_statuses_path redirect_to :action => 'index'
end end
def update_issue_done_ratio def update_issue_done_ratio
if request.post? && IssueStatus.update_issue_done_ratios if IssueStatus.update_issue_done_ratios
flash[:notice] = l(:notice_issue_done_ratios_updated) flash[:notice] = l(:notice_issue_done_ratios_updated)
else else
flash[:error] = l(:error_issue_done_ratios_not_updated) flash[:error] = l(:error_issue_done_ratios_not_updated)
end end
redirect_to issue_statuses_path redirect_to :action => 'index'
end end
end end
+94 -235
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,13 @@ class IssuesController < ApplicationController
default_search_scope :issues default_search_scope :issues
before_filter :find_issue, :only => [:show, :edit, :update] before_filter :find_issue, :only => [:show, :edit, :update]
before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy] before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
before_filter :find_project, :only => [:new, :create, :update_form] before_filter :check_project_uniqueness, :only => [:move, :perform_move]
before_filter :find_project, :only => [:new, :create]
before_filter :authorize, :except => [:index] before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => [:index] before_filter :find_optional_project, :only => [:index]
before_filter :check_for_default_issue_status, :only => [:new, :create] before_filter :check_for_default_issue_status, :only => [:new, :create]
before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form] before_filter :build_new_issue_from_params, :only => [:new, :create]
accept_rss_auth :index, :show accept_rss_auth :index, :show
accept_api_auth :index, :show, :create, :update, :destroy accept_api_auth :index, :show, :create, :update, :destroy
@@ -50,13 +51,21 @@ class IssuesController < ApplicationController
include SortHelper include SortHelper
include IssuesHelper include IssuesHelper
helper :timelog helper :timelog
helper :gantt
include Redmine::Export::PDF include Redmine::Export::PDF
verify :method => [:post, :delete],
:only => :destroy,
:render => { :nothing => true, :status => :method_not_allowed }
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def index def index
retrieve_query retrieve_query
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns) sort_update(@query.sortable_columns)
@query.sort_criteria = sort_criteria.to_a
if @query.valid? if @query.valid?
case params[:format] case params[:format]
@@ -71,8 +80,8 @@ class IssuesController < ApplicationController
end end
@issue_count = @query.issue_count @issue_count = @query.issue_count
@issue_pages = Paginator.new @issue_count, @limit, params['page'] @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
@offset ||= @issue_pages.offset @offset ||= @issue_pages.current.offset
@issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version], @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
:order => sort_clause, :order => sort_clause,
:offset => @offset, :offset => @offset,
@@ -80,55 +89,40 @@ class IssuesController < ApplicationController
@issue_count_by_group = @query.issue_count_by_group @issue_count_by_group = @query.issue_count_by_group
respond_to do |format| respond_to do |format|
format.html { render :template => 'issues/index', :layout => !request.xhr? } format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
format.api { format.api
Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
}
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') } format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'issues.pdf') } format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
end end
else else
respond_to do |format| # Send html if the query is not valid
format.html { render(:template => 'issues/index', :layout => !request.xhr?) } render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
format.any(:atom, :csv, :pdf) { render(:nothing => true) }
format.api { render_validation_errors(@query) }
end
end end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def show def show
@journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
@journals.each_with_index {|j,i| j.indice = i+1} @journals.each_with_index {|j,i| j.indice = i+1}
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
Journal.preload_journals_details_custom_fields(@journals)
# TODO: use #select! when ruby1.8 support is dropped
@journals.reject! {|journal| !journal.notes? && journal.visible_details.empty?}
@journals.reverse! if User.current.wants_comments_in_reverse_order? @journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.all if User.current.allowed_to?(:view_changesets, @project)
@changesets.reverse! if User.current.wants_comments_in_reverse_order? @changesets = @issue.changesets.visible.all
@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.active @priorities = IssuePriority.all
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@relation = IssueRelation.new
respond_to do |format| respond_to do |format|
format.html { format.html { render :template => 'issues/show.rhtml' }
retrieve_previous_and_next_issue_ids
render :template => 'issues/show'
}
format.api format.api
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' } format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
format.pdf { format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
pdf = issue_to_pdf(@issue, :journals => @journals)
send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
}
end end
end end
@@ -137,24 +131,21 @@ class IssuesController < ApplicationController
def new def new
respond_to do |format| respond_to do |format|
format.html { render :action => 'new', :layout => !request.xhr? } format.html { render :action => 'new', :layout => !request.xhr? }
format.js { render :partial => 'attributes' }
end end
end end
def create def create
call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue }) call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
if @issue.save if @issue.save
attachments = Attachment.attach_files(@issue, params[:attachments])
render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_create)
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue}) call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
respond_to do |format| respond_to do |format|
format.html { format.html {
render_attachment_warning_if_needed(@issue) redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject)) { :action => 'show', :id => @issue })
if params[:continue]
attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
redirect_to new_project_issue_path(@issue.project, :issue => attrs)
else
redirect_to issue_path(@issue)
end
} }
format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) } format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
end end
@@ -168,7 +159,9 @@ class IssuesController < ApplicationController
end end
def edit def edit
return unless update_issue_from_params update_issue_from_params
@journal = @issue.current_journal
respond_to do |format| respond_to do |format|
format.html { } format.html { }
@@ -177,28 +170,21 @@ class IssuesController < ApplicationController
end end
def update def update
return unless update_issue_from_params update_issue_from_params
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
saved = false
begin
saved = save_issue_with_child_records
rescue ActiveRecord::StaleObjectError
@conflict = true
if params[:last_journal_id]
@conflict_journals = @issue.journals_after(params[:last_journal_id]).all
@conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
end
end
if saved if @issue.save_issue_with_child_records(params, @time_entry)
render_attachment_warning_if_needed(@issue) render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default issue_path(@issue) } format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
format.api { render_api_ok } format.api { head :ok }
end end
else else
render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
@journal = @issue.current_journal
respond_to do |format| respond_to do |format|
format.html { render :action => 'edit' } format.html { render :action => 'edit' }
format.api { render_validation_errors(@issue) } format.api { render_validation_errors(@issue) }
@@ -206,105 +192,36 @@ class IssuesController < ApplicationController
end end
end end
# Updates the issue form when changing the project, status or tracker # Bulk edit a set of issues
# on issue creation/update
def update_form
end
# Bulk edit/copy a set of issues
def bulk_edit def bulk_edit
@issues.sort! @issues.sort!
@copy = params[:copy].present? @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
@notes = params[:notes] @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
@assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
if User.current.allowed_to?(:move_issues, @projects) @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
@allowed_projects = Issue.allowed_target_projects_on_move
if params[:issue]
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
if @target_project
target_projects = [@target_project]
end
end
end
target_projects ||= @projects
if @copy
@available_statuses = [IssueStatus.default]
else
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
end
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields.visible}.reduce(:&)
@assignables = target_projects.map(&:assignable_users).reduce(:&)
@trackers = target_projects.map(&:trackers).reduce(:&)
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
@categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
if @copy
@attachments_present = @issues.detect {|i| i.attachments.any?}.present?
@subtasks_present = @issues.detect {|i| !i.leaf?}.present?
end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
@issue_params = params[:issue] || {}
@issue_params[:custom_field_values] ||= {}
end end
def bulk_update def bulk_update
@issues.sort! @issues.sort!
@copy = params[:copy].present?
attributes = parse_params_for_bulk_issue_attributes(params) attributes = parse_params_for_bulk_issue_attributes(params)
unsaved_issues = [] unsaved_issue_ids = []
saved_issues = [] @issues.each do |issue|
issue.reload
if @copy && params[:copy_subtasks].present?
# Descendant issues will be copied with the parent task
# Don't copy them twice
@issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
end
@issues.each do |orig_issue|
orig_issue.reload
if @copy
issue = orig_issue.copy({},
:attachments => params[:copy_attachments].present?,
:subtasks => params[:copy_subtasks].present?
)
else
issue = orig_issue
end
journal = issue.init_journal(User.current, params[:notes]) journal = issue.init_journal(User.current, params[:notes])
issue.safe_attributes = attributes issue.safe_attributes = attributes
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
if issue.save unless issue.save
saved_issues << issue # Keep unsaved issue ids to display them in flash error
else unsaved_issue_ids << issue.id
unsaved_issues << orig_issue
end end
end end
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
if unsaved_issues.empty? redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
if params[:follow]
if @issues.size == 1 && saved_issues.size == 1
redirect_to issue_path(saved_issues.first)
elsif saved_issues.map(&:project).uniq.size == 1
redirect_to project_issues_path(saved_issues.map(&:project).first)
end
else
redirect_back_or_default _project_issues_path(@project)
end
else
@saved_issues = @issues
@unsaved_issues = unsaved_issues
@issues = Issue.visible.find_all_by_id(@unsaved_issues.map(&:id))
bulk_edit
render :action => 'bulk_edit'
end
end end
def destroy def destroy
@hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
if @hours > 0 if @hours > 0
case params[:todo] case params[:todo]
when 'destroy' when 'destroy'
@@ -332,65 +249,45 @@ class IssuesController < ApplicationController
end end
end end
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default _project_issues_path(@project) } format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
format.api { render_api_ok } format.api { head :ok }
end end
end end
private private
def find_issue
def find_project # Issue.visible.find(...) can not be used to redirect user to the login form
project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id]) # if the issue actually exists but requires authentication
@project = Project.find(project_id) @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
unless @issue.visible?
deny_access
return
end
@project = @issue.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def retrieve_previous_and_next_issue_ids def find_project
retrieve_query_from_session project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
if @query @project = Project.find(project_id)
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) rescue ActiveRecord::RecordNotFound
sort_update(@query.sortable_columns, 'issues_index_sort') render_404
limit = 500
issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
if (idx = issue_ids.index(@issue.id)) && idx < limit
if issue_ids.size < 500
@issue_position = idx + 1
@issue_count = issue_ids.size
end
@prev_issue_id = issue_ids[idx - 1] if idx > 0
@next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
end
end
end end
# Used by #edit and #update to set some common instance variables # Used by #edit and #update to set some common instance variables
# from the params # from the params
# TODO: Refactor, not everything in here is needed by #edit # TODO: Refactor, not everything in here is needed by #edit
def update_issue_from_params def update_issue_from_params
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@priorities = IssuePriority.all
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@time_entry.attributes = params[:time_entry] @time_entry.attributes = params[:time_entry]
@issue.init_journal(User.current) @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
@issue.init_journal(User.current, @notes)
issue_attributes = params[:issue] @issue.safe_attributes = params[:issue]
if issue_attributes && params[:conflict_resolution]
case params[:conflict_resolution]
when 'overwrite'
issue_attributes = issue_attributes.dup
issue_attributes.delete(:lock_version)
when 'add_notes'
issue_attributes = issue_attributes.slice(:notes)
when 'cancel'
redirect_to issue_path(@issue)
return false
end
end
@issue.safe_attributes = issue_attributes
@priorities = IssuePriority.active
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
true
end end
# TODO: Refactor, lots of extra code in here # TODO: Refactor, lots of extra code in here
@@ -398,36 +295,29 @@ class IssuesController < ApplicationController
def build_new_issue_from_params def build_new_issue_from_params
if params[:id].blank? if params[:id].blank?
@issue = Issue.new @issue = Issue.new
if params[:copy_from] @issue.copy_from(params[:copy_from]) if params[:copy_from]
begin
@copy_from = Issue.visible.find(params[:copy_from])
@copy_attachments = params[:copy_attachments].present? || request.get?
@copy_subtasks = params[:copy_subtasks].present? || request.get?
@issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks)
rescue ActiveRecord::RecordNotFound
render_404
return
end
end
@issue.project = @project @issue.project = @project
else else
@issue = @project.issues.visible.find(params[:id]) @issue = @project.issues.visible.find(params[:id])
end end
@issue.project = @project @issue.project = @project
@issue.author ||= User.current @issue.author = User.current
# Tracker must be set before custom field values # Tracker must be set before custom field values
@issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first) @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
if @issue.tracker.nil? if @issue.tracker.nil?
render_error l(:error_no_tracker_in_project) render_error l(:error_no_tracker_in_project)
return false return false
end end
@issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date? @issue.start_date ||= Date.today
@issue.safe_attributes = params[:issue] if params[:issue].is_a?(Hash)
@issue.safe_attributes = params[:issue]
@priorities = IssuePriority.active if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
@issue.watcher_user_ids = params[:issue]['watcher_user_ids']
end
end
@priorities = IssuePriority.all
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, true) @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
@available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
end end
def check_for_default_issue_status def check_for_default_issue_status
@@ -440,38 +330,7 @@ class IssuesController < ApplicationController
def parse_params_for_bulk_issue_attributes(params) def parse_params_for_bulk_issue_attributes(params)
attributes = (params[:issue] || {}).reject {|k,v| v.blank?} attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
if custom = attributes[:custom_field_values] attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
custom.reject! {|k,v| v.blank?}
custom.keys.each do |k|
if custom[k].is_a?(Array)
custom[k] << '' if custom[k].delete('__none__')
else
custom[k] = '' if custom[k] == '__none__'
end
end
end
attributes attributes
end end
# Saves @issue and a time_entry from the parameters
def save_issue_with_child_records
Issue.transaction do
if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
time_entry = @time_entry || TimeEntry.new
time_entry.project = @issue.project
time_entry.issue = @issue
time_entry.user = User.current
time_entry.spent_on = User.current.today
time_entry.attributes = params[:time_entry]
@issue.time_entries << time_entry
end
call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
if @issue.save
call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
else
raise ActiveRecord::Rollback
end
end
end
end end
+25 -11
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,20 +57,26 @@ class JournalsController < ApplicationController
end end
def new def new
@journal = Journal.visible.find(params[:journal_id]) if params[:journal_id] journal = Journal.find(params[:journal_id]) if params[:journal_id]
if @journal if journal
user = @journal.user user = journal.user
text = @journal.notes text = journal.notes
else else
user = @issue.author user = @issue.author
text = @issue.description text = @issue.description
end end
# Replaces pre blocks with [...] # Replaces pre blocks with [...]
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]') text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
@content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> " content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
@content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
rescue ActiveRecord::RecordNotFound
render_404 render(:update) { |page|
page.<< "$('notes').value = \"#{escape_javascript content}\";"
page.show 'update'
page << "Form.Element.focus('notes');"
page << "Element.scrollTo('update');"
page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
}
end end
def edit def edit
@@ -80,7 +86,7 @@ class JournalsController < ApplicationController
@journal.destroy if @journal.details.empty? && @journal.notes.blank? @journal.destroy if @journal.details.empty? && @journal.notes.blank?
call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params}) call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
respond_to do |format| respond_to do |format|
format.html { redirect_to issue_path(@journal.journalized) } format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id }
format.js { render :action => 'update' } format.js { render :action => 'update' }
end end
else else
@@ -97,9 +103,17 @@ class JournalsController < ApplicationController
private private
def find_journal def find_journal
@journal = Journal.visible.find(params[:id]) @journal = Journal.find(params[:id])
@project = @journal.journalized.project @project = @journal.journalized.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
# TODO: duplicated in IssuesController
def find_issue
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
end end
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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
@@ -15,15 +15,11 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require File.expand_path('../../test_helper', __FILE__) class LdapAuthSourcesController < AuthSourcesController
class UsersTest < ActionController::IntegrationTest protected
fixtures :users
def test_destroy_should_not_accept_get_requests def auth_source_class
assert_no_difference 'User.count' do AuthSourceLdap
get '/users/destroy/2', {}, credentials('admin')
assert_response 404
end
end end
end end
+5 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,6 +18,10 @@
class MailHandlerController < ActionController::Base class MailHandlerController < ActionController::Base
before_filter :check_credential before_filter :check_credential
verify :method => :post,
:only => :index,
:render => { :nothing => true, :status => 405 }
# Submits an incoming email to MailHandler # Submits an incoming email to MailHandler
def index def index
options = params.dup options = params.dup
+56 -81
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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,109 +17,84 @@
class MembersController < ApplicationController class MembersController < ApplicationController
model_object Member model_object Member
before_filter :find_model_object, :except => [:index, :create, :autocomplete] before_filter :find_model_object, :except => [:new, :autocomplete_for_member]
before_filter :find_project_from_association, :except => [:index, :create, :autocomplete] before_filter :find_project_from_association, :except => [:new, :autocomplete_for_member]
before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete] before_filter :find_project, :only => [:new, :autocomplete_for_member]
before_filter :authorize before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
def index def new
@offset, @limit = api_offset_and_limit
@member_count = @project.member_principals.count
@member_pages = Paginator.new @member_count, @limit, params['page']
@offset ||= @member_pages.offset
@members = @project.member_principals.all(
:order => "#{Member.table_name}.id",
:limit => @limit,
:offset => @offset
)
respond_to do |format|
format.html { head 406 }
format.api
end
end
def show
respond_to do |format|
format.html { head 406 }
format.api
end
end
def create
members = [] members = []
if params[:membership] if params[:member] && request.post?
if params[:membership][:user_ids] attrs = params[:member].dup
attrs = params[:membership].dup if (user_ids = attrs.delete(:user_ids))
user_ids = attrs.delete(:user_ids)
user_ids.each do |user_id| user_ids.each do |user_id|
members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id) members << Member.new(attrs.merge(:user_id => user_id))
end end
else else
members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id]) members << Member.new(attrs)
end end
@project.members << members @project.members << members
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to_settings_in_projects } if members.present? && members.all? {|m| m.valid? }
format.js { @members = members }
format.api { format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
@member = members.first
if @member.valid? format.js {
render :action => 'show', :status => :created, :location => membership_url(@member) render(:update) {|page|
else page.replace_html "tab-content-members", :partial => 'projects/settings/members'
render_validation_errors(@member) page << 'hideOnLoad()'
end members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
} }
}
else
format.js {
render(:update) {|page|
errors = members.collect {|m|
m.errors.full_messages
}.flatten.uniq
page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', ')))
}
}
end
end end
end end
def update def edit
if params[:membership] if request.post? and @member.update_attributes(params[:member])
@member.role_ids = params[:membership][:role_ids] respond_to do |format|
end format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
saved = @member.save format.js {
respond_to do |format| render(:update) {|page|
format.html { redirect_to_settings_in_projects } page.replace_html "tab-content-members", :partial => 'projects/settings/members'
format.js page << 'hideOnLoad()'
format.api { page.visual_effect(:highlight, "member-#{@member.id}")
if saved }
render_api_ok }
else end
render_validation_errors(@member)
end
}
end end
end end
def destroy def destroy
if request.delete? && @member.deletable? if request.post? && @member.deletable?
@member.destroy @member.destroy
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to_settings_in_projects } format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js format.js { render(:update) {|page|
format.api { page.replace_html "tab-content-members", :partial => 'projects/settings/members'
if @member.destroyed? page << 'hideOnLoad()'
render_api_ok }
else
head :unprocessable_entity
end
} }
end end
end end
def autocomplete def autocomplete_for_member
respond_to do |format| @principals = Principal.active.like(params[:q]).find(:all, :limit => 100) - @project.principals
format.js render :layout => false
end
end end
private
def redirect_to_settings_in_projects
redirect_to settings_project_path(@project, :tab => 'members')
end
end end
+48 -41
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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,11 +19,12 @@ class MessagesController < ApplicationController
menu_item :boards menu_item :boards
default_search_scope :messages default_search_scope :messages
before_filter :find_board, :only => [:new, :preview] before_filter :find_board, :only => [:new, :preview]
before_filter :find_attachments, :only => [:preview]
before_filter :find_message, :except => [:new, :preview] before_filter :find_message, :except => [:new, :preview]
before_filter :authorize, :except => [:preview, :edit, :destroy] before_filter :authorize, :except => [:preview, :edit, :destroy]
helper :boards verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
verify :xhr => true, :only => :quote
helper :watchers helper :watchers
helper :attachments helper :attachments
include AttachmentsHelper include AttachmentsHelper
@@ -35,18 +36,16 @@ class MessagesController < ApplicationController
page = params[:page] page = params[:page]
# Find the page of the requested reply # Find the page of the requested reply
if params[:r] && page.nil? if params[:r] && page.nil?
offset = @topic.children.where("#{Message.table_name}.id < ?", params[:r].to_i).count offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i])
page = 1 + offset / REPLIES_PER_PAGE page = 1 + offset / REPLIES_PER_PAGE
end end
@reply_count = @topic.children.count @reply_count = @topic.children.count
@reply_pages = Paginator.new @reply_count, REPLIES_PER_PAGE, page @reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page
@replies = @topic.children. @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}],
includes(:author, :attachments, {:board => :project}). :order => "#{Message.table_name}.created_on ASC",
reorder("#{Message.table_name}.created_on ASC"). :limit => @reply_pages.items_per_page,
limit(@reply_pages.per_page). :offset => @reply_pages.current.offset)
offset(@reply_pages.offset).
all
@reply = Message.new(:subject => "RE: #{@message.subject}") @reply = Message.new(:subject => "RE: #{@message.subject}")
render :action => "show", :layout => false if request.xhr? render :action => "show", :layout => false if request.xhr?
@@ -54,78 +53,87 @@ class MessagesController < ApplicationController
# Create a new topic # Create a new topic
def new def new
@message = Message.new @message = Message.new(params[:message])
@message.author = User.current @message.author = User.current
@message.board = @board @message.board = @board
@message.safe_attributes = params[:message] if params[:message] && User.current.allowed_to?(:edit_messages, @project)
if request.post? @message.locked = params[:message]['locked']
@message.save_attachments(params[:attachments]) @message.sticky = params[:message]['sticky']
if @message.save end
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message}) if request.post? && @message.save
render_attachment_warning_if_needed(@message) call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
redirect_to board_message_path(@board, @message) attachments = Attachment.attach_files(@message, params[:attachments])
end render_attachment_warning_if_needed(@message)
redirect_to :action => 'show', :id => @message
end end
end end
# Reply to a topic # Reply to a topic
def reply def reply
@reply = Message.new @reply = Message.new(params[:reply])
@reply.author = User.current @reply.author = User.current
@reply.board = @board @reply.board = @board
@reply.safe_attributes = params[:reply]
@topic.children << @reply @topic.children << @reply
if !@reply.new_record? if !@reply.new_record?
call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply}) call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
attachments = Attachment.attach_files(@reply, params[:attachments]) attachments = Attachment.attach_files(@reply, params[:attachments])
render_attachment_warning_if_needed(@reply) render_attachment_warning_if_needed(@reply)
end end
redirect_to board_message_path(@board, @topic, :r => @reply) redirect_to :action => 'show', :id => @topic, :r => @reply
end end
# Edit a message # Edit a message
def edit def edit
(render_403; return false) unless @message.editable_by?(User.current) (render_403; return false) unless @message.editable_by?(User.current)
@message.safe_attributes = params[:message] if params[:message]
if request.post? && @message.save @message.locked = params[:message]['locked']
@message.sticky = params[:message]['sticky']
end
if request.post? && @message.update_attributes(params[:message])
attachments = Attachment.attach_files(@message, params[:attachments]) attachments = Attachment.attach_files(@message, params[:attachments])
render_attachment_warning_if_needed(@message) render_attachment_warning_if_needed(@message)
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
@message.reload @message.reload
redirect_to board_message_path(@message.board, @message.root, :r => (@message.parent_id && @message.id)) redirect_to :action => 'show', :board_id => @message.board, :id => @message.root, :r => (@message.parent_id && @message.id)
end end
end end
# Delete a messages # Delete a messages
def destroy def destroy
(render_403; return false) unless @message.destroyable_by?(User.current) (render_403; return false) unless @message.destroyable_by?(User.current)
r = @message.to_param
@message.destroy @message.destroy
if @message.parent redirect_to @message.parent.nil? ?
redirect_to board_message_path(@board, @message.parent, :r => r) { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
else { :action => 'show', :id => @message.parent, :r => @message }
redirect_to project_board_path(@project, @board)
end
end end
def quote def quote
@subject = @message.subject user = @message.author
@subject = "RE: #{@subject}" unless @subject.starts_with?('RE:') text = @message.content
subject = @message.subject.gsub('"', '\"')
@content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> " subject = "RE: #{subject}" unless subject.starts_with?('RE:')
@content << @message.content.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
render(:update) { |page|
page << "$('reply_subject').value = \"#{subject}\";"
page.<< "$('message_content').value = \"#{content}\";"
page.show 'reply'
page << "Form.Element.focus('message_content');"
page << "Element.scrollTo('reply');"
page << "$('message_content').scrollTop = $('message_content').scrollHeight - $('message_content').clientHeight;"
}
end end
def preview def preview
message = @board.messages.find_by_id(params[:id]) message = @board.messages.find_by_id(params[:id])
@attachements = message.attachments if message
@text = (params[:message] || params[:reply])[:content] @text = (params[:message] || params[:reply])[:content]
@previewed = message
render :partial => 'common/preview' render :partial => 'common/preview'
end end
private private
def find_message def find_message
return unless find_board find_board
@message = @board.messages.find(params[:id], :include => :parent) @message = @board.messages.find(params[:id], :include => :parent)
@topic = @message.root @topic = @message.root
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
@@ -137,6 +145,5 @@ private
@project = @board.project @project = @board.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
nil
end end
end end
+27 -51
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 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
@@ -17,8 +17,6 @@
class MyController < ApplicationController class MyController < ApplicationController
before_filter :require_login before_filter :require_login
# let user change user's password when user has to
skip_before_filter :check_password_change, :only => :password
helper :issues helper :issues
helper :users helper :users
@@ -37,6 +35,9 @@ class MyController < ApplicationController
'right' => ['issuesreportedbyme'] 'right' => ['issuesreportedbyme']
}.freeze }.freeze
verify :xhr => true,
:only => [:add_block, :remove_block, :order_blocks]
def index def index
page page
render :action => 'page' render :action => 'page'
@@ -55,54 +56,35 @@ class MyController < ApplicationController
if request.post? if request.post?
@user.safe_attributes = params[:user] @user.safe_attributes = params[:user]
@user.pref.attributes = params[:pref] @user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
if @user.save if @user.save
@user.pref.save @user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
set_language_if_valid @user.language set_language_if_valid @user.language
flash[:notice] = l(:notice_account_updated) flash[:notice] = l(:notice_account_updated)
redirect_to my_account_path redirect_to :action => 'account'
return return
end end
end end
end end
# Destroys user's account
def destroy
@user = User.current
unless @user.own_account_deletable?
redirect_to my_account_path
return
end
if request.post? && params[:confirm]
@user.destroy
if @user.destroyed?
logout_user
flash[:notice] = l(:notice_account_deleted)
end
redirect_to home_path
end
end
# Manage user's password # Manage user's password
def password def password
@user = User.current @user = User.current
unless @user.change_password_allowed? unless @user.change_password_allowed?
flash[:error] = l(:notice_can_t_change_password) flash[:error] = l(:notice_can_t_change_password)
redirect_to my_account_path redirect_to :action => 'account'
return return
end end
if request.post? if request.post?
if !@user.check_password?(params[:password]) if @user.check_password?(params[:password])
flash.now[:error] = l(:notice_account_wrong_password)
elsif params[:password] == params[:new_password]
flash.now[:error] = l(:notice_new_password_must_be_different)
else
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
@user.must_change_passwd = false
if @user.save if @user.save
flash[:notice] = l(:notice_account_password_updated) flash[:notice] = l(:notice_account_password_updated)
redirect_to my_account_path redirect_to :action => 'account'
end end
else
flash[:error] = l(:notice_account_wrong_password)
end end
end end
end end
@@ -117,7 +99,7 @@ class MyController < ApplicationController
User.current.rss_key User.current.rss_key
flash[:notice] = l(:notice_feeds_access_key_reseted) flash[:notice] = l(:notice_feeds_access_key_reseted)
end end
redirect_to my_account_path redirect_to :action => 'account'
end end
# Create a new API key # Create a new API key
@@ -130,7 +112,7 @@ class MyController < ApplicationController
User.current.api_key User.current.api_key
flash[:notice] = l(:notice_api_access_key_reseted) flash[:notice] = l(:notice_api_access_key_reseted)
end end
redirect_to my_account_path redirect_to :action => 'account'
end end
# User's page layout configuration # User's page layout configuration
@@ -138,11 +120,7 @@ class MyController < ApplicationController
@user = User.current @user = User.current
@blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
@block_options = [] @block_options = []
BLOCKS.each do |k, v| BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
unless %w(top left right).detect {|f| (@blocks[f] ||= []).include?(k)}
@block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]
end
end
end end
# Add a block to user's page # Add a block to user's page
@@ -150,17 +128,16 @@ class MyController < ApplicationController
# params[:block] : id of the block to add # params[:block] : id of the block to add
def add_block def add_block
block = params[:block].to_s.underscore block = params[:block].to_s.underscore
if block.present? && BLOCKS.key?(block) (render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
@user = User.current @user = User.current
layout = @user.pref[:my_page_layout] || {} layout = @user.pref[:my_page_layout] || {}
# remove if already present in a group # remove if already present in a group
%w(top left right).each {|f| (layout[f] ||= []).delete block } %w(top left right).each {|f| (layout[f] ||= []).delete block }
# add it on top # add it on top
layout['top'].unshift block layout['top'].unshift block
@user.pref[:my_page_layout] = layout @user.pref[:my_page_layout] = layout
@user.pref.save @user.pref.save
end render :partial => "block", :locals => {:user => @user, :block_name => block}
redirect_to my_page_layout_path
end end
# Remove a block to user's page # Remove a block to user's page
@@ -173,7 +150,7 @@ class MyController < ApplicationController
%w(top left right).each {|f| (layout[f] ||= []).delete block } %w(top left right).each {|f| (layout[f] ||= []).delete block }
@user.pref[:my_page_layout] = layout @user.pref[:my_page_layout] = layout
@user.pref.save @user.pref.save
redirect_to my_page_layout_path render :nothing => true
end end
# Change blocks order on user's page # Change blocks order on user's page
@@ -183,8 +160,7 @@ class MyController < ApplicationController
group = params[:group] group = params[:group]
@user = User.current @user = User.current
if group.is_a?(String) if group.is_a?(String)
group_items = (params["blocks"] || []).collect(&:underscore) group_items = (params["list-#{group}"] || []).collect(&:underscore)
group_items.each {|s| s.sub!(/^block_/, '')}
if group_items and group_items.is_a? Array if group_items and group_items.is_a? Array
layout = @user.pref[:my_page_layout] || {} layout = @user.pref[:my_page_layout] || {}
# remove group blocks if they are presents in other groups # remove group blocks if they are presents in other groups
+22 -24
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,14 +20,13 @@ class NewsController < ApplicationController
model_object News model_object News
before_filter :find_model_object, :except => [:new, :create, :index] before_filter :find_model_object, :except => [:new, :create, :index]
before_filter :find_project_from_association, :except => [:new, :create, :index] before_filter :find_project_from_association, :except => [:new, :create, :index]
before_filter :find_project_by_project_id, :only => [:new, :create] before_filter :find_project, :only => [:new, :create]
before_filter :authorize, :except => [:index] before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => :index before_filter :find_optional_project, :only => :index
accept_rss_auth :index accept_rss_auth :index
accept_api_auth :index accept_api_auth :index
helper :watchers helper :watchers
helper :attachments
def index def index
case params[:format] case params[:format]
@@ -40,18 +39,15 @@ class NewsController < ApplicationController
scope = @project ? @project.news.visible : News.visible scope = @project ? @project.news.visible : News.visible
@news_count = scope.count @news_count = scope.count
@news_pages = Paginator.new @news_count, @limit, params['page'] @news_pages = Paginator.new self, @news_count, @limit, params['page']
@offset ||= @news_pages.offset @offset ||= @news_pages.current.offset
@newss = scope.all(:include => [:author, :project], @newss = scope.all(:include => [:author, :project],
:order => "#{News.table_name}.created_on DESC", :order => "#{News.table_name}.created_on DESC",
:offset => @offset, :offset => @offset,
:limit => @limit) :limit => @limit)
respond_to do |format| respond_to do |format|
format.html { format.html { render :layout => false if request.xhr? }
@news = News.new # for adding news inline
render :layout => false if request.xhr?
}
format.api format.api
format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") } format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
end end
@@ -68,14 +64,14 @@ class NewsController < ApplicationController
def create def create
@news = News.new(:project => @project, :author => User.current) @news = News.new(:project => @project, :author => User.current)
@news.safe_attributes = params[:news] if request.post?
@news.save_attachments(params[:attachments]) @news.attributes = params[:news]
if @news.save if @news.save
render_attachment_warning_if_needed(@news) flash[:notice] = l(:notice_successful_create)
flash[:notice] = l(:notice_successful_create) redirect_to :controller => 'news', :action => 'index', :project_id => @project
redirect_to project_news_index_path(@project) else
else render :action => 'new'
render :action => 'new' end
end end
end end
@@ -83,12 +79,9 @@ class NewsController < ApplicationController
end end
def update def update
@news.safe_attributes = params[:news] if request.put? and @news.update_attributes(params[:news])
@news.save_attachments(params[:attachments])
if @news.save
render_attachment_warning_if_needed(@news)
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to news_path(@news) redirect_to :action => 'show', :id => @news
else else
render :action => 'edit' render :action => 'edit'
end end
@@ -96,10 +89,15 @@ class NewsController < ApplicationController
def destroy def destroy
@news.destroy @news.destroy
redirect_to project_news_index_path(@project) redirect_to :action => 'index', :project_id => @project
end end
private private
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
def find_optional_project def find_optional_project
return true unless params[:project_id] return true unless params[:project_id]
+4 -7
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,17 +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 PreviewsController < ApplicationController class PreviewsController < ApplicationController
before_filter :find_project, :find_attachments before_filter :find_project
def issue def issue
@issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
if @issue if @issue
@attachements = @issue.attachments
@description = params[:issue] && params[:issue][:description] @description = params[:issue] && params[:issue][:description]
if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n") if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n")
@description = nil @description = nil
end end
# params[:notes] is useful for preview of notes in issue history @notes = params[:notes]
@notes = params[:notes] || (params[:issue] ? params[:issue][:notes] : nil)
else else
@description = (params[:issue] ? params[:issue][:description] : nil) @description = (params[:issue] ? params[:issue][:description] : nil)
end end
@@ -34,9 +34,6 @@ class PreviewsController < ApplicationController
end end
def news def news
if params[:id].present? && news = News.visible.find_by_id(params[:id])
@previewed = news
end
@text = (params[:news] ? params[:news][:description] : nil) @text = (params[:news] ? params[:news][:description] : nil)
render :partial => 'common/preview' render :partial => 'common/preview'
end end
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ProjectEnumerationsController < ApplicationController class ProjectEnumerationsController < ApplicationController
before_filter :find_project_by_project_id before_filter :find_project_by_project_id
before_filter :authorize before_filter :authorize
@@ -29,7 +12,7 @@ class ProjectEnumerationsController < ApplicationController
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
end end
redirect_to settings_project_path(@project, :tab => 'activities') redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end end
def destroy def destroy
@@ -37,6 +20,7 @@ class ProjectEnumerationsController < ApplicationController
time_entry_activity.destroy(time_entry_activity.parent) time_entry_activity.destroy(time_entry_activity.parent)
end end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project, :tab => 'activities') redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end end
end end
+67 -60
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -29,7 +29,7 @@ class ProjectsController < ApplicationController
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller| after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
if controller.request.post? if controller.request.post?
controller.send :expire_action, :controller => 'welcome', :action => 'robots' controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
end end
end end
@@ -43,46 +43,42 @@ class ProjectsController < ApplicationController
helper :repositories helper :repositories
include RepositoriesHelper include RepositoriesHelper
include ProjectsHelper include ProjectsHelper
helper :members
# Lists visible projects # Lists visible projects
def index def index
respond_to do |format| respond_to do |format|
format.html { format.html {
scope = Project @projects = Project.visible.find(:all, :order => 'lft')
unless params[:closed]
scope = scope.active
end
@projects = scope.visible.order('lft').all
} }
format.api { format.api {
@offset, @limit = api_offset_and_limit @offset, @limit = api_offset_and_limit
@project_count = Project.visible.count @project_count = Project.visible.count
@projects = Project.visible.offset(@offset).limit(@limit).order('lft').all @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => 'lft')
} }
format.atom { format.atom {
projects = Project.visible.order('created_on DESC').limit(Setting.feeds_limit.to_i).all projects = Project.visible.find(:all, :order => 'created_on DESC',
:limit => Setting.feeds_limit.to_i)
render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}") render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
} }
end end
end end
def new def new
@issue_custom_fields = IssueCustomField.sorted.all @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.sorted.all @trackers = Tracker.all
@project = Project.new @project = Project.new(params[:project])
@project.safe_attributes = params[:project]
end end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
@issue_custom_fields = IssueCustomField.sorted.all @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.sorted.all @trackers = Tracker.all
@project = Project.new @project = Project.new
@project.safe_attributes = params[:project] @project.safe_attributes = params[:project]
if validate_parent_id && @project.save if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
# Add current user as a project member if current user is not admin # Add current user as a project member if he is not admin
unless User.current.admin? unless User.current.admin?
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
m = Member.new(:user => User.current, :roles => [r]) m = Member.new(:user => User.current, :roles => [r])
@@ -91,12 +87,7 @@ class ProjectsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
if params[:continue] redirect_to :controller => 'projects', :action => 'settings', :id => @project
attrs = {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}
redirect_to new_project_path(attrs)
else
redirect_to settings_project_path(@project)
end
} }
format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
end end
@@ -106,15 +97,23 @@ class ProjectsController < ApplicationController
format.api { render_validation_errors(@project) } format.api { render_validation_errors(@project) }
end end
end end
end end
def copy def copy
@issue_custom_fields = IssueCustomField.sorted.all @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.sorted.all @trackers = Tracker.all
@root_projects = Project.find(:all,
:conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
:order => 'name')
@source_project = Project.find(params[:id]) @source_project = Project.find(params[:id])
if request.get? if request.get?
@project = Project.copy_from(@source_project) @project = Project.copy_from(@source_project)
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers? if @project
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
else
redirect_to :controller => 'admin', :action => 'projects'
end
else else
Mailer.with_deliveries(params[:notifications] == '1') do Mailer.with_deliveries(params[:notifications] == '1') do
@project = Project.new @project = Project.new
@@ -122,40 +121,43 @@ class ProjectsController < ApplicationController
if validate_parent_id && @project.copy(@source_project, :only => params[:only]) if validate_parent_id && @project.copy(@source_project, :only => params[:only])
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to settings_project_path(@project) redirect_to :controller => 'projects', :action => 'settings', :id => @project
elsif !@project.new_record? elsif !@project.new_record?
# Project was created # Project was created
# But some objects were not copied due to validation failures # But some objects were not copied due to validation failures
# (eg. issues from disabled trackers) # (eg. issues from disabled trackers)
# TODO: inform about that # TODO: inform about that
redirect_to settings_project_path(@project) redirect_to :controller => 'projects', :action => 'settings', :id => @project
end end
end end
end end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
# source_project not found redirect_to :controller => 'admin', :action => 'projects'
render_404
end end
# Show @project # Show @project
def show def show
# try to redirect to the requested menu item if params[:jump]
if params[:jump] && redirect_to_project_menu_item(@project, params[:jump]) # try to redirect to the requested menu item
return redirect_to_project_menu_item(@project, params[:jump]) && return
end end
@users_by_role = @project.users_by_role @users_by_role = @project.users_by_role
@subprojects = @project.children.visible.all @subprojects = @project.children.visible.all
@news = @project.news.limit(5).includes(:author, :project).reorder("#{News.table_name}.created_on DESC").all @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@trackers = @project.rolled_up_trackers @trackers = @project.rolled_up_trackers
cond = @project.project_condition(Setting.display_subprojects_issues?) cond = @project.project_condition(Setting.display_subprojects_issues?)
@open_issues_by_tracker = Issue.visible.open.where(cond).count(:group => :tracker) @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
@total_issues_by_tracker = Issue.visible.where(cond).count(:group => :tracker) :include => [:project, :status, :tracker],
:conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
@total_issues_by_tracker = Issue.visible.count(:group => :tracker,
:include => [:project, :status, :tracker],
:conditions => cond)
if User.current.allowed_to?(:view_time_entries, @project) if User.current.allowed_to?(:view_time_entries, @project)
@total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f @total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
end end
@key = User.current.rss_key @key = User.current.rss_key
@@ -167,16 +169,19 @@ class ProjectsController < ApplicationController
end end
def settings def settings
@issue_custom_fields = IssueCustomField.sorted.all @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@issue_category ||= IssueCategory.new @issue_category ||= IssueCategory.new
@member ||= @project.members.new @member ||= @project.members.new
@trackers = Tracker.sorted.all @trackers = Tracker.all
@repository ||= @project.repository
@wiki ||= @project.wiki @wiki ||= @project.wiki
end end
def edit def edit
end end
# TODO: convert to PUT only
verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update def update
@project.safe_attributes = params[:project] @project.safe_attributes = params[:project]
if validate_parent_id && @project.save if validate_parent_id && @project.save
@@ -184,9 +189,9 @@ class ProjectsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project) redirect_to :action => 'settings', :id => @project
} }
format.api { render_api_ok } format.api { head :ok }
end end
else else
respond_to do |format| respond_to do |format|
@@ -199,10 +204,11 @@ class ProjectsController < ApplicationController
end end
end end
verify :method => :post, :only => :modules, :render => {:nothing => true, :status => :method_not_allowed }
def modules def modules
@project.enabled_module_names = params[:enabled_module_names] @project.enabled_module_names = params[:enabled_module_names]
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project, :tab => 'modules') redirect_to :action => 'settings', :id => @project, :tab => 'modules'
end end
def archive def archive
@@ -211,39 +217,40 @@ class ProjectsController < ApplicationController
flash[:error] = l(:error_can_not_archive_project) flash[:error] = l(:error_can_not_archive_project)
end end
end end
redirect_to admin_projects_path(:status => params[:status]) redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end end
def unarchive def unarchive
@project.unarchive if request.post? && !@project.active? @project.unarchive if request.post? && !@project.active?
redirect_to admin_projects_path(:status => params[:status]) redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end
def close
@project.close
redirect_to project_path(@project)
end
def reopen
@project.reopen
redirect_to project_path(@project)
end end
# Delete @project # Delete @project
def destroy def destroy
@project_to_destroy = @project @project_to_destroy = @project
if api_request? || params[:confirm] if request.get?
@project_to_destroy.destroy # display confirmation view
respond_to do |format| else
format.html { redirect_to admin_projects_path } if api_request? || params[:confirm]
format.api { render_api_ok } @project_to_destroy.destroy
respond_to do |format|
format.html { redirect_to :controller => 'admin', :action => 'projects' }
format.api { head :ok }
end
end end
end end
# hide project in layout # hide project in layout
@project = nil @project = nil
end end
private private
def find_optional_project
return true unless params[:id]
@project = Project.find(params[:id])
authorize
rescue ActiveRecord::RecordNotFound
render_404
end
# Validates parent_id param according to user's permissions # Validates parent_id param according to user's permissions
# TODO: move it to Project model in a validation that depends on User.current # TODO: move it to Project model in a validation that depends on User.current
+31 -69
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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,80 +17,54 @@
class QueriesController < ApplicationController class QueriesController < ApplicationController
menu_item :issues menu_item :issues
before_filter :find_query, :except => [:new, :create, :index] before_filter :find_query, :except => :new
before_filter :find_optional_project, :only => [:new, :create] before_filter :find_optional_project, :only => :new
accept_api_auth :index
include QueriesHelper
def index
case params[:format]
when 'xml', 'json'
@offset, @limit = api_offset_and_limit
else
@limit = per_page_option
end
@query_count = IssueQuery.visible.count
@query_pages = Paginator.new @query_count, @limit, params['page']
@queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
respond_to do |format|
format.api
end
end
def new def new
@query = IssueQuery.new @query = Query.new(params[:query])
@query.user = User.current
@query.project = @project
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
end
def create
@query = IssueQuery.new(params[:query])
@query.user = User.current
@query.project = params[:query_is_for_all] ? nil : @project @query.project = params[:query_is_for_all] ? nil : @project
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.user = User.current
@query.build_from_params(params) @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]
if @query.save if request.post? && params[:confirm] && @query.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to_issues(:query_id => @query) redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
else return
render :action => 'new', :layout => !request.xhr?
end end
render :layout => false if request.xhr?
end end
def edit def edit
end if request.post?
@query.filters = {}
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f]
@query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all]
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.group_by ||= params[:group_by]
@query.column_names = params[:c] if params[:c]
@query.column_names = nil if params[:default_columns]
def update if @query.save
@query.attributes = params[:query] flash[:notice] = l(:notice_successful_update)
@query.project = nil if params[:query_is_for_all] redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? end
@query.build_from_params(params)
@query.column_names = nil if params[:default_columns]
if @query.save
flash[:notice] = l(:notice_successful_update)
redirect_to_issues(:query_id => @query)
else
render :action => 'edit'
end end
end end
def destroy def destroy
@query.destroy @query.destroy if request.post?
redirect_to_issues(:set_filter => 1) redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
end end
private private
def find_query def find_query
@query = IssueQuery.find(params[:id]) @query = Query.find(params[:id])
@project = @query.project @project = @query.project
render_403 unless @query.editable_by?(User.current) render_403 unless @query.editable_by?(User.current)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
@@ -103,16 +77,4 @@ private
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def redirect_to_issues(options)
if params[:gantt]
if @project
redirect_to project_gantt_path(@project, options)
else
redirect_to issues_gantt_path(options)
end
else
redirect_to _project_issues_path(@project, options)
end
end
end end
+9 -9
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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
@@ -22,10 +22,10 @@ class ReportsController < ApplicationController
def issue_report def issue_report
@trackers = @project.trackers @trackers = @project.trackers
@versions = @project.shared_versions.sort @versions = @project.shared_versions.sort
@priorities = IssuePriority.all.reverse @priorities = IssuePriority.all
@categories = @project.issue_categories @categories = @project.issue_categories
@assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort @assignees = @project.members.collect { |m| m.user }.sort
@authors = @project.users.sort @authors = @project.members.collect { |m| m.user }.sort
@subprojects = @project.descendants.visible @subprojects = @project.descendants.visible
@issues_by_tracker = Issue.by_tracker(@project) @issues_by_tracker = Issue.by_tracker(@project)
@@ -53,7 +53,7 @@ class ReportsController < ApplicationController
@report_title = l(:field_version) @report_title = l(:field_version)
when "priority" when "priority"
@field = "priority_id" @field = "priority_id"
@rows = IssuePriority.all.reverse @rows = IssuePriority.all
@data = Issue.by_priority(@project) @data = Issue.by_priority(@project)
@report_title = l(:field_priority) @report_title = l(:field_priority)
when "category" when "category"
@@ -63,12 +63,12 @@ class ReportsController < ApplicationController
@report_title = l(:field_category) @report_title = l(:field_category)
when "assigned_to" when "assigned_to"
@field = "assigned_to_id" @field = "assigned_to_id"
@rows = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort @rows = @project.members.collect { |m| m.user }.sort
@data = Issue.by_assigned_to(@project) @data = Issue.by_assigned_to(@project)
@report_title = l(:field_assigned_to) @report_title = l(:field_assigned_to)
when "author" when "author"
@field = "author_id" @field = "author_id"
@rows = @project.users.sort @rows = @project.members.collect { |m| m.user }.sort
@data = Issue.by_author(@project) @data = Issue.by_author(@project)
@report_title = l(:field_author) @report_title = l(:field_author)
when "subproject" when "subproject"
@@ -90,6 +90,6 @@ class ReportsController < ApplicationController
private private
def find_issue_statuses def find_issue_statuses
@statuses = IssueStatus.sorted.all @statuses = IssueStatus.find(:all, :order => 'position')
end end
end end
+80 -143
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,77 +18,52 @@
require 'SVG/Graph/Bar' require 'SVG/Graph/Bar'
require 'SVG/Graph/BarHorizontal' require 'SVG/Graph/BarHorizontal'
require 'digest/sha1' require 'digest/sha1'
require 'redmine/scm/adapters/abstract_adapter'
class ChangesetNotFound < Exception; end class ChangesetNotFound < Exception; end
class InvalidRevisionParam < Exception; end class InvalidRevisionParam < Exception; end
class RepositoriesController < ApplicationController class RepositoriesController < ApplicationController
menu_item :repository menu_item :repository
menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers] menu_item :settings, :only => :edit
default_search_scope :changesets default_search_scope :changesets
before_filter :find_project_by_project_id, :only => [:new, :create] before_filter :find_repository, :except => :edit
before_filter :find_repository, :only => [:edit, :update, :destroy, :committers] before_filter :find_project, :only => :edit
before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
before_filter :authorize before_filter :authorize
accept_rss_auth :revisions accept_rss_auth :revisions
rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
def new
scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
@repository = Repository.factory(scm)
@repository.is_default = @project.repository.nil?
@repository.project = @project
end
def create
attrs = pickup_extra_info
@repository = Repository.factory(params[:repository_scm])
@repository.safe_attributes = params[:repository]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end
@repository.project = @project
if request.post? && @repository.save
redirect_to settings_project_path(@project, :tab => 'repositories')
else
render :action => 'new'
end
end
def edit def edit
end @repository = @project.repository
if !@repository && !params[:repository_scm].blank?
def update @repository = Repository.factory(params[:repository_scm])
attrs = pickup_extra_info @repository.project = @project if @repository
@repository.safe_attributes = attrs[:attrs]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end end
@repository.project = @project if request.post? && @repository
if request.put? && @repository.save p1 = params[:repository]
redirect_to settings_project_path(@project, :tab => 'repositories') p = {}
else p_extra = {}
render :action => 'edit' 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
end end
end render(:update) do |page|
page.replace_html "tab-content-repository",
def pickup_extra_info :partial => 'projects/settings/repository'
p = {} if @repository && !@project.repository
p_extra = {} @project.reload # needed to reload association
params[:repository].each do |k, v| page.replace_html "main-menu", render_main_menu(@project)
if k =~ /^extra_/
p_extra[k] = v
else
p[k] = v
end end
end end
{:attrs => p, :attrs_extra => p_extra}
end end
private :pickup_extra_info
def committers def committers
@committers = @repository.committers @committers = @repository.committers
@@ -101,17 +76,20 @@ class RepositoriesController < ApplicationController
# Build a hash with repository usernames as keys and corresponding user ids as values # Build a hash with repository usernames as keys and corresponding user ids as values
@repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h} @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project, :tab => 'repositories') redirect_to :action => 'committers', :id => @project
end end
end end
def destroy def destroy
@repository.destroy if request.delete? @repository.destroy
redirect_to settings_project_path(@project, :tab => 'repositories') redirect_to :controller => 'projects',
:action => 'settings',
:id => @project,
:tab => 'repository'
end end
def show def show
@repository.fetch_changesets if @project.active? && Setting.autofetch_changesets? && @path.empty? @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
@entries = @repository.entries(@path, @rev) @entries = @repository.entries(@path, @rev)
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
@@ -121,7 +99,6 @@ class RepositoriesController < ApplicationController
(show_error_not_found; return) unless @entries (show_error_not_found; return) unless @entries
@changesets = @repository.latest_changesets(@path, @rev) @changesets = @repository.latest_changesets(@path, @rev)
@properties = @repository.properties(@path, @rev) @properties = @repository.properties(@path, @rev)
@repositories = @project.repositories
render :action => 'show' render :action => 'show'
end end
end end
@@ -138,14 +115,13 @@ class RepositoriesController < ApplicationController
def revisions def revisions
@changeset_count = @repository.changesets.count @changeset_count = @repository.changesets.count
@changeset_pages = Paginator.new @changeset_count, @changeset_pages = Paginator.new self, @changeset_count,
per_page_option, per_page_option,
params['page'] params['page']
@changesets = @repository.changesets. @changesets = @repository.changesets.find(:all,
limit(@changeset_pages.per_page). :limit => @changeset_pages.items_per_page,
offset(@changeset_pages.offset). :offset => @changeset_pages.current.offset,
includes(:user, :repository, :parents). :include => [:user, :repository])
all
respond_to do |format| respond_to do |format|
format.html { render :layout => false if request.xhr? } format.html { render :layout => false if request.xhr? }
@@ -153,15 +129,7 @@ class RepositoriesController < ApplicationController
end end
end end
def raw
entry_and_raw(true)
end
def entry def entry
entry_and_raw(false)
end
def entry_and_raw(is_raw)
@entry = @repository.entry(@path, @rev) @entry = @repository.entry(@path, @rev)
(show_error_not_found; return) unless @entry (show_error_not_found; return) unless @entry
@@ -170,14 +138,13 @@ class RepositoriesController < ApplicationController
@content = @repository.cat(@path, @rev) @content = @repository.cat(@path, @rev)
(show_error_not_found; return) unless @content (show_error_not_found; return) unless @content
if is_raw || if 'raw' == params[:format] ||
(@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) ||
! is_entry_text_data?(@content, @path) ! is_entry_text_data?(@content, @path)
# Force the download # Force the download
send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) } send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
send_type = Redmine::MimeType.of(@path) send_type = Redmine::MimeType.of(@path)
send_opt[:type] = send_type.to_s if send_type send_opt[:type] = send_type.to_s if send_type
send_opt[:disposition] = (Redmine::MimeType.is_type?('image', @path) && !is_raw ? 'inline' : 'attachment')
send_data @content, send_opt send_data @content, send_opt
else else
# Prevent empty lines when displaying a file with Windows style eol # Prevent empty lines when displaying a file with Windows style eol
@@ -187,7 +154,6 @@ class RepositoriesController < ApplicationController
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
end end
end end
private :entry_and_raw
def is_entry_text_data?(ent, path) def is_entry_text_data?(ent, path)
# UTF-16 contains "\x00". # UTF-16 contains "\x00".
@@ -206,46 +172,21 @@ class RepositoriesController < ApplicationController
(show_error_not_found; return) unless @entry (show_error_not_found; return) unless @entry
@annotate = @repository.scm.annotate(@path, @rev) @annotate = @repository.scm.annotate(@path, @rev)
if @annotate.nil? || @annotate.empty? (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
(render_error l(:error_scm_annotate); return)
end
ann_buf_size = 0
@annotate.lines.each do |buf|
ann_buf_size += buf.size
end
if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte
(render_error l(:error_scm_annotate_big_text_file); return)
end
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
end end
def revision def revision
raise ChangesetNotFound if @rev.blank?
@changeset = @repository.find_changeset_by_name(@rev)
raise ChangesetNotFound unless @changeset
respond_to do |format| respond_to do |format|
format.html format.html
format.js {render :layout => false} format.js {render :layout => false}
end end
end rescue ChangesetNotFound
show_error_not_found
# Adds a related issue to a changeset
# POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
def add_related_issue
@issue = @changeset.find_referenced_issue_by_id(params[:issue_id])
if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
@issue = nil
end
if @issue
@changeset.issues << @issue
end
end
# Removes a related issue from a changeset
# DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
def remove_related_issue
@issue = Issue.visible.find_by_id(params[:issue_id])
if @issue
@changeset.issues.delete(@issue)
end
end end
def diff def diff
@@ -300,25 +241,15 @@ class RepositoriesController < ApplicationController
private private
def find_repository
@repository = Repository.find(params[:id])
@project = @repository.project
rescue ActiveRecord::RecordNotFound
render_404
end
REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
def find_project_repository def find_repository
@project = Project.find(params[:id]) @project = Project.find(params[:id])
if params[:repository_id].present? @repository = @project.repository
@repository = @project.repositories.find_by_identifier_param(params[:repository_id])
else
@repository = @project.repository
end
(render_404; return false) unless @repository (render_404; return false) unless @repository
@path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s @path = params[:path].join('/') unless params[:path].nil?
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip @path ||= ''
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
@rev_to = params[:rev_to] @rev_to = params[:rev_to]
unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE) unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE)
@@ -332,13 +263,6 @@ class RepositoriesController < ApplicationController
show_error_not_found show_error_not_found
end end
def find_changeset
if @rev.present?
@changeset = @repository.find_changeset_by_name(@rev)
end
show_error_not_found unless @changeset
end
def show_error_not_found def show_error_not_found
render_error :message => l(:error_scm_not_found), :status => 404 render_error :message => l(:error_scm_not_found), :status => 404
end end
@@ -352,20 +276,17 @@ class RepositoriesController < ApplicationController
@date_to = Date.today @date_to = Date.today
@date_from = @date_to << 11 @date_from = @date_to << 11
@date_from = Date.civil(@date_from.year, @date_from.month, 1) @date_from = Date.civil(@date_from.year, @date_from.month, 1)
commits_by_day = Changeset. commits_by_day = repository.changesets.count(
where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to). :all, :group => :commit_date,
group(:commit_date). :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
count
commits_by_month = [0] * 12 commits_by_month = [0] * 12
commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
changes_by_day = Change. changes_by_day = repository.changes.count(
joins(:changeset). :all, :group => :commit_date,
where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to). :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
group(:commit_date).
count
changes_by_month = [0] * 12 changes_by_month = [0] * 12
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
fields = [] fields = []
12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)} 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
@@ -396,10 +317,10 @@ class RepositoriesController < ApplicationController
end end
def graph_commits_per_author(repository) def graph_commits_per_author(repository)
commits_by_author = Changeset.where("repository_id = ?", repository.id).group(:committer).count commits_by_author = repository.changesets.count(:all, :group => :committer)
commits_by_author.to_a.sort! {|x, y| x.last <=> y.last} commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
changes_by_author = Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", repository.id).group(:committer).count changes_by_author = repository.changes.count(:all, :group => :committer)
h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o} h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
fields = commits_by_author.collect {|r| r.first} fields = commits_by_author.collect {|r| r.first}
@@ -414,7 +335,7 @@ class RepositoriesController < ApplicationController
fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
graph = SVG::Graph::BarHorizontal.new( graph = SVG::Graph::BarHorizontal.new(
:height => 30 * commits_data.length, :height => 400,
:width => 800, :width => 800,
:fields => fields, :fields => fields,
:stack => :side, :stack => :side,
@@ -435,3 +356,19 @@ class RepositoriesController < ApplicationController
graph.burn graph.burn
end end
end end
class Date
def months_ago(date = Date.today)
(date.year - self.year)*12 + (date.month - self.month)
end
def weeks_ago(date = Date.today)
(date.year - self.year)*52 + (date.cweek - self.cweek)
end
end
class String
def with_leading_slash
starts_with?('/') ? self : "/#{self}"
end
end
+23 -53
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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
@@ -18,75 +18,53 @@
class RolesController < ApplicationController class RolesController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin, :except => [:index, :show] before_filter :require_admin
before_filter :require_admin_or_api_request, :only => [:index, :show]
before_filter :find_role, :only => [:show, :edit, :update, :destroy] verify :method => :post, :only => [ :destroy, :move ],
accept_api_auth :index, :show :redirect_to => { :action => :index }
def index def index
respond_to do |format| @role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
format.html { render :action => "index", :layout => false if request.xhr?
@role_pages, @roles = paginate Role.sorted, :per_page => 25
render :action => "index", :layout => false if request.xhr?
}
format.api {
@roles = Role.givable.all
}
end
end
def show
respond_to do |format|
format.api
end
end end
def new def new
# Prefills the form with 'Non member' role permissions by default # Prefills the form with 'Non member' role permissions
@role = Role.new(params[:role] || {:permissions => Role.non_member.permissions}) @role = Role.new(params[:role] || {:permissions => Role.non_member.permissions})
if params[:copy].present? && @copy_from = Role.find_by_id(params[:copy])
@role.copy_from(@copy_from)
end
@roles = Role.sorted.all
end
def create
@role = Role.new(params[:role])
if request.post? && @role.save if request.post? && @role.save
# workflow copy # workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from])) if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from]))
@role.workflow_rules.copy(copy_from) @role.workflows.copy(copy_from)
end end
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to roles_path redirect_to :action => 'index'
else else
@roles = Role.sorted.all @permissions = @role.setable_permissions
render :action => 'new' @roles = Role.find :all, :order => 'builtin, position'
end end
end end
def edit def edit
end @role = Role.find(params[:id])
if request.post? and @role.update_attributes(params[:role])
def update
if request.put? and @role.update_attributes(params[:role])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to roles_path redirect_to :action => 'index'
else else
render :action => 'edit' @permissions = @role.setable_permissions
end end
end end
def destroy def destroy
@role = Role.find(params[:id])
@role.destroy @role.destroy
redirect_to roles_path redirect_to :action => 'index'
rescue rescue
flash[:error] = l(:error_can_not_remove_role) flash[:error] = l(:error_can_not_remove_role)
redirect_to roles_path redirect_to :action => 'index'
end end
def permissions def report
@roles = Role.sorted.all @roles = Role.find(:all, :order => 'builtin, position')
@permissions = Redmine::AccessControl.permissions.select { |p| !p.public? } @permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
if request.post? if request.post?
@roles.each do |role| @roles.each do |role|
@@ -94,15 +72,7 @@ class RolesController < ApplicationController
role.save role.save
end end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to roles_path redirect_to :action => 'index'
end end
end end
private
def find_role
@role = Role.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end end
+7 -4
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,6 +18,9 @@
class SearchController < ApplicationController class SearchController < ApplicationController
before_filter :find_optional_project before_filter :find_optional_project
helper :messages
include MessagesHelper
def index def index
@question = params[:q] || "" @question = params[:q] || ""
@question.strip! @question.strip!
@@ -31,7 +34,7 @@ class SearchController < ApplicationController
when 'my_projects' when 'my_projects'
User.current.memberships.collect(&:project) User.current.memberships.collect(&:project)
when 'subprojects' when 'subprojects'
@project ? (@project.self_and_descendants.active.all) : nil @project ? (@project.self_and_descendants.active) : nil
else else
@project @project
end end
@@ -40,8 +43,8 @@ class SearchController < ApplicationController
begin; offset = params[:offset].to_time if params[:offset]; rescue; end begin; offset = params[:offset].to_time if params[:offset]; rescue; end
# quick jump to an issue # quick jump to an issue
if (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i)) if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1.to_i)
redirect_to issue_path(issue) redirect_to :controller => "issues", :action => "show", :id => $1
return return
end end
+9 -19
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,9 +17,6 @@
class SettingsController < ApplicationController class SettingsController < ApplicationController
layout 'admin' layout 'admin'
menu_item :plugins, :only => :plugin
helper :queries
before_filter :require_admin before_filter :require_admin
@@ -33,40 +30,33 @@ class SettingsController < ApplicationController
if request.post? && params[:settings] && params[:settings].is_a?(Hash) if request.post? && params[:settings] && params[:settings].is_a?(Hash)
settings = (params[:settings] || {}).dup.symbolize_keys settings = (params[:settings] || {}).dup.symbolize_keys
settings.each do |name, value| settings.each do |name, value|
Setting.set_from_params name, value # remove blank values in array settings
value.delete_if {|v| v.blank? } if value.is_a?(Array)
Setting[name] = value
end end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to settings_path(:tab => params[:tab]) redirect_to :action => 'edit', :tab => params[:tab]
else else
@options = {} @options = {}
user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]} @options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] }
@options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]}
@deliveries = ActionMailer::Base.perform_deliveries @deliveries = ActionMailer::Base.perform_deliveries
@guessed_host_and_path = request.host_with_port.dup @guessed_host_and_path = request.host_with_port.dup
@guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank? @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
@commit_update_keywords = Setting.commit_update_keywords.dup
@commit_update_keywords = [{}] unless @commit_update_keywords.is_a?(Array) && @commit_update_keywords.any?
Redmine::Themes.rescan Redmine::Themes.rescan
end end
end end
def plugin def plugin
@plugin = Redmine::Plugin.find(params[:id]) @plugin = Redmine::Plugin.find(params[:id])
unless @plugin.configurable?
render_404
return
end
if request.post? if request.post?
Setting.send "plugin_#{@plugin.id}=", params[:settings] Setting["plugin_#{@plugin.id}"] = params[:settings]
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to plugin_settings_path(@plugin) redirect_to :action => 'plugin', :id => @plugin.id
else else
@partial = @plugin.settings[:partial] @partial = @plugin.settings[:partial]
@settings = Setting.send "plugin_#{@plugin.id}" @settings = Setting["plugin_#{@plugin.id}"]
end end
rescue Redmine::PluginNotFound rescue Redmine::PluginNotFound
render_404 render_404
+10 -22
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 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
@@ -19,12 +19,9 @@ class SysController < ActionController::Base
before_filter :check_enabled before_filter :check_enabled
def projects def projects
p = Project.active.has_module(:repository).order("#{Project.table_name}.identifier").preload(:repository).all p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
# extra_info attribute from repository breaks activeresource client # extra_info attribute from repository breaks activeresource client
render :xml => p.to_xml( render :xml => p.to_xml(:only => [:id, :identifier, :name, :is_public, :status], :include => {:repository => {:only => [:id, :url]}})
:only => [:id, :identifier, :name, :is_public, :status],
:include => {:repository => {:only => [:id, :url]}}
)
end end
def create_project_repository def create_project_repository
@@ -33,10 +30,9 @@ class SysController < ActionController::Base
render :nothing => true, :status => 409 render :nothing => true, :status => 409
else else
logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}." logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
repository = Repository.factory(params[:vendor], params[:repository]) project.repository = Repository.factory(params[:vendor], params[:repository])
repository.project = project if project.repository && project.repository.save
if repository.save render :xml => project.repository.to_xml(:only => [:id, :url]), :status => 201
render :xml => {repository.class.name.underscore.gsub('/', '-') => {:id => repository.id, :url => repository.url}}, :status => 201
else else
render :nothing => true, :status => 422 render :nothing => true, :status => 422
end end
@@ -45,22 +41,14 @@ class SysController < ActionController::Base
def fetch_changesets def fetch_changesets
projects = [] projects = []
scope = Project.active.has_module(:repository)
if params[:id] if params[:id]
project = nil projects << Project.active.has_module(:repository).find(params[:id])
if params[:id].to_s =~ /^\d*$/
project = scope.find(params[:id])
else
project = scope.find_by_identifier(params[:id])
end
raise ActiveRecord::RecordNotFound unless project
projects << project
else else
projects = scope.all projects = Project.active.has_module(:repository).find(:all, :include => :repository)
end end
projects.each do |project| projects.each do |project|
project.repositories.each do |repository| if project.repository
repository.fetch_changesets project.repository.fetch_changesets
end end
end end
render :nothing => true, :status => 200 render :nothing => true, :status => 200
@@ -0,0 +1,209 @@
class TimeEntryReportsController < ApplicationController
menu_item :issues
before_filter :find_optional_project
before_filter :load_available_criterias
helper :sort
include SortHelper
helper :issues
helper :timelog
include TimelogHelper
helper :custom_fields
include CustomFieldsHelper
def report
@criterias = params[:criterias] || []
@criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
@criterias.uniq!
@criterias = @criterias[0,3]
@columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month'
retrieve_date_range
unless @criterias.empty?
sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
sql_condition = ''
if @project.nil?
sql_condition = Project.allowed_to_condition(User.current, :view_time_entries)
elsif @issue.nil?
sql_condition = @project.project_condition(Setting.display_subprojects_issues?)
else
sql_condition = "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
end
sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
sql << " FROM #{TimeEntry.table_name}"
sql << time_report_joins
sql << " WHERE"
sql << " (%s) AND" % sql_condition
sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)]
sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
@hours = ActiveRecord::Base.connection.select_all(sql)
@hours.each do |row|
case @columns
when 'year'
row['year'] = row['tyear']
when 'month'
row['month'] = "#{row['tyear']}-#{row['tmonth']}"
when 'week'
row['week'] = "#{row['tyear']}-#{row['tweek']}"
when 'day'
row['day'] = "#{row['spent_on']}"
end
end
@total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
@periods = []
# Date#at_beginning_of_ not supported in Rails 1.2.x
date_from = @from.to_time
# 100 columns max
while date_from <= @to.to_time && @periods.length < 100
case @columns
when 'year'
@periods << "#{date_from.year}"
date_from = (date_from + 1.year).at_beginning_of_year
when 'month'
@periods << "#{date_from.year}-#{date_from.month}"
date_from = (date_from + 1.month).at_beginning_of_month
when 'week'
@periods << "#{date_from.year}-#{date_from.to_date.cweek}"
date_from = (date_from + 7.day).at_beginning_of_week
when 'day'
@periods << "#{date_from.to_date}"
date_from = date_from + 1.day
end
end
end
respond_to do |format|
format.html { render :layout => !request.xhr? }
format.csv { send_data(report_to_csv(@criterias, @periods, @hours), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
end
end
private
# TODO: duplicated in TimelogController
def find_optional_project
if !params[:issue_id].blank?
@issue = Issue.find(params[:issue_id])
@project = @issue.project
elsif !params[:project_id].blank?
@project = Project.find(params[:project_id])
end
deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
end
# Retrieves the date range based on predefined ranges or specific from/to param dates
# TODO: duplicated in TimelogController
def retrieve_date_range
@free_period = false
@from, @to = nil, nil
if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
case params[:period].to_s
when 'today'
@from = @to = Date.today
when 'yesterday'
@from = @to = Date.today - 1
when 'current_week'
@from = Date.today - (Date.today.cwday - 1)%7
@to = @from + 6
when 'last_week'
@from = Date.today - 7 - (Date.today.cwday - 1)%7
@to = @from + 6
when '7_days'
@from = Date.today - 7
@to = Date.today
when 'current_month'
@from = Date.civil(Date.today.year, Date.today.month, 1)
@to = (@from >> 1) - 1
when 'last_month'
@from = Date.civil(Date.today.year, Date.today.month, 1) << 1
@to = (@from >> 1) - 1
when '30_days'
@from = Date.today - 30
@to = Date.today
when 'current_year'
@from = Date.civil(Date.today.year, 1, 1)
@to = Date.civil(Date.today.year, 12, 31)
end
elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
@free_period = true
else
# default
end
@from, @to = @to, @from if @from && @to && @from > @to
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
end
def load_available_criterias
@available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
:klass => Project,
:label => :label_project},
'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
:klass => Version,
:label => :label_version},
'category' => {:sql => "#{Issue.table_name}.category_id",
:klass => IssueCategory,
:label => :field_category},
'member' => {:sql => "#{TimeEntry.table_name}.user_id",
:klass => User,
:label => :label_member},
'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
:klass => Tracker,
:label => :label_tracker},
'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
:klass => TimeEntryActivity,
:label => :label_activity},
'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
:klass => Issue,
:label => :label_issue}
}
# Add list and boolean custom fields as available criterias
custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields)
custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)",
:format => cf.field_format,
:label => cf.name}
end if @project
# Add list and boolean time entry custom fields
TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)",
:format => cf.field_format,
:label => cf.name}
end
# Add list and boolean time entry activity custom fields
TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id)",
:format => cf.field_format,
:label => cf.name}
end
call_hook(:controller_timelog_available_criterias, { :available_criterias => @available_criterias, :project => @project })
@available_criterias
end
def time_report_joins
sql = ''
sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
# TODO: rename hook
call_hook(:controller_timelog_time_report_joins, {:sql => sql} )
sql
end
end
+153 -126
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,77 +17,84 @@
class TimelogController < ApplicationController class TimelogController < ApplicationController
menu_item :issues menu_item :issues
before_filter :find_project, :only => [:new, :create]
before_filter :find_project_for_new_time_entry, :only => [:create]
before_filter :find_time_entry, :only => [:show, :edit, :update] before_filter :find_time_entry, :only => [:show, :edit, :update]
before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy] before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
before_filter :authorize, :except => [:new, :index, :report] before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => [:index]
before_filter :find_optional_project, :only => [:index, :report]
before_filter :find_optional_project_for_new_time_entry, :only => [:new]
before_filter :authorize_global, :only => [:new, :index, :report]
accept_rss_auth :index accept_rss_auth :index
accept_api_auth :index, :show, :create, :update, :destroy accept_api_auth :index, :show, :create, :update, :destroy
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
helper :sort helper :sort
include SortHelper include SortHelper
helper :issues helper :issues
include TimelogHelper include TimelogHelper
helper :custom_fields helper :custom_fields
include CustomFieldsHelper include CustomFieldsHelper
helper :queries
include QueriesHelper
def index def index
@query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_') sort_init 'spent_on', 'desc'
sort_update 'spent_on' => 'spent_on',
'user' => 'user_id',
'activity' => 'activity_id',
'project' => "#{Project.table_name}.name",
'issue' => 'issue_id',
'hours' => 'hours'
sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria) cond = ARCondition.new
sort_update(@query.sortable_columns) if @issue
scope = time_entry_scope(:order => sort_clause). cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
includes(:project, :activity, :user, :issue). elsif @project
preload(:issue => [:project, :tracker, :status, :assigned_to, :priority]) cond << @project.project_condition(Setting.display_subprojects_issues?)
end
retrieve_date_range
cond << ['spent_on BETWEEN ? AND ?', @from, @to]
respond_to do |format| respond_to do |format|
format.html { format.html {
@entry_count = scope.count # Paginate results
@entry_pages = Paginator.new @entry_count, per_page_option, params['page'] @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
@entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).all @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
@total_hours = scope.sum(:hours).to_f @entries = TimeEntry.visible.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions,
:order => sort_clause,
:limit => @entry_pages.items_per_page,
:offset => @entry_pages.current.offset)
@total_hours = TimeEntry.visible.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f
render :layout => !request.xhr? render :layout => !request.xhr?
} }
format.api { format.api {
@entry_count = scope.count @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
@offset, @limit = api_offset_and_limit @offset, @limit = api_offset_and_limit
@entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).all @entries = TimeEntry.visible.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions,
:order => sort_clause,
:limit => @limit,
:offset => @offset)
} }
format.atom { format.atom {
entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").all entries = TimeEntry.visible.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions,
:order => "#{TimeEntry.table_name}.created_on DESC",
:limit => Setting.feeds_limit.to_i)
render_feed(entries, :title => l(:label_spent_time)) render_feed(entries, :title => l(:label_spent_time))
} }
format.csv { format.csv {
# Export all entries # Export all entries
@entries = scope.all @entries = TimeEntry.visible.find(:all,
send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv') :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:conditions => cond.conditions,
:order => sort_clause)
send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
} }
end end
end end
def report
@query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
scope = time_entry_scope
@report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
respond_to do |format|
format.html { render :layout => !request.xhr? }
format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
end
end
def show def show
respond_to do |format| respond_to do |format|
# TODO: Implement html response # TODO: Implement html response
@@ -98,57 +105,16 @@ class TimelogController < ApplicationController
def new def new
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.safe_attributes = params[:time_entry] @time_entry.attributes = params[:time_entry]
end
def create
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.safe_attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
render :action => 'edit'
if @time_entry.save
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_create)
if params[:continue]
if params[:project_id]
options = {
:time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url]
}
if @time_entry.issue
redirect_to new_project_issue_time_entry_path(@time_entry.project, @time_entry.issue, options)
else
redirect_to new_project_time_entry_path(@time_entry.project, options)
end
else
options = {
:time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url]
}
redirect_to new_time_entry_path(options)
end
else
redirect_back_or_default project_time_entries_path(@time_entry.project)
end
}
format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
end
else
respond_to do |format|
format.html { render :action => 'new' }
format.api { render_validation_errors(@time_entry) }
end
end
end end
def edit verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
@time_entry.safe_attributes = params[:time_entry] def create
end @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.attributes = params[:time_entry]
def update
@time_entry.safe_attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
@@ -156,9 +122,37 @@ class TimelogController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_back_or_default project_time_entries_path(@time_entry.project) redirect_back_or_default :action => 'index', :project_id => @time_entry.project
} }
format.api { render_api_ok } format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
end
else
respond_to do |format|
format.html { render :action => 'edit' }
format.api { render_validation_errors(@time_entry) }
end
end
end
def edit
@time_entry.attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update
@time_entry.attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
if @time_entry.save
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_back_or_default :action => 'index', :project_id => @time_entry.project
}
format.api { head :ok }
end end
else else
respond_to do |format| respond_to do |format|
@@ -179,43 +173,43 @@ class TimelogController < ApplicationController
unsaved_time_entry_ids = [] unsaved_time_entry_ids = []
@time_entries.each do |time_entry| @time_entries.each do |time_entry|
time_entry.reload time_entry.reload
time_entry.safe_attributes = attributes time_entry.attributes = attributes
call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry }) call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
unless time_entry.save unless time_entry.save
logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info
# Keep unsaved time_entry ids to display them in flash error # Keep unsaved time_entry ids to display them in flash error
unsaved_time_entry_ids << time_entry.id unsaved_time_entry_ids << time_entry.id
end end
end end
set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids) set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
redirect_back_or_default project_time_entries_path(@projects.first) redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
destroyed = TimeEntry.transaction do @time_entries.each do |t|
@time_entries.each do |t| begin
unless t.destroy && t.destroyed? unless t.destroy && t.destroyed?
raise ActiveRecord::Rollback respond_to do |format|
format.html {
flash[:error] = l(:notice_unable_delete_time_entry)
redirect_to :back
}
format.api { render_validation_errors(t) }
end
return
end end
rescue ::ActionController::RedirectBackError
redirect_to :action => 'index', :project_id => @projects.first
return
end end
end end
respond_to do |format| respond_to do |format|
format.html { format.html {
if destroyed flash[:notice] = l(:notice_successful_delete)
flash[:notice] = l(:notice_successful_delete) redirect_back_or_default(:action => 'index', :project_id => @projects.first)
else
flash[:error] = l(:notice_unable_delete_time_entry)
end
redirect_back_or_default project_time_entries_path(@projects.first)
}
format.api {
if destroyed
render_api_ok
else
render_validation_errors(@time_entries)
end
} }
format.api { head :ok }
end end
end end
@@ -251,25 +245,20 @@ private
end end
end end
def find_optional_project_for_new_time_entry def find_project
if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
@project = Project.find(project_id)
end
if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present? if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
@issue = Issue.find(issue_id) @issue = Issue.find(issue_id)
@project ||= @issue.project @project = @issue.project
elsif (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
@project = Project.find(project_id)
else
render_404
return false
end end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def find_project_for_new_time_entry
find_optional_project_for_new_time_entry
if @project.nil?
render_404
end
end
def find_optional_project def find_optional_project
if !params[:issue_id].blank? if !params[:issue_id].blank?
@issue = Issue.find(params[:issue_id]) @issue = Issue.find(params[:issue_id])
@@ -277,15 +266,53 @@ private
elsif !params[:project_id].blank? elsif !params[:project_id].blank?
@project = Project.find(params[:project_id]) @project = Project.find(params[:project_id])
end end
deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
end end
# Returns the TimeEntry scope for index and report actions # Retrieves the date range based on predefined ranges or specific from/to param dates
def time_entry_scope(options={}) def retrieve_date_range
scope = @query.results_scope(options) @free_period = false
if @issue @from, @to = nil, nil
scope = scope.on_issue(@issue)
if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
case params[:period].to_s
when 'today'
@from = @to = Date.today
when 'yesterday'
@from = @to = Date.today - 1
when 'current_week'
@from = Date.today - (Date.today.cwday - 1)%7
@to = @from + 6
when 'last_week'
@from = Date.today - 7 - (Date.today.cwday - 1)%7
@to = @from + 6
when '7_days'
@from = Date.today - 7
@to = Date.today
when 'current_month'
@from = Date.civil(Date.today.year, Date.today.month, 1)
@to = (@from >> 1) - 1
when 'last_month'
@from = Date.civil(Date.today.year, Date.today.month, 1) << 1
@to = (@from >> 1) - 1
when '30_days'
@from = Date.today - 30
@to = Date.today
when 'current_year'
@from = Date.civil(Date.today.year, 1, 1)
@to = Date.civil(Date.today.year, 12, 31)
end
elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
@free_period = true
else
# default
end end
scope
@from, @to = @to, @from if @from && @to && @from > @to
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
end end
def parse_params_for_bulk_time_entry_attributes(params) def parse_params_for_bulk_time_entry_attributes(params)
+15 -52
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 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
@@ -18,57 +18,38 @@
class TrackersController < ApplicationController class TrackersController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin, :except => :index before_filter :require_admin
before_filter :require_admin_or_api_request, :only => :index
accept_api_auth :index verify :method => :post, :only => :destroy, :redirect_to => { :action => :index }
def index def index
respond_to do |format| @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
format.html { render :action => "index", :layout => false if request.xhr?
@tracker_pages, @trackers = paginate Tracker.sorted, :per_page => 25
render :action => "index", :layout => false if request.xhr?
}
format.api {
@trackers = Tracker.sorted.all
}
end
end end
def new def new
@tracker ||= Tracker.new(params[:tracker])
@trackers = Tracker.sorted.all
@projects = Project.all
end
def create
@tracker = Tracker.new(params[:tracker]) @tracker = Tracker.new(params[:tracker])
if @tracker.save if request.post? and @tracker.save
# workflow copy # workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from])) if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
@tracker.workflow_rules.copy(copy_from) @tracker.workflows.copy(copy_from)
end end
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to trackers_path redirect_to :action => 'index'
return return
end end
new @trackers = Tracker.find :all, :order => 'position'
render :action => 'new' @projects = Project.find(:all)
end end
def edit def edit
@tracker ||= Tracker.find(params[:id])
@projects = Project.all
end
def update
@tracker = Tracker.find(params[:id]) @tracker = Tracker.find(params[:id])
if @tracker.update_attributes(params[:tracker]) if request.post? and @tracker.update_attributes(params[:tracker])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to trackers_path redirect_to :action => 'index'
return return
end end
edit @projects = Project.find(:all)
render :action => 'edit'
end end
def destroy def destroy
@@ -78,24 +59,6 @@ class TrackersController < ApplicationController
else else
@tracker.destroy @tracker.destroy
end end
redirect_to trackers_path redirect_to :action => 'index'
end
def fields
if request.post? && params[:trackers]
params[:trackers].each do |tracker_id, tracker_params|
tracker = Tracker.find_by_id(tracker_id)
if tracker
tracker.core_fields = tracker_params[:core_fields]
tracker.custom_field_ids = tracker_params[:custom_field_ids]
tracker.save
end
end
flash[:notice] = l(:notice_successful_update)
redirect_to fields_trackers_path
return
end
@trackers = Tracker.sorted.all
@custom_fields = IssueCustomField.all.sort
end end
end end
+69 -39
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -38,16 +38,25 @@ class UsersController < ApplicationController
@limit = per_page_option @limit = per_page_option
end end
@status = params[:status] || 1 scope = User
scope = scope.in_group(params[:group_id].to_i) if params[:group_id].present?
scope = User.logged.status(@status) @status = params[:status] ? params[:status].to_i : 1
scope = scope.like(params[:name]) if params[:name].present? c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
scope = scope.in_group(params[:group_id]) if params[:group_id].present?
@user_count = scope.count unless params[:name].blank?
@user_pages = Paginator.new @user_count, @limit, params['page'] name = "%#{params[:name].strip.downcase}%"
@offset ||= @user_pages.offset c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
@users = scope.order(sort_clause).limit(@limit).offset(@offset).all end
@user_count = scope.count(:conditions => c.conditions)
@user_pages = Paginator.new self, @user_count, @limit, params['page']
@offset ||= @user_pages.current.offset
@users = scope.find :all,
:order => sort_clause,
:conditions => c.conditions,
:limit => @limit,
:offset => @offset
respond_to do |format| respond_to do |format|
format.html { format.html {
@@ -60,7 +69,7 @@ class UsersController < ApplicationController
def show def show
# show projects based on current user visibility # show projects based on current user visibility
@memberships = @user.memberships.where(Project.visible_condition(User.current)).all @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current))
events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10) events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
@events_by_day = events.group_by(&:event_date) @events_by_day = events.group_by(&:event_date)
@@ -80,10 +89,10 @@ class UsersController < ApplicationController
def new def new
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
@user.safe_attributes = params[:user] @auth_sources = AuthSource.find(:all)
@auth_sources = AuthSource.all
end end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
@user.safe_attributes = params[:user] @user.safe_attributes = params[:user]
@@ -91,26 +100,28 @@ class UsersController < ApplicationController
@user.login = params[:user][:login] @user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
if @user.save # TODO: Similar to My#account
@user.pref.attributes = params[:pref] @user.pref.attributes = params[:pref]
@user.pref.save @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
Mailer.account_information(@user, @user.password).deliver if params[:send_information] if @user.save
@user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
Mailer.deliver_account_information(@user, params[:user][:password]) if params[:send_information]
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user))) flash[:notice] = l(:notice_successful_create)
if params[:continue] redirect_to(params[:continue] ?
attrs = params[:user].slice(:generate_password) {:controller => 'users', :action => 'new'} :
redirect_to new_user_path(:user => attrs) {:controller => 'users', :action => 'edit', :id => @user}
else )
redirect_to edit_user_path(@user)
end
} }
format.api { render :action => 'show', :status => :created, :location => user_url(@user) } format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
end end
else else
@auth_sources = AuthSource.all @auth_sources = AuthSource.find(:all)
# Clear password input # Clear password input
@user.password = @user.password_confirmation = nil @user.password = @user.password_confirmation = nil
@@ -122,10 +133,11 @@ class UsersController < ApplicationController
end end
def edit def edit
@auth_sources = AuthSource.all @auth_sources = AuthSource.find(:all)
@membership ||= Member.new @membership ||= Member.new
end end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update def update
@user.admin = params[:user][:admin] if params[:user][:admin] @user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login] @user.login = params[:user][:login] if params[:user][:login]
@@ -137,25 +149,27 @@ class UsersController < ApplicationController
was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE]) was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
# TODO: Similar to My#account # TODO: Similar to My#account
@user.pref.attributes = params[:pref] @user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
if @user.save if @user.save
@user.pref.save @user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
if was_activated if was_activated
Mailer.account_activated(@user).deliver Mailer.deliver_account_activated(@user)
elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil? elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
Mailer.account_information(@user, @user.password).deliver Mailer.deliver_account_information(@user, params[:user][:password])
end end
respond_to do |format| respond_to do |format|
format.html { format.html {
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to_referer_or edit_user_path(@user) redirect_to :back
} }
format.api { render_api_ok } format.api { head :ok }
end end
else else
@auth_sources = AuthSource.all @auth_sources = AuthSource.find(:all)
@membership ||= Member.new @membership ||= Member.new
# Clear password input # Clear password input
@user.password = @user.password_confirmation = nil @user.password = @user.password_confirmation = nil
@@ -165,33 +179,49 @@ class UsersController < ApplicationController
format.api { render_validation_errors(@user) } format.api { render_validation_errors(@user) }
end end
end end
rescue ::ActionController::RedirectBackError
redirect_to :controller => 'users', :action => 'edit', :id => @user
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
@user.destroy @user.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default(users_path) } format.html { redirect_to(users_url) }
format.api { render_api_ok } format.api { head :ok }
end end
end end
def edit_membership def edit_membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user) @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save @membership.save if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } if @membership.valid?
format.js format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
format.js {
render(:update) {|page|
page.replace_html "tab-content-memberships", :partial => 'users/memberships'
page.visual_effect(:highlight, "member-#{@membership.id}")
}
}
else
format.js {
render(:update) {|page|
page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
}
}
end
end end
end end
def destroy_membership def destroy_membership
@membership = Member.find(params[:membership_id]) @membership = Member.find(params[:membership_id])
if @membership.deletable? if request.post? && @membership.deletable?
@membership.destroy @membership.destroy
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
format.js format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
end end
end end
+54 -77
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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,75 +20,58 @@ class VersionsController < ApplicationController
model_object Version model_object Version
before_filter :find_model_object, :except => [:index, :new, :create, :close_completed] before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed] before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
before_filter :find_project_by_project_id, :only => [:index, :new, :create, :close_completed] before_filter :find_project, :only => [:index, :new, :create, :close_completed]
before_filter :authorize before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
helper :custom_fields helper :custom_fields
helper :projects helper :projects
def index def index
respond_to do |format| @trackers = @project.trackers.find(:all, :order => 'position')
format.html { retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
@trackers = @project.trackers.sorted.all @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?}) project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
@versions = @project.shared_versions || [] @versions = @project.shared_versions || []
@versions += @project.rolled_up_versions.visible if @with_subprojects @versions += @project.rolled_up_versions.visible if @with_subprojects
@versions = @versions.uniq.sort @versions = @versions.uniq.sort
unless params[:completed] @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
@completed_versions = @versions.select {|version| version.closed? || version.completed? }
@versions -= @completed_versions
end
@issues_by_version = {} @issues_by_version = {}
if @selected_tracker_ids.any? && @versions.any? unless @selected_tracker_ids.empty?
issues = Issue.visible. @versions.each do |version|
includes(:project, :tracker). issues = version.fixed_issues.visible.find(:all,
preload(:status, :priority, :fixed_version). :include => [:project, :status, :tracker, :priority],
where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)). :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids},
order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id") :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
@issues_by_version = issues.group_by(&:fixed_version) @issues_by_version[version] = issues
end end
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
}
format.api {
@versions = @project.shared_versions.all
}
end end
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
end end
def show def show
respond_to do |format| @issues = @version.fixed_issues.visible.find(:all,
format.html { :include => [:status, :tracker, :priority],
@issues = @version.fixed_issues.visible. :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
includes(:status, :tracker, :priority).
reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
all
}
format.api
end
end end
def new def new
@version = @project.versions.build @version = @project.versions.build
@version.safe_attributes = params[:version] if params[:version]
attributes = params[:version].dup
respond_to do |format| attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
format.html @version.attributes = attributes
format.js
end end
end end
def create def create
# TODO: refactor with code above in #new
@version = @project.versions.build @version = @project.versions.build
if params[:version] if params[:version]
attributes = params[:version].dup attributes = params[:version].dup
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing']) attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
@version.safe_attributes = attributes @version.attributes = attributes
end end
if request.post? if request.post?
@@ -96,18 +79,21 @@ class VersionsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_back_or_default settings_project_path(@project, :tab => 'versions') redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end end
format.js format.js do
format.api do # IE doesn't support the replace_html rjs method for select box options
render :action => 'show', :status => :created, :location => version_url(@version) render(:update) {|page| page.replace "issue_fixed_version_id",
content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
}
end end
end end
else else
respond_to do |format| respond_to do |format|
format.html { render :action => 'new' } format.html { render :action => 'new' }
format.js { render :action => 'new' } format.js do
format.api { render_validation_errors(@version) } render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
end
end end
end end
end end
@@ -120,19 +106,12 @@ class VersionsController < ApplicationController
if request.put? && params[:version] if request.put? && params[:version]
attributes = params[:version].dup attributes = params[:version].dup
attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing']) attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
@version.safe_attributes = attributes if @version.update_attributes(attributes)
if @version.save flash[:notice] = l(:notice_successful_update)
respond_to do |format| redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_back_or_default settings_project_path(@project, :tab => 'versions')
}
format.api { render_api_ok }
end
else else
respond_to do |format| respond_to do |format|
format.html { render :action => 'edit' } format.html { render :action => 'edit' }
format.api { render_validation_errors(@version) }
end end
end end
end end
@@ -142,35 +121,32 @@ class VersionsController < ApplicationController
if request.put? if request.put?
@project.close_completed_versions @project.close_completed_versions
end end
redirect_to settings_project_path(@project, :tab => 'versions') redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end end
def destroy def destroy
if @version.fixed_issues.empty? if @version.fixed_issues.empty?
@version.destroy @version.destroy
respond_to do |format| redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
format.html { redirect_back_or_default settings_project_path(@project, :tab => 'versions') }
format.api { render_api_ok }
end
else else
respond_to do |format| flash[:error] = l(:notice_unable_delete_version)
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
flash[:error] = l(:notice_unable_delete_version)
redirect_to settings_project_path(@project, :tab => 'versions')
}
format.api { head :unprocessable_entity }
end
end end
end end
def status_by def status_by
respond_to do |format| respond_to do |format|
format.html { render :action => 'show' } format.html { render :action => 'show' }
format.js format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
end end
end end
private private
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil) def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
if ids = params[:tracker_ids] if ids = params[:tracker_ids]
@@ -179,4 +155,5 @@ class VersionsController < ApplicationController
@selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s } @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
end end
end end
end end
+49 -65
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,94 +16,78 @@
# 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 WatchersController < ApplicationController class WatchersController < ApplicationController
before_filter :require_login, :find_watchables, :only => [:watch, :unwatch] before_filter :find_project
before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
before_filter :authorize, :only => [:new, :destroy]
verify :method => :post,
:only => [ :watch, :unwatch ],
:render => { :nothing => true, :status => :method_not_allowed }
def watch def watch
set_watcher(@watchables, User.current, true) if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
render_403
else
set_watcher(User.current, true)
end
end end
def unwatch def unwatch
set_watcher(@watchables, User.current, false) set_watcher(User.current, false)
end end
before_filter :find_project, :authorize, :only => [:new, :create, :append, :destroy, :autocomplete_for_user]
accept_api_auth :create, :destroy
def new def new
end @watcher = Watcher.new(params[:watcher])
@watcher.watchable = @watched
def create @watcher.save if request.post?
user_ids = []
if params[:watcher].is_a?(Hash)
user_ids << (params[:watcher][:user_ids] || params[:watcher][:user_id])
else
user_ids << params[:user_id]
end
user_ids.flatten.compact.uniq.each do |user_id|
Watcher.create(:watchable => @watched, :user_id => user_id)
end
respond_to do |format| respond_to do |format|
format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}} format.html { redirect_to :back }
format.js format.js do
format.api { render_api_ok } render :update do |page|
end page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
end end
end
def append
if params[:watcher].is_a?(Hash)
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
@users = User.active.find_all_by_id(user_ids)
end end
rescue ::ActionController::RedirectBackError
render :text => 'Watcher added.', :layout => true
end end
def destroy def destroy
@watched.set_watcher(User.find(params[:user_id]), false) @watched.set_watcher(User.find(params[:user_id]), false) if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
format.js format.js do
format.api { render_api_ok } render :update do |page|
page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
end
end
end end
end end
def autocomplete_for_user private
@users = User.active.sorted.like(params[:q]).limit(100).all
if @watched
@users -= @watched.watcher_users
end
render :layout => false
end
private
def find_project def find_project
if params[:object_type] && params[:object_id] klass = Object.const_get(params[:object_type].camelcase)
klass = Object.const_get(params[:object_type].camelcase) return false unless klass.respond_to?('watched_by')
return false unless klass.respond_to?('watched_by') @watched = klass.find(params[:object_id])
@watched = klass.find(params[:object_id]) @project = @watched.project
@project = @watched.project
elsif params[:project_id]
@project = Project.visible.find_by_param(params[:project_id])
end
rescue rescue
render_404 render_404
end end
def find_watchables def set_watcher(user, watching)
klass = Object.const_get(params[:object_type].camelcase) rescue nil @watched.set_watcher(user, watching)
if klass && klass.respond_to?('watched_by')
@watchables = klass.find_all_by_id(Array.wrap(params[:object_id]))
raise Unauthorized if @watchables.any? {|w| w.respond_to?(:visible?) && !w.visible?}
end
render_404 unless @watchables.present?
end
def set_watcher(watchables, user, watching)
watchables.each do |watchable|
watchable.set_watcher(user, watching)
end
respond_to do |format| respond_to do |format|
format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}} format.html { redirect_to :back }
format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} } format.js do
render(:update) do |page|
c = watcher_css(@watched)
page.select(".#{c}").each do |item|
page.replace_html item, watcher_link(@watched, user)
end
end
end
end end
rescue ::ActionController::RedirectBackError
render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
end end
end end
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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
+58 -130
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -15,6 +15,8 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'diff'
# The WikiController follows the Rails REST controller pattern but with # The WikiController follows the Rails REST controller pattern but with
# a few differences # a few differences
# #
@@ -33,25 +35,16 @@ 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_or_new_page, :only => [:show, :edit, :update]
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version] before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
accept_api_auth :index, :show, :update, :destroy
before_filter :find_attachments, :only => [:preview]
helper :attachments helper :attachments
include AttachmentsHelper include AttachmentsHelper
helper :watchers helper :watchers
include Redmine::Export::PDF
# List of pages, sorted alphabetically and by parent (hierarchy) # List of pages, sorted alphabetically and by parent (hierarchy)
def index def index
load_pages_for_index load_pages_for_index
@pages_by_parent_id = @pages.group_by(&:parent_id)
respond_to do |format|
format.html {
@pages_by_parent_id = @pages.group_by(&:parent_id)
}
format.api
end
end end
# List of page, by last update # List of page, by last update
@@ -62,13 +55,8 @@ class WikiController < ApplicationController
# display a page (in editing mode if it doesn't exist) # display a page (in editing mode if it doesn't exist)
def show def show
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project) if @page.new_record?
deny_access if User.current.allowed_to?(:edit_wiki_pages, @project) && editable?
return
end
@content = @page.content_for_version(params[:version])
if @content.nil?
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
edit edit
render :action => 'edit' render :action => 'edit'
else else
@@ -76,11 +64,14 @@ class WikiController < ApplicationController
end end
return return
end end
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
# Redirects user to the current version if he's not allowed to view previous versions
redirect_to :version => nil
return
end
@content = @page.content_for_version(params[:version])
if User.current.allowed_to?(:export_wiki_pages, @project) if User.current.allowed_to?(:export_wiki_pages, @project)
if params[:format] == 'pdf' if params[:format] == 'html'
send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
return
elsif params[:format] == 'html'
export = render_to_string :action => 'export', :layout => false export = render_to_string :action => 'export', :layout => false
send_data(export, :type => 'text/html', :filename => "#{@page.title}.html") send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
return return
@@ -90,106 +81,57 @@ class WikiController < ApplicationController
end end
end end
@editable = editable? @editable = editable?
@sections_editable = @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) && render :action => 'show'
@content.current_version? &&
Redmine::WikiFormatting.supports_section_edit?
respond_to do |format|
format.html
format.api
end
end end
# edit an existing page or a new one # edit an existing page or a new one
def edit def edit
return render_403 unless editable? return render_403 unless editable?
if @page.new_record? @page.content = WikiContent.new(:page => @page) if @page.new_record?
if params[:parent].present?
@page.parent = @page.wiki.find_page(params[:parent].to_s)
end
end
@content = @page.content_for_version(params[:version]) @content = @page.content_for_version(params[:version])
@content ||= WikiContent.new(:page => @page)
@content.text = initial_page_content(@page) if @content.text.blank? @content.text = initial_page_content(@page) if @content.text.blank?
# don't keep previous comment # don't keep previous comment
@content.comments = nil @content.comments = nil
# To prevent StaleObjectError exception when reverting to a previous version # To prevent StaleObjectError exception when reverting to a previous version
@content.version = @page.content.version if @page.content @content.version = @page.content.version
@text = @content.text
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@section = params[:section].to_i
@text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section)
render_404 if @text.blank?
end
end end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
# Creates a new page or updates an existing one # Creates a new page or updates an existing one
def update def update
return render_403 unless editable? return render_403 unless editable?
was_new_page = @page.new_record? @page.content = WikiContent.new(:page => @page) if @page.new_record?
@page.safe_attributes = params[:wiki_page]
@content = @page.content || WikiContent.new(:page => @page) @content = @page.content_for_version(params[:version])
content_params = params[:content] @content.text = initial_page_content(@page) if @content.text.blank?
if content_params.nil? && params[:wiki_page].is_a?(Hash) # don't keep previous comment
content_params = params[:wiki_page].slice(:text, :comments, :version) @content.comments = nil
end
content_params ||= {}
@content.comments = content_params[:comments] if !@page.new_record? && params[:content].present? && @content.text == params[:content][:text]
@text = content_params[:text] attachments = Attachment.attach_files(@page, params[:attachments])
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? render_attachment_warning_if_needed(@page)
@section = params[:section].to_i # don't save if text wasn't changed
@section_hash = params[:section_hash] redirect_to :action => 'show', :project_id => @project, :id => @page.title
@content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(@section, @text, @section_hash) return
else
@content.version = content_params[:version] if content_params[:version]
@content.text = @text
end end
@content.attributes = params[:content]
@content.author = User.current @content.author = User.current
# if page is new @page.save will also save content, but not if page isn't a new record
if @page.save_with_content(@content) if (@page.new_record? ? @page.save : @content.save)
attachments = Attachment.attach_files(@page, params[:attachments]) attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page) render_attachment_warning_if_needed(@page)
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
redirect_to :action => 'show', :project_id => @project, :id => @page.title
respond_to do |format|
format.html {
anchor = @section ? "section-#{@section}" : nil
redirect_to project_wiki_page_path(@project, @page.title, :anchor => anchor)
}
format.api {
if was_new_page
render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title)
else
render_api_ok
end
}
end
else else
respond_to do |format| render :action => 'edit'
format.html { render :action => 'edit' }
format.api { render_validation_errors(@content) }
end
end end
rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError rescue ActiveRecord::StaleObjectError
# Optimistic locking exception # Optimistic locking exception
respond_to do |format| flash.now[:error] = l(:notice_locking_conflict)
format.html { render :action => 'edit'
flash.now[:error] = l(:notice_locking_conflict)
render :action => 'edit'
}
format.api { render_api_head :conflict }
end
rescue ActiveRecord::RecordNotSaved
respond_to do |format|
format.html { render :action => 'edit' }
format.api { render_validation_errors(@content) }
end
end end
# rename a page # rename a page
@@ -200,26 +142,26 @@ class WikiController < ApplicationController
@original_title = @page.pretty_title @original_title = @page.pretty_title
if request.post? && @page.update_attributes(params[:wiki_page]) if request.post? && @page.update_attributes(params[:wiki_page])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to project_wiki_page_path(@project, @page.title) redirect_to :action => 'show', :project_id => @project, :id => @page.title
end end
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 project_wiki_page_path(@project, @page.title) redirect_to :action => 'show', :project_id => @project, :id => @page.title
end end
# show page history # show page history
def history def history
@version_count = @page.content.versions.count @version_count = @page.content.versions.count
@version_pages = Paginator.new @version_count, per_page_option, params['page'] @version_pages = Paginator.new self, @version_count, per_page_option, params['p']
# don't load text # don't load text
@versions = @page.content.versions. @versions = @page.content.versions.find :all,
select("id, author_id, comments, updated_on, version"). :select => "id, author_id, comments, updated_on, version",
reorder('version DESC'). :order => 'version DESC',
limit(@version_pages.per_page + 1). :limit => @version_pages.items_per_page + 1,
offset(@version_pages.offset). :offset => @version_pages.current.offset
all
render :layout => false if request.xhr? render :layout => false if request.xhr?
end end
@@ -234,6 +176,7 @@ class WikiController < ApplicationController
render_404 unless @annotate render_404 unless @annotate
end end
verify :method => :delete, :only => [:destroy], :redirect_to => { :action => :show }
# Removes a wiki page and its history # Removes a wiki page and its history
# Children can be either set as root pages, removed or reassigned to another parent page # Children can be either set as root pages, removed or reassigned to another parent page
def destroy def destroy
@@ -256,36 +199,21 @@ class WikiController < ApplicationController
end end
else else
@reassignable_to = @wiki.pages - @page.self_and_descendants @reassignable_to = @wiki.pages - @page.self_and_descendants
# display the destroy form if it's a user request return
return unless api_request?
end end
end end
@page.destroy @page.destroy
respond_to do |format| redirect_to :action => 'index', :project_id => @project
format.html { redirect_to project_wiki_index_path(@project) }
format.api { render_api_ok }
end
end end
def destroy_version # Export wiki to a single html file
return render_403 unless editable?
@content = @page.content_for_version(params[:version])
@content.destroy
redirect_to_referer_or history_project_wiki_page_path(@project, @page.title)
end
# Export wiki to a single pdf or html file
def export def export
@pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}]) if User.current.allowed_to?(:export_wiki_pages, @project)
respond_to do |format| @pages = @wiki.pages.find :all, :order => 'title'
format.html { export = render_to_string :action => 'export_multiple', :layout => false
export = render_to_string :action => 'export_multiple', :layout => false send_data(export, :type => 'text/html', :filename => "wiki.html")
send_data(export, :type => 'text/html', :filename => "wiki.html") else
} redirect_to :action => 'show', :project_id => @project, :id => nil
format.pdf {
send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
}
end end
end end
@@ -294,7 +222,7 @@ class WikiController < ApplicationController
# page is nil when previewing a new page # page is nil when previewing a new page
return render_403 unless page.nil? || editable?(page) return render_403 unless page.nil? || editable?(page)
if page if page
@attachments += page.attachments @attachements = page.attachments
@previewed = page.content @previewed = page.content
end end
@text = params[:content][:text] @text = params[:content][:text]
@@ -351,6 +279,6 @@ private
end end
def load_pages_for_index def load_pages_for_index
@pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all @pages = @wiki.pages.with_updated_on.all(:order => 'title', :include => {:wiki => :project})
end end
end end
+5 -4
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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,15 +22,16 @@ class WikisController < ApplicationController
# Create or update a project's wiki # Create or update a project's wiki
def edit def edit
@wiki = @project.wiki || Wiki.new(:project => @project) @wiki = @project.wiki || Wiki.new(:project => @project)
@wiki.safe_attributes = params[:wiki] @wiki.attributes = params[:wiki]
@wiki.save if request.post? @wiki.save if request.post?
render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
end end
# Delete a project's wiki # Delete a project's wiki
def destroy def destroy
if request.post? && params[:confirm] && @project.wiki if request.post? && params[:confirm] && @project.wiki
@project.wiki.destroy @project.wiki.destroy
redirect_to settings_project_path(@project, :tab => 'wiki') redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
end end
end end
end end
+18 -44
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 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
@@ -18,27 +18,30 @@
class WorkflowsController < ApplicationController class WorkflowsController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin, :find_roles, :find_trackers before_filter :require_admin
before_filter :find_roles
before_filter :find_trackers
def index def index
@workflow_counts = WorkflowTransition.count_by_tracker_and_role @workflow_counts = Workflow.count_by_tracker_and_role
end end
def edit def edit
@role = Role.find_by_id(params[:role_id]) if params[:role_id] @role = Role.find_by_id(params[:role_id])
@tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id] @tracker = Tracker.find_by_id(params[:tracker_id])
if request.post? if request.post?
WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
(params[:issue_status] || []).each { |status_id, transitions| (params[:issue_status] || []).each { |status_id, transitions|
transitions.each { |new_status_id, options| transitions.each { |new_status_id, options|
author = options.is_a?(Array) && options.include?('author') && !options.include?('always') author = options.is_a?(Array) && options.include?('author') && !options.include?('always')
assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always') assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always')
WorkflowTransition.create(:role_id => @role.id, :tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee) @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee)
} }
} }
if @role.save if @role.save
redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker
return return
end end
end end
@@ -47,10 +50,10 @@ class WorkflowsController < ApplicationController
if @tracker && @used_statuses_only && @tracker.issue_statuses.any? if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
@statuses = @tracker.issue_statuses @statuses = @tracker.issue_statuses
end end
@statuses ||= IssueStatus.sorted.all @statuses ||= IssueStatus.find(:all, :order => 'position')
if @tracker && @role && @statuses.any? if @tracker && @role && @statuses.any?
workflows = WorkflowTransition.where(:role_id => @role.id, :tracker_id => @tracker.id).all workflows = Workflow.all(:conditions => {:role_id => @role.id, :tracker_id => @tracker.id})
@workflows = {} @workflows = {}
@workflows['always'] = workflows.select {|w| !w.author && !w.assignee} @workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
@workflows['author'] = workflows.select {|w| w.author} @workflows['author'] = workflows.select {|w| w.author}
@@ -58,35 +61,6 @@ class WorkflowsController < ApplicationController
end end
end end
def permissions
@role = Role.find_by_id(params[:role_id]) if params[:role_id]
@tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id]
if request.post? && @role && @tracker
WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {})
redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
return
end
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
@statuses = @tracker.issue_statuses
end
@statuses ||= IssueStatus.sorted.all
if @role && @tracker
@fields = (Tracker::CORE_FIELDS_ALL - @tracker.disabled_core_fields).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
@custom_fields = @tracker.custom_fields
@permissions = WorkflowPermission.where(:tracker_id => @tracker.id, :role_id => @role.id).all.inject({}) do |h, w|
h[w.old_status_id] ||= {}
h[w.old_status_id][w.field_name] = w.rule
h
end
@statuses.each {|status| @permissions[status.id] ||= {}}
end
end
def copy def copy
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any' if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
@@ -106,12 +80,12 @@ class WorkflowsController < ApplicationController
if request.post? if request.post?
if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?)
flash.now[:error] = l(:error_workflow_copy_source) flash.now[:error] = l(:error_workflow_copy_source)
elsif @target_trackers.blank? || @target_roles.blank? elsif @target_trackers.nil? || @target_roles.nil?
flash.now[:error] = l(:error_workflow_copy_target) flash.now[:error] = l(:error_workflow_copy_target)
else else
WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles) Workflow.copy(@source_tracker, @source_role, @target_trackers, @target_roles)
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to workflows_copy_path(:source_tracker_id => @source_tracker, :source_role_id => @source_role) redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role
end end
end end
end end
@@ -119,10 +93,10 @@ class WorkflowsController < ApplicationController
private private
def find_roles def find_roles
@roles = Role.sorted.all @roles = Role.find(:all, :order => 'builtin, position')
end end
def find_trackers def find_trackers
@trackers = Tracker.sorted.all @trackers = Tracker.find(:all, :order => 'position')
end end
end end
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
-33
View File
@@ -1,33 +0,0 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ActivitiesHelper
def sort_activity_events(events)
events_by_group = events.group_by(&:event_group)
sorted_events = []
events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event|
if group_events = events_by_group.delete(event.event_group)
group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i|
sorted_events << [e, i > 0]
end
end
end
sorted_events
end
end
+3 -7
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,8 +18,6 @@
module AdminHelper module AdminHelper
def project_status_options_for_select(selected) def project_status_options_for_select(selected)
options_for_select([[l(:label_all), ''], options_for_select([[l(:label_all), ''],
[l(:project_status_active), '1'], [l(:status_active), 1]], selected)
[l(:project_status_closed), '5'],
[l(:project_status_archived), '9']], selected.to_s)
end end
end end
File diff suppressed because it is too large Load Diff
+16 -17
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,27 +19,28 @@ module AttachmentsHelper
# Displays view/delete links to the attachments of the given object # Displays view/delete links to the attachments of the given object
# Options: # Options:
# :author -- author names are not displayed if set to false # :author -- author names are not displayed if set to false
# :thumbails -- display thumbnails if enabled in settings
def link_to_attachments(container, options = {}) def link_to_attachments(container, options = {})
options.assert_valid_keys(:author, :thumbnails) options.assert_valid_keys(:author)
if container.attachments.any? if container.attachments.any?
options = {:deletable => container.attachments_deletable?, :author => true}.merge(options) options = {:deletable => container.attachments_deletable?, :author => true}.merge(options)
render :partial => 'attachments/links', render :partial => 'attachments/links', :locals => {:attachments => container.attachments, :options => options}
:locals => {:attachments => container.attachments, :options => options, :thumbnails => (options[:thumbnails] && Setting.thumbnails_enabled?)}
end end
end end
def render_api_attachment(attachment, api) def to_utf8(str)
api.attachment do if str.respond_to?(:force_encoding)
api.id attachment.id str.force_encoding('UTF-8')
api.filename attachment.filename return str if str.valid_encoding?
api.filesize attachment.filesize else
api.content_type attachment.content_type return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
api.description attachment.description end
api.content_url url_for(:controller => 'attachments', :action => 'download', :id => attachment, :filename => attachment.filename, :only_path => false)
api.author(:id => attachment.author.id, :name => attachment.author.name) if attachment.author begin
api.created_on attachment.created_on Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
rescue Iconv::InvalidEncoding
# "UTF-8//IGNORE" is not supported on some OS
str
end end
end end
end end
+2 -7
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,7 +16,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module AuthSourcesHelper module AuthSourcesHelper
def auth_source_partial_name(auth_source)
"form_#{auth_source.class.name.underscore}"
end
end end
+2 -24
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,24 +16,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module BoardsHelper module BoardsHelper
def board_breadcrumb(item)
board = item.is_a?(Message) ? item.board : item
links = [link_to(l(:label_board_plural), project_boards_path(item.project))]
boards = board.ancestors.reverse
if item.is_a?(Message)
boards << board
end
links += boards.map {|ancestor| link_to(h(ancestor.name), project_board_path(ancestor.project, ancestor))}
breadcrumb links
end
def boards_options_for_select(boards)
options = []
Board.board_tree(boards) do |board, level|
label = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
label << board.name
options << [label, board.id]
end
options
end
end end
+3 -24
View File
@@ -1,22 +1,3 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module CalendarsHelper module CalendarsHelper
def link_to_previous_month(year, month, options={}) def link_to_previous_month(year, month, options={})
target_year, target_month = if month == 1 target_year, target_month = if month == 1
@@ -31,8 +12,7 @@ module CalendarsHelper
"#{month_name(target_month)}" "#{month_name(target_month)}"
end end
# \xc2\xab(utf-8) = &#171; link_to_month(('&#171; ' + name), target_year, target_month, options)
link_to_month(("\xc2\xab " + name), target_year, target_month, options)
end end
def link_to_next_month(year, month, options={}) def link_to_next_month(year, month, options={})
@@ -48,11 +28,10 @@ module CalendarsHelper
"#{month_name(target_month)}" "#{month_name(target_month)}"
end end
# \xc2\xbb(utf-8) = &#187; link_to_month((name + ' &#187;'), target_year, target_month, options)
link_to_month((name + " \xc2\xbb"), target_year, target_month, options)
end end
def link_to_month(link_name, year, month, options={}) def link_to_month(link_name, year, month, options={})
link_to_content_update(h(link_name), params.merge(:year => year, :month => month)) link_to_content_update(link_name, params.merge(:year => year, :month => month))
end end
end end
-43
View File
@@ -1,43 +0,0 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ContextMenusHelper
def context_menu_link(name, url, options={})
options[:class] ||= ''
if options.delete(:selected)
options[:class] << ' icon-checked disabled'
options[:disabled] = true
end
if options.delete(:disabled)
options.delete(:method)
options.delete(:data)
options[:onclick] = 'return false;'
options[:class] << ' disabled'
url = '#'
end
link_to h(name), url, options
end
def bulk_update_custom_field_context_menu_link(field, text, value)
context_menu_link h(text),
bulk_update_issues_path(:ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back),
:method => :post,
:selected => (@issue && @issue.custom_field_value(field) == value)
end
end
+36 -100
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,123 +17,75 @@
module CustomFieldsHelper module CustomFieldsHelper
CUSTOM_FIELDS_TABS = [
{:name => 'IssueCustomField', :partial => 'custom_fields/index',
:label => :label_issue_plural},
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
:label => :label_spent_time},
{:name => 'ProjectCustomField', :partial => 'custom_fields/index',
:label => :label_project_plural},
{:name => 'VersionCustomField', :partial => 'custom_fields/index',
:label => :label_version_plural},
{:name => 'UserCustomField', :partial => 'custom_fields/index',
:label => :label_user_plural},
{:name => 'GroupCustomField', :partial => 'custom_fields/index',
:label => :label_group_plural},
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
:label => TimeEntryActivity::OptionName},
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
:label => IssuePriority::OptionName},
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
:label => DocumentCategory::OptionName}
]
def custom_fields_tabs def custom_fields_tabs
CUSTOM_FIELDS_TABS tabs = [{:name => 'IssueCustomField', :partial => 'custom_fields/index', :label => :label_issue_plural},
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', :label => :label_spent_time},
{:name => 'ProjectCustomField', :partial => 'custom_fields/index', :label => :label_project_plural},
{:name => 'VersionCustomField', :partial => 'custom_fields/index', :label => :label_version_plural},
{:name => 'UserCustomField', :partial => 'custom_fields/index', :label => :label_user_plural},
{:name => 'GroupCustomField', :partial => 'custom_fields/index', :label => :label_group_plural},
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', :label => TimeEntryActivity::OptionName},
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', :label => IssuePriority::OptionName},
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName}
]
end end
# Return custom field html tag corresponding to its format # Return custom field html tag corresponding to its format
def custom_field_tag(name, custom_value) def custom_field_tag(name, custom_value)
custom_field = custom_value.custom_field custom_field = custom_value.custom_field
field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
field_id = "#{name}_custom_field_values_#{custom_field.id}" field_id = "#{name}_custom_field_values_#{custom_field.id}"
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.try(:edit_as)
when "date" when "date"
text_field_tag(field_name, custom_value.value, tag_options.merge(:size => 10)) + text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
calendar_for(field_id) calendar_for(field_id)
when "text" when "text"
text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3)) text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
when "bool" when "bool"
hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options) hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, :id => field_id)
when "list" when "list"
blank_option = ''.html_safe blank_option = custom_field.is_required? ?
unless custom_field.multiple? (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
if custom_field.is_required? '<option></option>'
unless custom_field.default_value.present? select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id)
blank_option = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '')
end
else
blank_option = content_tag('option')
end
end
s = select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value),
tag_options.merge(:multiple => custom_field.multiple?))
if custom_field.multiple?
s << hidden_field_tag(field_name, '')
end
s
else else
text_field_tag(field_name, custom_value.value, tag_options) text_field_tag(field_name, custom_value.value, :id => field_id)
end end
end end
# Return custom field label tag # Return custom field label tag
def custom_field_label_tag(name, custom_value, options={}) def custom_field_label_tag(name, custom_value)
required = options[:required] || custom_value.custom_field.is_required? content_tag "label", custom_value.custom_field.name +
(custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : ""),
content_tag "label", h(custom_value.custom_field.name) + :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
(required ? " <span class=\"required\">*</span>".html_safe : ""), :class => (custom_value.errors.empty? ? nil : "error" )
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
end end
# Return custom field tag with its label tag # Return custom field tag with its label tag
def custom_field_tag_with_label(name, custom_value, options={}) def custom_field_tag_with_label(name, custom_value)
custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value) custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
end end
def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil, value='') def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil)
field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
field_id = "#{name}_custom_field_values_#{custom_field.id}" field_id = "#{name}_custom_field_values_#{custom_field.id}"
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
unset_tag = ''
unless custom_field.is_required?
unset_tag = content_tag('label',
check_box_tag(field_name, '__none__', (value == '__none__'), :id => nil, :data => {:disables => "##{field_id}"}) + l(:button_clear),
:class => 'inline'
)
end
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.try(:edit_as)
when "date" when "date"
text_field_tag(field_name, value, tag_options.merge(:size => 10)) + text_field_tag(field_name, '', :id => field_id, :size => 10) +
calendar_for(field_id) + calendar_for(field_id)
unset_tag
when "text" when "text"
text_area_tag(field_name, value, tag_options.merge(:rows => 3)) + text_area_tag(field_name, '', :id => field_id, :rows => 3, :style => 'width:90%')
'<br />'.html_safe +
unset_tag
when "bool" when "bool"
select_tag(field_name, options_for_select([[l(:label_no_change_option), ''], select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
[l(:general_text_yes), '1'], [l(:general_text_yes), '1'],
[l(:general_text_no), '0']], value), tag_options) [l(:general_text_no), '0']]), :id => field_id)
when "list" when "list"
options = [] select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values_options(projects)), :id => field_id)
options << [l(:label_no_change_option), ''] unless custom_field.multiple?
options << [l(:label_none), '__none__'] unless custom_field.is_required?
options += custom_field.possible_values_options(projects)
select_tag(field_name, options_for_select(options, value), tag_options.merge(:multiple => custom_field.multiple?))
else else
text_field_tag(field_name, value, tag_options) + text_field_tag(field_name, '', :id => field_id)
unset_tag
end end
end end
@@ -147,11 +97,7 @@ module CustomFieldsHelper
# Return a string used to display a custom value # Return a string used to display a custom value
def format_value(value, field_format) def format_value(value, field_format)
if value.is_a?(Array) Redmine::CustomFieldFormat.format_value(value, field_format) # Proxy
value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ')
else
Redmine::CustomFieldFormat.format_value(value, field_format)
end
end end
# Return an array of custom field formats which can be used in select_tag # Return an array of custom field formats which can be used in select_tag
@@ -163,18 +109,8 @@ module CustomFieldsHelper
def render_api_custom_values(custom_values, api) def render_api_custom_values(custom_values, api)
api.array :custom_fields do api.array :custom_fields do
custom_values.each do |custom_value| custom_values.each do |custom_value|
attrs = {:id => custom_value.custom_field_id, :name => custom_value.custom_field.name} api.custom_field :id => custom_value.custom_field_id, :name => custom_value.custom_field.name do
attrs.merge!(:multiple => true) if custom_value.custom_field.multiple? api.value custom_value.value
api.custom_field attrs do
if custom_value.value.is_a?(Array)
api.array :value do
custom_value.value.each do |value|
api.value value unless value.blank?
end
end
else
api.value custom_value.value
end
end end
end end
end unless custom_values.empty? end unless custom_values.empty?
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+5 -7
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,19 +22,19 @@ module GanttHelper
when :in when :in
if gantt.zoom < 4 if gantt.zoom < 4
link_to_content_update l(:text_zoom_in), link_to_content_update l(:text_zoom_in),
params.merge(gantt.params.merge(:zoom => (gantt.zoom + 1))), params.merge(gantt.params.merge(:zoom => (gantt.zoom+1))),
:class => 'icon icon-zoom-in' :class => 'icon icon-zoom-in'
else else
content_tag(:span, l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe content_tag('span', l(:text_zoom_in), :class => 'icon icon-zoom-in')
end end
when :out when :out
if gantt.zoom > 1 if gantt.zoom > 1
link_to_content_update l(:text_zoom_out), link_to_content_update l(:text_zoom_out),
params.merge(gantt.params.merge(:zoom => (gantt.zoom - 1))), params.merge(gantt.params.merge(:zoom => (gantt.zoom-1))),
:class => 'icon icon-zoom-out' :class => 'icon icon-zoom-out'
else else
content_tag(:span, l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe content_tag('span', l(:text_zoom_out), :class => 'icon icon-zoom-out')
end end
end end
end end
+10 -18
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 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
@@ -18,25 +16,19 @@
# 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 GroupsHelper module GroupsHelper
# Options for the new membership projects combo-box
def options_for_membership_project_select(user, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
options << project_tree_options_for_select(projects) do |p|
{:disabled => (user.projects.include?(p))}
end
options
end
def group_settings_tabs def group_settings_tabs
tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general}, tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general},
{:name => 'users', :partial => 'groups/users', :label => :label_user_plural}, {:name => 'users', :partial => 'groups/users', :label => :label_user_plural},
{:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural} {:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural}
] ]
end end
def render_principals_for_new_group_users(group)
scope = User.active.sorted.not_in_group(group).like(params[:q])
principal_count = scope.count
principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page']
principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all
s = content_tag('div', principals_check_box_tags('user_ids[]', principals), :id => 'principals')
links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
link_to text, autocomplete_for_user_group_path(group, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
}
s + content_tag('p', links, :class => 'pagination')
end
end end
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+2
View File
@@ -0,0 +1,2 @@
module IssueMovesHelper
end
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+116 -221
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -48,13 +46,13 @@ module IssuesHelper
@cached_label_priority ||= l(:field_priority) @cached_label_priority ||= l(:field_priority)
@cached_label_project ||= l(:field_project) @cached_label_project ||= l(:field_project)
link_to_issue(issue) + "<br /><br />".html_safe + link_to_issue(issue) + "<br /><br />" +
"<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe + "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />" +
"<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe + "<strong>#{@cached_label_status}</strong>: #{issue.status.name}<br />" +
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe + "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe + "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
"<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe + "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
"<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
end end
def issue_heading(issue) def issue_heading(issue)
@@ -65,7 +63,7 @@ module IssuesHelper
s = '' s = ''
ancestors = issue.root? ? [] : issue.ancestors.visible.all ancestors = issue.root? ? [] : issue.ancestors.visible.all
ancestors.each do |ancestor| ancestors.each do |ancestor|
s << '<div>' + content_tag('p', link_to_issue(ancestor, :project => (issue.project_id != ancestor.project_id))) s << '<div>' + content_tag('p', link_to_issue(ancestor))
end end
s << '<div>' s << '<div>'
subject = h(issue.subject) subject = h(issue.subject)
@@ -74,99 +72,31 @@ module IssuesHelper
end end
s << content_tag('h3', subject) s << content_tag('h3', subject)
s << '</div>' * (ancestors.size + 1) s << '</div>' * (ancestors.size + 1)
s.html_safe 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.visible.sort_by(&:lft)) do |child, level|
css = "issue issue-#{child.id} hascontextmenu"
css << " idnt idnt-#{level}" if level > 0
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, :project => (issue.project_id != child.project_id)), :class => 'subject') + content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') +
content_tag('td', h(child.status)) + content_tag('td', h(child.status)) +
content_tag('td', link_to_user(child.assigned_to)) + content_tag('td', link_to_user(child.assigned_to)) +
content_tag('td', progress_bar(child.done_ratio, :width => '80px')), content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
:class => css) :class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
end end
s << '</table></form>' s << '</form></table>'
s.html_safe s
end
# Returns an array of error messages for bulk edited issues
def bulk_edit_error_messages(issues)
messages = {}
issues.each do |issue|
issue.errors.full_messages.each do |message|
messages[message] ||= []
messages[message] << issue
end
end
messages.map { |message, issues|
"#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
}
end
# Returns a link for adding a new subtask to the given issue
def link_to_new_subtask(issue)
attrs = {
:tracker_id => issue.tracker,
:parent_issue_id => issue
}
link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs))
end
class IssueFieldsRows
include ActionView::Helpers::TagHelper
def initialize
@left = []
@right = []
end
def left(*args)
args.any? ? @left << cells(*args) : @left
end
def right(*args)
args.any? ? @right << cells(*args) : @right
end
def size
@left.size > @right.size ? @left.size : @right.size
end
def to_html
html = ''.html_safe
blank = content_tag('th', '') + content_tag('td', '')
size.times do |i|
left = @left[i] || blank
right = @right[i] || blank
html << content_tag('tr', left + right)
end
html
end
def cells(label, text, options={})
content_tag('th', "#{label}:", options) + content_tag('td', text, options)
end
end
def issue_fields_rows
r = IssueFieldsRows.new
yield r
r.to_html
end end
def render_custom_fields_rows(issue) def render_custom_fields_rows(issue)
values = issue.visible_custom_field_values return if issue.custom_field_values.empty?
return if values.empty?
ordered_values = [] ordered_values = []
half = (values.size / 2.0).ceil half = (issue.custom_field_values.size / 2.0).ceil
half.times do |i| half.times do |i|
ordered_values << values[i] ordered_values << issue.custom_field_values[i]
ordered_values << values[i + half] ordered_values << issue.custom_field_values[i + half]
end end
s = "<tr>\n" s = "<tr>\n"
n = 0 n = 0
@@ -176,7 +106,7 @@ module IssuesHelper
n += 1 n += 1
end end
s << "</tr>\n" s << "</tr>\n"
s.html_safe s
end end
def issues_destroy_confirmation_message(issues) def issues_destroy_confirmation_message(issues)
@@ -199,150 +129,75 @@ module IssuesHelper
def sidebar_queries def sidebar_queries
unless @sidebar_queries unless @sidebar_queries
@sidebar_queries = IssueQuery.visible. # User can see public queries and his own queries
order("#{Query.table_name}.name ASC"). visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
# Project specific queries and global queries # Project specific queries and global queries
where(@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])
all @sidebar_queries = Query.find(:all,
:select => 'id, name, is_public',
:order => "name ASC",
:conditions => visible.conditions)
end end
@sidebar_queries @sidebar_queries
end end
def query_links(title, queries) def query_links(title, queries)
return '' if queries.empty?
# links to #index on issues/show # links to #index on issues/show
url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
content_tag('h3', title) + "\n" + content_tag('h3', title) +
content_tag('ul', queries.collect {|query|
queries.collect {|query| link_to(h(query.name), url_params.merge(:query_id => query))
css = 'query' }.join('<br />')
css << ' selected' if query == @query
content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
}.join("\n").html_safe,
:class => 'queries'
) + "\n"
end end
def render_sidebar_queries def render_sidebar_queries
out = ''.html_safe out = ''
out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?)) queries = sidebar_queries.select {|q| !q.is_public?}
out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?)) out << query_links(l(:label_my_queries), queries) if queries.any?
queries = sidebar_queries.select {|q| q.is_public?}
out << query_links(l(:label_query_plural), queries) if queries.any?
out out
end end
def email_issue_attributes(issue, user) def show_detail(detail, no_html=false)
items = []
%w(author status priority assigned_to category fixed_version).each do |attribute|
unless issue.disabled_core_fields.include?(attribute+"_id")
items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
end
end
issue.visible_custom_field_values(user).each do |value|
items << "#{value.custom_field.name}: #{show_value(value)}"
end
items
end
def render_email_issue_attributes(issue, user, html=false)
items = email_issue_attributes(issue, user)
if html
content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe)
else
items.map{|s| "* #{s}"}.join("\n")
end
end
# Returns the textual representation of a journal details
# as an array of strings
def details_to_strings(details, no_html=false, options={})
options[:only_path] = (options[:only_path] == false ? false : true)
strings = []
values_by_field = {}
details.each do |detail|
if detail.property == 'cf'
field = detail.custom_field
if field && field.multiple?
values_by_field[field] ||= {:added => [], :deleted => []}
if detail.old_value
values_by_field[field][:deleted] << detail.old_value
end
if detail.value
values_by_field[field][:added] << detail.value
end
next
end
end
strings << show_detail(detail, no_html, options)
end
values_by_field.each do |field, changes|
detail = JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s)
detail.instance_variable_set "@custom_field", field
if changes[:added].any?
detail.value = changes[:added]
strings << show_detail(detail, no_html, options)
elsif changes[:deleted].any?
detail.old_value = changes[:deleted]
strings << show_detail(detail, no_html, options)
end
end
strings
end
# Returns the textual representation of a single journal detail
def show_detail(detail, no_html=false, options={})
multiple = false
case detail.property case detail.property
when 'attr' when 'attr'
field = detail.prop_key.to_s.gsub(/\_id$/, "") field = detail.prop_key.to_s.gsub(/\_id$/, "")
label = l(("field_" + field).to_sym) label = l(("field_" + field).to_sym)
case detail.prop_key case
when 'due_date', 'start_date' when ['due_date', 'start_date'].include?(detail.prop_key)
value = format_date(detail.value.to_date) if detail.value value = format_date(detail.value.to_date) if detail.value
old_value = format_date(detail.old_value.to_date) if detail.old_value old_value = format_date(detail.old_value.to_date) if detail.old_value
when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id', when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
'priority_id', 'category_id', 'fixed_version_id'
value = find_name_by_reflection(field, detail.value) value = find_name_by_reflection(field, detail.value)
old_value = find_name_by_reflection(field, detail.old_value) old_value = find_name_by_reflection(field, detail.old_value)
when 'estimated_hours' when detail.prop_key == 'estimated_hours'
value = "%0.02f" % detail.value.to_f unless detail.value.blank? value = "%0.02f" % detail.value.to_f unless detail.value.blank?
old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank? old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
when 'parent_id' when detail.prop_key == 'parent_id'
label = l(:field_parent_issue) label = l(:field_parent_issue)
value = "##{detail.value}" unless detail.value.blank? value = "##{detail.value}" unless detail.value.blank?
old_value = "##{detail.old_value}" unless detail.old_value.blank? old_value = "##{detail.old_value}" unless detail.old_value.blank?
when 'is_private' when detail.prop_key == 'is_private'
value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank? value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank? old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
end end
when 'cf' when 'cf'
custom_field = detail.custom_field custom_field = CustomField.find_by_id(detail.prop_key)
if custom_field if custom_field
multiple = custom_field.multiple?
label = custom_field.name label = custom_field.name
value = format_value(detail.value, custom_field.field_format) if detail.value value = format_value(detail.value, custom_field.field_format) if detail.value
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
end end
when 'attachment' when 'attachment'
label = l(:label_attachment) label = l(:label_attachment)
when 'relation'
if detail.value && !detail.old_value
rel_issue = Issue.visible.find_by_id(detail.value)
value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" :
(no_html ? rel_issue : link_to_issue(rel_issue))
elsif detail.old_value && !detail.value
rel_issue = Issue.visible.find_by_id(detail.old_value)
old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" :
(no_html ? rel_issue : link_to_issue(rel_issue))
end
label = l(detail.prop_key.to_sym)
end end
call_hook(:helper_issues_show_detail_after_setting, call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
{:detail => detail, :label => label, :value => value, :old_value => old_value })
label ||= detail.prop_key label ||= detail.prop_key
value ||= detail.value value ||= detail.value
@@ -351,19 +206,10 @@ module IssuesHelper
unless no_html unless no_html
label = content_tag('strong', label) label = content_tag('strong', label)
old_value = content_tag("i", h(old_value)) if detail.old_value old_value = content_tag("i", h(old_value)) if detail.old_value
if detail.old_value && detail.value.blank? && detail.property != 'relation' old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
old_value = content_tag("del", old_value) if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
end
if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key)
# Link to the attachment if it has not been removed # Link to the attachment if it has not been removed
value = link_to_attachment(atta, :download => true, :only_path => options[:only_path]) value = link_to_attachment(a)
if options[:only_path] != false && atta.is_text?
value += link_to(
image_tag('magnifier.png'),
:controller => 'attachments', :action => 'show',
:id => atta, :filename => atta.filename
)
end
else else
value = content_tag("i", h(value)) if value value = content_tag("i", h(value)) if value
end end
@@ -373,42 +219,33 @@ module IssuesHelper
s = l(:text_journal_changed_no_detail, :label => label) s = l(:text_journal_changed_no_detail, :label => label)
unless no_html unless no_html
diff_link = link_to 'diff', diff_link = link_to 'diff',
{:controller => 'journals', :action => 'diff', :id => detail.journal_id, {:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id},
:detail_id => detail.id, :only_path => options[:only_path]},
:title => l(:label_view_diff) :title => l(:label_view_diff)
s << " (#{ diff_link })" s << " (#{ diff_link })"
end end
s.html_safe s
elsif detail.value.present? elsif !detail.value.blank?
case detail.property case detail.property
when 'attr', 'cf' when 'attr', 'cf'
if detail.old_value.present? if !detail.old_value.blank?
l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe l(:text_journal_changed, :label => label, :old => old_value, :new => value)
elsif multiple
l(:text_journal_added, :label => label, :value => value).html_safe
else else
l(:text_journal_set_to, :label => label, :value => value).html_safe l(:text_journal_set_to, :label => label, :value => value)
end end
when 'attachment', 'relation' when 'attachment'
l(:text_journal_added, :label => label, :value => value).html_safe l(:text_journal_added, :label => label, :value => value)
end end
else else
l(:text_journal_deleted, :label => label, :old => old_value).html_safe l(:text_journal_deleted, :label => label, :old => old_value)
end end
end end
# Find the name of an associated record stored in the field attribute # Find the name of an associated record stored in the field attribute
def find_name_by_reflection(field, id) def find_name_by_reflection(field, id)
unless id.present?
return nil
end
association = Issue.reflect_on_association(field.to_sym) association = Issue.reflect_on_association(field.to_sym)
if association if association
record = association.class_name.constantize.find_by_id(id) record = association.class_name.constantize.find_by_id(id)
if record return record.name if record
record.name.force_encoding('UTF-8') if record.name.respond_to?(:force_encoding)
return record.name
end
end end
end end
@@ -425,4 +262,62 @@ module IssuesHelper
end end
end end
end end
def issues_to_csv(issues, project = nil)
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
decimal_separator = l(:general_csv_decimal_separator)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
headers = [ "#",
l(:field_status),
l(:field_project),
l(:field_tracker),
l(:field_priority),
l(:field_subject),
l(:field_assigned_to),
l(:field_category),
l(:field_fixed_version),
l(:field_author),
l(:field_start_date),
l(:field_due_date),
l(:field_done_ratio),
l(:field_estimated_hours),
l(:field_parent_issue),
l(:field_created_on),
l(:field_updated_on)
]
# Export project custom fields if project is given
# otherwise export custom fields marked as "For all projects"
custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields
custom_fields.each {|f| headers << f.name}
# Description in the last column
headers << l(:field_description)
csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
# csv lines
issues.each do |issue|
fields = [issue.id,
issue.status.name,
issue.project.name,
issue.tracker.name,
issue.priority.name,
issue.subject,
issue.assigned_to,
issue.category,
issue.fixed_version,
issue.author.name,
format_date(issue.start_date),
format_date(issue.due_date),
issue.done_ratio,
issue.estimated_hours.to_s.gsub('.', decimal_separator),
issue.parent_id,
format_time(issue.created_on),
format_time(issue.updated_on)
]
custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) }
fields << issue.description
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
end
end
export
end
end end
+9 -13
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2008 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -23,24 +21,22 @@ module JournalsHelper
editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project))) editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project)))
links = [] links = []
if !journal.notes.blank? if !journal.notes.blank?
links << link_to(image_tag('comment.png'), links << link_to_remote(image_tag('comment.png'),
{:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal}, { :url => {:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal} },
:remote => true, :title => l(:button_quote)) if options[:reply_links]
:method => 'post',
:title => l(:button_quote)) if options[:reply_links]
links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes", links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes",
{ :controller => 'journals', :action => 'edit', :id => journal, :format => 'js' }, { :controller => 'journals', :action => 'edit', :id => journal },
:title => l(:button_edit)) if editable :title => l(:button_edit)) if editable
end end
content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty? content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty?
content << textilizable(journal, :notes) content << textilizable(journal, :notes)
css_classes = "wiki" css_classes = "wiki"
css_classes << " editable" if editable css_classes << " editable" if editable
content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes) content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => css_classes)
end end
def link_to_in_place_notes_editor(text, field_id, url, options={}) def link_to_in_place_notes_editor(text, field_id, url, options={})
onclick = "$.ajax({url: '#{url_for(url)}', type: 'get'}); return false;" onclick = "new Ajax.Request('#{url_for(url)}', {asynchronous:true, evalScripts:true, method:'get'}); return false;"
link_to text, '#', options.merge(:onclick => onclick) link_to text, '#', options.merge(:onclick => onclick)
end end
end end
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2008 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+2 -18
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,18 +16,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MembersHelper module MembersHelper
def render_principals_for_new_members(project)
scope = Principal.active.sorted.not_member_of(project).like(params[:q])
principal_count = scope.count
principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page']
principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all
s = content_tag('div', principals_check_box_tags('membership[user_ids][]', principals), :id => 'principals')
links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
}
s + content_tag('p', links, :class => 'pagination')
end
end end
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+2 -54
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,54 +16,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MyHelper module MyHelper
def calendar_items(startdt, enddt)
Issue.visible.
where(:project_id => User.current.projects.map(&:id)).
where("(start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)", startdt, enddt, startdt, enddt).
includes(:project, :tracker, :priority, :assigned_to).
all
end
def documents_items
Document.visible.order("#{Document.table_name}.created_on DESC").limit(10).all
end
def issuesassignedtome_items
Issue.visible.open.
where(:assigned_to_id => ([User.current.id] + User.current.group_ids)).
limit(10).
includes(:status, :project, :tracker, :priority).
order("#{IssuePriority.table_name}.position DESC, #{Issue.table_name}.updated_on DESC").
all
end
def issuesreportedbyme_items
Issue.visible.
where(:author_id => User.current.id).
limit(10).
includes(:status, :project, :tracker).
order("#{Issue.table_name}.updated_on DESC").
all
end
def issueswatched_items
Issue.visible.on_active_project.watched_by(User.current.id).recently_updated.limit(10).all
end
def news_items
News.visible.
where(:project_id => User.current.projects.map(&:id)).
limit(10).
includes(:project, :author).
order("#{News.table_name}.created_on DESC").
all
end
def timelog_items
TimeEntry.
where("#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", User.current.id, Date.today - 6, Date.today).
includes(:activity, :project, {:issue => [:tracker, :status]}).
order("#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC").
all
end
end end
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+39 -15
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -30,7 +28,7 @@ module ProjectsHelper
{:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural}, {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
{:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural}, {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
{:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki}, {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
{:name => 'repositories', :action => :manage_repository, :partial => 'projects/settings/repositories', :label => :label_repository_plural}, {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
{:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}, {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural},
{:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities} {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
] ]
@@ -46,20 +44,43 @@ module ProjectsHelper
end end
options = '' options = ''
options << "<option value=''>&nbsp;</option>" if project.allowed_parents.include?(nil) options << "<option value=''></option>" if project.allowed_parents.include?(nil)
options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected) options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id') content_tag('select', options, :name => 'project[parent_id]', :id => 'project_parent_id')
end end
# Renders the projects index # Renders a tree of projects as a nested set of unordered lists
# The given collection may be a subset of the whole project tree
# (eg. some intermediate nodes are private and can not be seen)
def render_project_hierarchy(projects) def render_project_hierarchy(projects)
render_project_nested_lists(projects) do |project| s = ''
s = link_to_project(project, {}, :class => "#{project.css_classes} #{User.current.member_of?(project) ? 'my-project' : nil}") if projects.any?
if project.description.present? ancestors = []
s << content_tag('div', textilizable(project.short_description, :project => project), :class => 'wiki description') original_project = @project
projects.each do |project|
# set the project environment to please macros.
@project = project
if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
else
ancestors.pop
s << "</li>"
while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
ancestors.pop
s << "</ul></li>\n"
end
end
classes = (ancestors.empty? ? 'root' : 'child')
s << "<li class='#{classes}'><div class='#{classes}'>" +
link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
s << "</div>\n"
ancestors << project
end end
s s << ("</li></ul>\n" * ancestors.size)
@project = original_project
end end
s
end end
# Returns a set of options for a select field, grouped by project. # Returns a set of options for a select field, grouped by project.
@@ -68,12 +89,15 @@ module ProjectsHelper
versions.each do |version| versions.each do |version|
grouped[version.project.name] << [version.name, version.id] grouped[version.project.name] << [version.name, version.id]
end end
# Add in the selected
if selected && !versions.include?(selected)
grouped[selected.project.name] << [selected.name, selected.id]
end
selected = selected.is_a?(Version) ? selected.id : selected
if grouped.keys.size > 1 if grouped.keys.size > 1
grouped_options_for_select(grouped, selected) grouped_options_for_select(grouped, selected && selected.id)
else else
options_for_select((grouped.values.first || []), selected) options_for_select((grouped.values.first || []), selected && selected.id)
end end
end end
+25 -149
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,84 +16,24 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module QueriesHelper module QueriesHelper
def filters_options_for_select(query)
options_for_select(filters_options(query))
end
def filters_options(query) def operators_for_select(filter_type)
options = [[]] Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
options += query.available_filters.map do |field, field_options|
[field_options[:name], field]
end
end
def query_filters_hidden_tags(query)
tags = ''.html_safe
query.filters.each do |field, options|
tags << hidden_field_tag("f[]", field, :id => nil)
tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
options[:values].each do |value|
tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
end
end
tags
end
def query_columns_hidden_tags(query)
tags = ''.html_safe
query.columns.each do |column|
tags << hidden_field_tag("c[]", column.name, :id => nil)
end
tags
end
def query_hidden_tags(query)
query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
end
def available_block_columns_tags(query)
tags = ''.html_safe
query.available_block_columns.each do |column|
tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column)) + " #{column.caption}", :class => 'inline')
end
tags
end
def query_available_inline_columns_options(query)
(query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
end
def query_selected_inline_columns_options(query)
(query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
end
def render_query_columns_selection(query, options={})
tag_name = (options[:name] || 'c') + '[]'
render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
end end
def column_header(column) def column_header(column)
column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
:default_order => column.default_order) : :default_order => column.default_order) :
content_tag('th', h(column.caption)) content_tag('th', column.caption)
end end
def column_content(column, issue) def column_content(column, issue)
value = column.value(issue) value = column.value(issue)
if value.is_a?(Array)
value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
else
column_value(column, issue, value)
end
end
def column_value(column, issue, value)
case value.class.name case value.class.name
when 'String' when 'String'
if column.name == :subject if column.name == :subject
link_to(h(value), :controller => 'issues', :action => 'show', :id => issue) link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
elsif column.name == :description
issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
else else
h(value) h(value)
end end
@@ -103,16 +41,12 @@ module QueriesHelper
format_time(value) format_time(value)
when 'Date' when 'Date'
format_date(value) format_date(value)
when 'Fixnum' when 'Fixnum', 'Float'
if column.name == :id if column.name == :done_ratio
link_to value, issue_path(issue)
elsif column.name == :done_ratio
progress_bar(value, :width => '80px') progress_bar(value, :width => '80px')
else else
value.to_s value.to_s
end end
when 'Float'
sprintf "%.2f", value
when 'User' when 'User'
link_to_user value link_to_user value
when 'Project' when 'Project'
@@ -124,101 +58,43 @@ module QueriesHelper
when 'FalseClass' when 'FalseClass'
l(:general_text_No) l(:general_text_No)
when 'Issue' when 'Issue'
value.visible? ? link_to_issue(value) : "##{value.id}" link_to_issue(value, :subject => false)
when 'IssueRelation'
other = value.other_issue(issue)
content_tag('span',
(l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe,
:class => value.css_classes_for(issue))
else else
h(value) h(value)
end end
end end
def csv_content(column, issue)
value = column.value(issue)
if value.is_a?(Array)
value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
else
csv_value(column, issue, value)
end
end
def csv_value(column, issue, value)
case value.class.name
when 'Time'
format_time(value)
when 'Date'
format_date(value)
when 'Float'
sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
when 'IssueRelation'
other = value.other_issue(issue)
l(value.label_for(issue)) + " ##{other.id}"
else
value.to_s
end
end
def query_to_csv(items, query, options={})
encoding = l(:general_csv_encoding)
columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
query.available_block_columns.each do |column|
if options[column.name].present?
columns << column
end
end
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
# csv lines
items.each do |item|
csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) }
end
end
export
end
# Retrieve query from session or build a new query # Retrieve query from session or build a new query
def retrieve_query def retrieve_query
if !params[:query_id].blank? if !params[:query_id].blank?
cond = "project_id IS NULL" cond = "project_id IS NULL"
cond << " OR project_id = #{@project.id}" if @project cond << " OR project_id = #{@project.id}" if @project
@query = IssueQuery.where(cond).find(params[:query_id]) @query = Query.find(params[:query_id], :conditions => cond)
raise ::Unauthorized unless @query.visible? raise ::Unauthorized unless @query.visible?
@query.project = @project @query.project = @project
session[:query] = {:id => @query.id, :project_id => @query.project_id} session[:query] = {:id => @query.id, :project_id => @query.project_id}
sort_clear sort_clear
elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
# Give it a name, required to be valid
@query = IssueQuery.new(:name => "_")
@query.project = @project
@query.build_from_params(params)
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
else else
# retrieve from session if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
@query = nil # Give it a name, required to be valid
@query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id] @query = Query.new(:name => "_")
@query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) @query.project = @project
@query.project = @project if params[:fields] || params[:f]
end @query.filters = {}
end @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
else
def retrieve_query_from_session @query.available_filters.keys.each do |field|
if session[:query] @query.add_short_filter(field, params[field]) if params[field]
if session[:query][:id] end
@query = IssueQuery.find_by_id(session[:query][:id]) end
return unless @query @query.group_by = params[:group_by]
else @query.column_names = params[:c] || (params[:query] && params[:query][:column_names])
@query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
end
if session[:query].has_key?(:project_id)
@query.project_id = session[:query][:project_id]
else else
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
@query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
@query.project = @project @query.project = @project
end end
@query
end end
end end
end end
+3 -10
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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 +22,7 @@ module ReportsHelper
data.each { |row| data.each { |row|
match = 1 match = 1
criteria.each { |k, v| criteria.each { |k, v|
match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && (v == 0 ? ['f', false] : ['t', true]).include?(row[k])) match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && row[k] == (v == 0 ? "f" : "t"))
} unless criteria.nil? } unless criteria.nil?
a = a + row["total"].to_i if match == 1 a = a + row["total"].to_i if match == 1
} unless data.nil? } unless data.nil?
@@ -33,11 +31,6 @@ module ReportsHelper
def aggregate_link(data, criteria, *args) def aggregate_link(data, criteria, *args)
a = aggregate data, criteria a = aggregate data, criteria
a > 0 ? link_to(h(a), *args) : '-' a > 0 ? link_to(a, *args) : '-'
end
def aggregate_path(project, field, row, options={})
parameters = {:set_filter => 1, :subproject_id => '!*', field => row.id}.merge(options)
project_issues_path(row.is_a?(Project) ? row : project, parameters)
end end
end end
+72 -85
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,6 +15,9 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv'
require 'redmine/codeset_util'
module RepositoriesHelper module RepositoriesHelper
def format_revision(revision) def format_revision(revision)
if revision.respond_to? :format_identifier if revision.respond_to? :format_identifier
@@ -36,24 +37,24 @@ module RepositoriesHelper
unless properties.nil? || properties.empty? unless properties.nil? || properties.empty?
content = '' content = ''
properties.keys.sort.each do |property| properties.keys.sort.each do |property|
content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>".html_safe) content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>")
end end
content_tag('ul', content.html_safe, :class => 'properties') content_tag('ul', content, :class => 'properties')
end end
end end
def render_changeset_changes def render_changeset_changes
changes = @changeset.filechanges.limit(1000).reorder('path').all.collect do |change| changes = @changeset.changes.find(:all, :limit => 1000, :order => 'path').collect do |change|
case change.action case change.action
when 'A' when 'A'
# Detects moved/copied files # Detects moved/copied files
if !change.from_path.blank? if !change.from_path.blank?
change.action = change.action =
@changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C' @changeset.changes.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
end end
change change
when 'D' when 'D'
@changeset.filechanges.detect {|c| c.from_path == change.path} ? nil : change @changeset.changes.detect {|c| c.from_path == change.path} ? nil : change
else else
change change
end end
@@ -86,39 +87,66 @@ module RepositoriesHelper
if s = tree[file][:s] if s = tree[file][:s]
style << ' folder' style << ' folder'
path_param = to_path_param(@repository.relative_path(file)) path_param = to_path_param(@repository.relative_path(file))
text = link_to(h(text), :controller => 'repositories', text = link_to(text, :controller => 'repositories',
:action => 'show', :action => 'show',
:id => @project, :id => @project,
:repository_id => @repository.identifier_param,
:path => path_param, :path => path_param,
:rev => @changeset.identifier) :rev => @changeset.identifier)
output << "<li class='#{style}'>#{text}" output << "<li class='#{style}'>#{text}</li>"
output << render_changes_tree(s) output << render_changes_tree(s)
output << "</li>"
elsif c = tree[file][:c] elsif c = tree[file][:c]
style << " change-#{c.action}" style << " change-#{c.action}"
path_param = to_path_param(@repository.relative_path(c.path)) path_param = to_path_param(@repository.relative_path(c.path))
text = link_to(h(text), :controller => 'repositories', text = link_to(text, :controller => 'repositories',
:action => 'entry', :action => 'entry',
:id => @project, :id => @project,
:repository_id => @repository.identifier_param,
:path => path_param, :path => path_param,
:rev => @changeset.identifier) unless c.action == 'D' :rev => @changeset.identifier) unless c.action == 'D'
text << " - #{h(c.revision)}" unless c.revision.blank? text << " - #{c.revision}" unless c.revision.blank?
text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories', text << ' (' + link_to('diff', :controller => 'repositories',
:action => 'diff', :action => 'diff',
:id => @project, :id => @project,
:repository_id => @repository.identifier_param,
:path => path_param, :path => path_param,
:rev => @changeset.identifier) + ') '.html_safe if c.action == 'M' :rev => @changeset.identifier) + ') ' if c.action == 'M'
text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank? text << ' ' + content_tag('span', c.from_path, :class => 'copied-from') unless c.from_path.blank?
output << "<li class='#{style}'>#{text}</li>" output << "<li class='#{style}'>#{text}</li>"
end end
end end
output << '</ul>' output << '</ul>'
output.html_safe output
end end
def 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
if str.respond_to?(:force_encoding)
str.force_encoding('UTF-8')
end
@encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
@encodings.each do |encoding|
begin
return Iconv.conv('UTF-8', encoding, str)
rescue Iconv::Failure
# do nothing here and try the next encoding
end
end
str = Redmine::CodesetUtil.replace_invalid_utf8(str)
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) && if repository.is_a?(Repository) &&
@@ -138,7 +166,15 @@ module RepositoriesHelper
select_tag('repository_scm', select_tag('repository_scm',
options_for_select(scm_options, repository.class.name.demodulize), options_for_select(scm_options, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?), :disabled => (repository && !repository.new_record?),
:data => {:remote => true, :method => 'get'}) :onchange => remote_function(
:url => {
:controller => 'repositories',
:action => 'edit',
:id => @project
},
:method => :get,
:with => "Form.serialize(this.form)")
)
end end
def with_leading_slash(path) def with_leading_slash(path)
@@ -151,9 +187,8 @@ module RepositoriesHelper
def subversion_field_tags(form, repository) def subversion_field_tags(form, repository)
content_tag('p', form.text_field(:url, :size => 60, :required => true, content_tag('p', form.text_field(:url, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')) + :disabled => (repository && !repository.root_url.blank?)) +
'<br />'.html_safe + '<br />(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
'(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
content_tag('p', form.text_field(:login, :size => 30)) + content_tag('p', form.text_field(:login, :size => 30)) +
content_tag('p', form.password_field( content_tag('p', form.password_field(
:password, :size => 30, :name => 'ignore', :password, :size => 30, :name => 'ignore',
@@ -166,7 +201,7 @@ module RepositoriesHelper
content_tag('p', form.text_field( content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository), :url, :label => l(:field_path_to_repository),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url'))) + :disabled => (repository && !repository.new_record?))) +
content_tag('p', form.select( content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS, :log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true)) :label => l(:field_commit_logs_encoding), :required => true))
@@ -176,29 +211,28 @@ module RepositoriesHelper
content_tag('p', form.text_field( content_tag('p', form.text_field(
:url, :label => l(:field_path_to_repository), :url, :label => l(:field_path_to_repository),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url') :disabled => (repository && !repository.root_url.blank?)
) + ) +
'<br />'.html_safe + l(:text_mercurial_repository_note)) + '<br />' + l(:text_mercurial_repository_note)) +
content_tag('p', form.select( content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS, :path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding) :label => l(:field_scm_path_encoding)
) + ) +
'<br />'.html_safe + l(:text_scm_path_encoding_note)) '<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 => l(:field_path_to_repository), :url, :label => l(:field_path_to_repository),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url') :disabled => (repository && !repository.root_url.blank?)
) + ) +
'<br />'.html_safe + '<br />' + l(:text_git_repository_note)) +
l(:text_git_repository_note)) +
content_tag('p', form.select( content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS, :path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding) :label => l(:field_scm_path_encoding)
) + ) +
'<br />'.html_safe + l(:text_scm_path_encoding_note)) + '<br />' + l(:text_scm_path_encoding_note)) +
content_tag('p', form.check_box( content_tag('p', form.check_box(
:extra_report_last_commit, :extra_report_last_commit,
:label => l(:label_git_report_last_commit) :label => l(:label_git_report_last_commit)
@@ -210,12 +244,12 @@ module RepositoriesHelper
:root_url, :root_url,
:label => l(:field_cvsroot), :label => l(:field_cvsroot),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('root_url'))) + :disabled => !repository.new_record?)) +
content_tag('p', form.text_field( content_tag('p', form.text_field(
:url, :url,
:label => l(:field_cvs_module), :label => l(:field_cvs_module),
:size => 30, :required => true, :size => 30, :required => true,
:disabled => !repository.safe_attribute?('url'))) + :disabled => !repository.new_record?)) +
content_tag('p', form.select( content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS, :log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true)) + :label => l(:field_commit_logs_encoding), :required => true)) +
@@ -223,14 +257,14 @@ module RepositoriesHelper
:path_encoding, [nil] + Setting::ENCODINGS, :path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding) :label => l(:field_scm_path_encoding)
) + ) +
'<br />'.html_safe + l(:text_scm_path_encoding_note)) '<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 => l(:field_path_to_repository), :url, :label => l(:field_path_to_repository),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url'))) + :disabled => (repository && !repository.new_record?))) +
content_tag('p', form.select( content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS, :log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true)) :label => l(:field_commit_logs_encoding), :required => true))
@@ -240,58 +274,11 @@ module RepositoriesHelper
content_tag('p', form.text_field( content_tag('p', form.text_field(
:url, :label => l(:field_root_directory), :url, :label => l(:field_root_directory),
:size => 60, :required => true, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url'))) + :disabled => (repository && !repository.root_url.blank?))) +
content_tag('p', form.select( content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS, :path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding) :label => l(:field_scm_path_encoding)
) + ) +
'<br />'.html_safe + l(:text_scm_path_encoding_note)) '<br />' + l(:text_scm_path_encoding_note))
end
def index_commits(commits, heads)
return nil if commits.nil? or commits.first.parents.nil?
refs_map = {}
heads.each do |head|
refs_map[head.scmid] ||= []
refs_map[head.scmid] << head
end
commits_by_scmid = {}
commits.reverse.each_with_index do |commit, commit_index|
commits_by_scmid[commit.scmid] = {
:parent_scmids => commit.parents.collect { |parent| parent.scmid },
:rdmid => commit_index,
:refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil,
:scmid => commit.scmid,
:href => block_given? ? yield(commit.scmid) : commit.scmid
}
end
heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
space = nil
heads.each do |head|
if commits_by_scmid.include? head.scmid
space = index_head((space || -1) + 1, head, commits_by_scmid)
end
end
# when no head matched anything use first commit
space ||= index_head(0, commits.first, commits_by_scmid)
return commits_by_scmid, space
end
def index_head(space, commit, commits_by_scmid)
stack = [[space, commits_by_scmid[commit.scmid]]]
max_space = space
until stack.empty?
space, commit = stack.pop
commit[:space] = space if commit[:space].nil?
space -= 1
commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
parent_commit = commits_by_scmid[parent_scmid]
if parent_commit and parent_commit[:space].nil?
stack.unshift [space += 1, parent_commit]
end
end
max_space = space if max_space < space
end
max_space
end end
end end
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+4 -10
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -37,7 +35,7 @@ module SearchHelper
result << content_tag('span', h(words), :class => "highlight token-#{t}") result << content_tag('span', h(words), :class => "highlight token-#{t}")
end end
end end
result.html_safe result
end end
def type_label(t) def type_label(t)
@@ -49,7 +47,6 @@ module SearchHelper
options << [l(:label_my_projects), 'my_projects'] unless User.current.memberships.empty? options << [l(:label_my_projects), 'my_projects'] unless User.current.memberships.empty?
options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.descendants.active.empty? options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.descendants.active.empty?
options << [@project.name, ''] unless @project.nil? options << [@project.name, ''] unless @project.nil?
label_tag("scope", l(:description_project_scope), :class => "hidden-for-sighted") +
select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1 select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1
end end
@@ -60,11 +57,8 @@ module SearchHelper
c = results_by_type[t] c = results_by_type[t]
next if c == 0 next if c == 0
text = "#{type_label(t)} (#{c})" text = "#{type_label(t)} (#{c})"
links << link_to(h(text), :q => params[:q], :titles_only => params[:titles_only], links << link_to(text, :q => params[:q], :titles_only => params[:titles_only], :all_words => params[:all_words], :scope => params[:scope], t => 1)
:all_words => params[:all_words], :scope => params[:scope], t => 1)
end end
('<ul>'.html_safe + ('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
links.map {|link| content_tag('li', link)}.join(' ').html_safe +
'</ul>'.html_safe) unless links.empty?
end end
end end
+19 -34
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,18 +32,18 @@ module SettingsHelper
if blank_text = options.delete(:blank) if blank_text = options.delete(:blank)
choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices
end end
setting_label(setting, options).html_safe + setting_label(setting, options) +
select_tag("settings[#{setting}]", select_tag("settings[#{setting}]",
options_for_select(choices, Setting.send(setting).to_s), options_for_select(choices, Setting.send(setting).to_s),
options).html_safe options)
end end
def setting_multiselect(setting, choices, options={}) def setting_multiselect(setting, choices, options={})
setting_values = Setting.send(setting) setting_values = Setting.send(setting)
setting_values = [] unless setting_values.is_a?(Array) setting_values = [] unless setting_values.is_a?(Array)
content_tag("label", l(options[:label] || "setting_#{setting}")) + setting_label(setting, options) +
hidden_field_tag("settings[#{setting}][]", '').html_safe + hidden_field_tag("settings[#{setting}][]", '') +
choices.collect do |choice| choices.collect do |choice|
text, value = (choice.is_a?(Array) ? choice : [choice, choice]) text, value = (choice.is_a?(Array) ? choice : [choice, choice])
content_tag( content_tag(
@@ -53,33 +51,32 @@ module SettingsHelper
check_box_tag( check_box_tag(
"settings[#{setting}][]", "settings[#{setting}][]",
value, value,
setting_values.include?(value), Setting.send(setting).include?(value)
:id => nil
) + text.to_s, ) + text.to_s,
:class => (options[:inline] ? 'inline' : 'block') :class => 'block'
) )
end.join.html_safe end.join
end end
def setting_text_field(setting, options={}) def setting_text_field(setting, options={})
setting_label(setting, options).html_safe + setting_label(setting, options) +
text_field_tag("settings[#{setting}]", Setting.send(setting), options).html_safe text_field_tag("settings[#{setting}]", Setting.send(setting), options)
end end
def setting_text_area(setting, options={}) def setting_text_area(setting, options={})
setting_label(setting, options).html_safe + setting_label(setting, options) +
text_area_tag("settings[#{setting}]", Setting.send(setting), options).html_safe text_area_tag("settings[#{setting}]", Setting.send(setting), options)
end end
def setting_check_box(setting, options={}) def setting_check_box(setting, options={})
setting_label(setting, options).html_safe + setting_label(setting, options) +
hidden_field_tag("settings[#{setting}]", 0, :id => nil).html_safe + hidden_field_tag("settings[#{setting}]", 0) +
check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options).html_safe check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options)
end end
def setting_label(setting, options={}) def setting_label(setting, options={})
label = options.delete(:label) label = options.delete(:label)
label != false ? label_tag("settings_#{setting}", l(label || "setting_#{setting}")).html_safe : '' label != false ? content_tag("label", l(label || "setting_#{setting}")) : ''
end end
# Renders a notification field for a Redmine::Notifiable option # Renders a notification field for a Redmine::Notifiable option
@@ -87,20 +84,8 @@ module SettingsHelper
return content_tag(:label, return content_tag(:label,
check_box_tag('settings[notified_events][]', check_box_tag('settings[notified_events][]',
notifiable.name, notifiable.name,
Setting.notified_events.include?(notifiable.name), :id => nil).html_safe + Setting.notified_events.include?(notifiable.name)) +
l_or_humanize(notifiable.name, :prefix => 'label_').html_safe, l_or_humanize(notifiable.name, :prefix => 'label_'),
:class => notifiable.parent.present? ? "parent" : '').html_safe :class => notifiable.parent.present? ? "parent" : '')
end
def cross_project_subtasks_options
options = [
[:label_disabled, ''],
[:label_cross_project_system, 'system'],
[:label_cross_project_tree, 'tree'],
[:label_cross_project_hierarchy, 'hierarchy'],
[:label_cross_project_descendants, 'descendants']
]
options.map {|label, value| [l(label), value.to_s]}
end end
end end
+4 -16
View File
@@ -1,5 +1,3 @@
# encoding: utf-8
#
# Helpers to sort tables using clickable column headers. # Helpers to sort tables using clickable column headers.
# #
# Author: Stuart Rackham <srackham@methods.co.nz>, March 2005. # Author: Stuart Rackham <srackham@methods.co.nz>, March 2005.
@@ -80,20 +78,15 @@ module SortHelper
@criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',') @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',')
end end
# Returns an array of SQL fragments used to sort the list
def to_sql def to_sql
sql = @criteria.collect do |k,o| sql = @criteria.collect do |k,o|
if s = @available_criteria[k] if s = @available_criteria[k]
(o ? s.to_a : s.to_a.collect {|c| append_desc(c)}) (o ? s.to_a : s.to_a.collect {|c| append_desc(c)}).join(', ')
end end
end.flatten.compact end.compact.join(', ')
sql.blank? ? nil : sql sql.blank? ? nil : sql
end end
def to_a
@criteria.dup
end
def add!(key, asc) def add!(key, asc)
@criteria.delete_if {|k,o| k == key} @criteria.delete_if {|k,o| k == key}
@criteria = [[key, asc]] + @criteria @criteria = [[key, asc]] + @criteria
@@ -165,8 +158,7 @@ module SortHelper
# sort_clause. # sort_clause.
# - criteria can be either an array or a hash of allowed keys # - criteria can be either an array or a hash of allowed keys
# #
def sort_update(criteria, sort_name=nil) def sort_update(criteria)
sort_name ||= self.sort_name
@sort_criteria = SortCriteria.new @sort_criteria = SortCriteria.new
@sort_criteria.available_criteria = criteria @sort_criteria.available_criteria = criteria
@sort_criteria.from_param(params[:sort] || session[sort_name]) @sort_criteria.from_param(params[:sort] || session[sort_name])
@@ -187,10 +179,6 @@ module SortHelper
@sort_criteria.to_sql @sort_criteria.to_sql
end end
def sort_criteria
@sort_criteria
end
# Returns a link which sorts by the named column. # Returns a link which sorts by the named column.
# #
# - column is the name of an attribute in the sorted record collection. # - column is the name of an attribute in the sorted record collection.
@@ -217,7 +205,7 @@ module SortHelper
# Add project_id to url_options # Add project_id to url_options
url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id) url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id)
link_to_content_update(h(caption), url_options, :class => css) link_to_content_update(caption, 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
+76 -40
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -56,10 +54,10 @@ module TimelogHelper
end end
def select_hours(data, criteria, value) def select_hours(data, criteria, value)
if value.to_s.empty? if value.to_s.empty?
data.select {|row| row[criteria].blank? } data.select {|row| row[criteria].blank? }
else else
data.select {|row| row[criteria].to_s == value.to_s} data.select {|row| row[criteria].to_s == value.to_s}
end end
end end
@@ -77,7 +75,6 @@ module TimelogHelper
[l(:label_yesterday), 'yesterday'], [l(:label_yesterday), 'yesterday'],
[l(:label_this_week), 'current_week'], [l(:label_this_week), 'current_week'],
[l(:label_last_week), 'last_week'], [l(:label_last_week), 'last_week'],
[l(:label_last_n_weeks, 2), 'last_2_weeks'],
[l(:label_last_n_days, 7), '7_days'], [l(:label_last_n_days, 7), '7_days'],
[l(:label_this_month), 'current_month'], [l(:label_this_month), 'current_month'],
[l(:label_last_month), 'last_month'], [l(:label_last_month), 'last_month'],
@@ -86,10 +83,50 @@ module TimelogHelper
value) value)
end end
def format_criteria_value(criteria_options, value) def entries_to_csv(entries)
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
decimal_separator = l(:general_csv_decimal_separator)
custom_fields = TimeEntryCustomField.find(:all)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
headers = [l(:field_spent_on),
l(:field_user),
l(:field_activity),
l(:field_project),
l(:field_issue),
l(:field_tracker),
l(:field_subject),
l(:field_hours),
l(:field_comments)
]
# Export custom fields
headers += custom_fields.collect(&:name)
csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
# csv lines
entries.each do |entry|
fields = [format_date(entry.spent_on),
entry.user,
entry.activity,
entry.project,
(entry.issue ? entry.issue.id : nil),
(entry.issue ? entry.issue.tracker : nil),
(entry.issue ? entry.issue.subject : nil),
entry.hours.to_s.gsub('.', decimal_separator),
entry.comments
]
fields += custom_fields.collect {|f| show_value(entry.custom_value_for(f)) }
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
end
end
export
end
def format_criteria_value(criteria, value)
if value.blank? if value.blank?
"[#{l(:label_none)}]" l(:label_none)
elsif k = criteria_options[:klass] elsif k = @available_criterias[criteria][:klass]
obj = k.find_by_id(value.to_i) obj = k.find_by_id(value.to_i)
if obj.is_a?(Issue) if obj.is_a?(Issue)
obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}" obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}"
@@ -97,58 +134,57 @@ module TimelogHelper
obj obj
end end
else else
format_value(value, criteria_options[:format]) format_value(value, @available_criterias[criteria][:format])
end end
end end
def report_to_csv(report) def report_to_csv(criterias, periods, hours)
decimal_separator = l(:general_csv_decimal_separator)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# Column headers # Column headers
headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) } headers = criterias.collect {|criteria| l(@available_criterias[criteria][:label]) }
headers += report.periods headers += periods
headers << l(:label_total_time) headers << l(:label_total)
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8( csv << headers.collect {|c| to_utf8(c) }
c.to_s,
l(:general_csv_encoding) ) }
# Content # Content
report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours) report_criteria_to_csv(csv, criterias, periods, hours)
# Total row # Total row
str_total = Redmine::CodesetUtil.from_utf8(l(:label_total_time), l(:general_csv_encoding)) row = [ l(:label_total) ] + [''] * (criterias.size - 1)
row = [ str_total ] + [''] * (report.criteria.size - 1)
total = 0 total = 0
report.periods.each do |period| periods.each do |period|
sum = sum_hours(select_hours(report.hours, report.columns, period.to_s)) sum = sum_hours(select_hours(hours, @columns, period.to_s))
total += sum total += sum
row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '') row << (sum > 0 ? "%.2f" % sum : '')
end end
row << ("%.2f" % total).gsub('.',decimal_separator) row << "%.2f" %total
csv << row csv << row
end end
export export
end end
def report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours, level=0) def report_criteria_to_csv(csv, criterias, periods, hours, level=0)
decimal_separator = l(:general_csv_decimal_separator) hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value|
hours.collect {|h| h[criteria[level]].to_s}.uniq.each do |value| hours_for_value = select_hours(hours, criterias[level], value)
hours_for_value = select_hours(hours, criteria[level], value)
next if hours_for_value.empty? next if hours_for_value.empty?
row = [''] * level row = [''] * level
row << Redmine::CodesetUtil.from_utf8( row << to_utf8(format_criteria_value(criterias[level], value))
format_criteria_value(available_criteria[criteria[level]], value).to_s, row += [''] * (criterias.length - level - 1)
l(:general_csv_encoding) )
row += [''] * (criteria.length - level - 1)
total = 0 total = 0
periods.each do |period| periods.each do |period|
sum = sum_hours(select_hours(hours_for_value, columns, period.to_s)) sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s))
total += sum total += sum
row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '') row << (sum > 0 ? "%.2f" % sum : '')
end end
row << ("%.2f" % total).gsub('.',decimal_separator) row << "%.2f" %total
csv << row csv << row
if criteria.length > level + 1
report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours_for_value, level + 1) if criterias.length > level + 1
report_criteria_to_csv(csv, criterias, periods, hours_for_value, level + 1)
end end
end end
end end
def to_utf8(s)
@ic ||= Iconv.new(l(:general_csv_encoding), 'UTF-8')
begin; @ic.iconv(s.to_s); rescue; s.to_s; end
end
end end
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+14 -7
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -21,9 +19,18 @@ module UsersHelper
def users_status_options_for_select(selected) def users_status_options_for_select(selected)
user_count_by_status = User.count(:group => 'status').to_hash user_count_by_status = User.count(:group => 'status').to_hash
options_for_select([[l(:label_all), ''], options_for_select([[l(:label_all), ''],
["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'], ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", 1],
["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'], ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", 2],
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s) ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", 3]], selected)
end
# Options for the new membership projects combo-box
def options_for_membership_project_select(user, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
options << project_tree_options_for_select(projects) do |p|
{:disabled => (user.projects.include?(p))}
end
options
end end
def user_mail_notification_options(user) def user_mail_notification_options(user)
+9 -17
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,30 +17,24 @@
module VersionsHelper module VersionsHelper
def version_anchor(version) STATUS_BY_CRITERIAS = %w(category tracker status priority author assigned_to)
if @project == version.project
anchor version.name
else
anchor "#{version.project.try(:identifier)}-#{version.name}"
end
end
STATUS_BY_CRITERIAS = %w(tracker status priority author assigned_to category)
def render_issue_status_by(version, criteria) def render_issue_status_by(version, criteria)
criteria = 'tracker' unless STATUS_BY_CRITERIAS.include?(criteria) criteria = 'category' unless STATUS_BY_CRITERIAS.include?(criteria)
h = Hash.new {|k,v| k[v] = [0, 0]} h = Hash.new {|k,v| k[v] = [0, 0]}
begin begin
# Total issue count # Total issue count
Issue.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][0] = s} Issue.count(:group => criteria,
:conditions => ["#{Issue.table_name}.fixed_version_id = ?", version.id]).each {|c,s| h[c][0] = s}
# Open issues count # Open issues count
Issue.open.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][1] = s} Issue.count(:group => criteria,
:include => :status,
:conditions => ["#{Issue.table_name}.fixed_version_id = ? AND #{IssueStatus.table_name}.is_closed = ?", version.id, false]).each {|c,s| h[c][1] = s}
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
# When grouping by an association, Rails throws this exception if there's no result (bug) # When grouping by an association, Rails throws this exception if there's no result (bug)
end end
# Sort with nil keys in last position counts = h.keys.compact.sort.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
counts = h.keys.sort {|a,b| a.nil? ? 1 : (b.nil? ? -1 : a <=> b)}.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
max = counts.collect {|c| c[:total]}.max max = counts.collect {|c| c[:total]}.max
render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max} render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max}
+23 -41
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,63 +18,47 @@
module WatchersHelper module WatchersHelper
def watcher_tag(object, user, options={}) def watcher_tag(object, user, options={})
ActiveSupport::Deprecation.warn "#watcher_tag is deprecated and will be removed in Redmine 3.0. Use #watcher_link instead." content_tag("span", watcher_link(object, user), :class => watcher_css(object))
watcher_link(object, user)
end end
def watcher_link(objects, user) def watcher_link(object, user)
return '' unless user && user.logged? return '' unless user && user.logged? && object.respond_to?('watched_by?')
objects = Array.wrap(objects) watched = object.watched_by?(user)
url = {:controller => 'watchers',
:action => (watched ? 'unwatch' : 'watch'),
:object_type => object.class.to_s.underscore,
:object_id => object.id}
link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
{:url => url},
:href => url_for(url),
:class => (watched ? 'icon icon-fav' : 'icon icon-fav-off'))
watched = Watcher.any_watched?(objects, user)
css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ')
text = watched ? l(:button_unwatch) : l(:button_watch)
url = watch_path(
:object_type => objects.first.class.to_s.underscore,
:object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort)
)
method = watched ? 'delete' : 'post'
link_to text, url, :remote => true, :method => method, :class => css
end end
# Returns the css class used to identify watch links for a given +object+ # Returns the css class used to identify watch links for a given +object+
def watcher_css(objects) def watcher_css(object)
objects = Array.wrap(objects) "#{object.class.to_s.underscore}-#{object.id}-watcher"
id = (objects.size == 1 ? objects.first.id : 'bulk')
"#{objects.first.class.to_s.underscore}-#{id}-watcher"
end end
# Returns a comma separated list of users watching the given object # Returns a comma separated list of users watching the given object
def watchers_list(object) def watchers_list(object)
remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project) remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
content = ''.html_safe
lis = object.watcher_users.collect do |user| lis = object.watcher_users.collect do |user|
s = ''.html_safe s = avatar(user, :size => "16").to_s + link_to_user(user, :class => 'user').to_s
s << avatar(user, :size => "16").to_s
s << link_to_user(user, :class => 'user')
if remove_allowed if remove_allowed
url = {:controller => 'watchers', url = {:controller => 'watchers',
:action => 'destroy', :action => 'destroy',
:object_type => object.class.to_s.underscore, :object_type => object.class.to_s.underscore,
:object_id => object.id, :object_id => object.id,
:user_id => user} :user_id => user}
s << ' ' s += ' ' + link_to_remote(image_tag('delete.png'),
s << link_to(image_tag('delete.png'), url, {:url => url},
:remote => true, :method => 'delete', :class => "delete") :href => url_for(url),
:style => "vertical-align: middle",
:class => "delete")
end end
content << content_tag('li', s, :class => "user-#{user.id}") "<li>#{ s }</li>"
end end
content.present? ? content_tag('ul', content, :class => 'watchers') : content lis.empty? ? "" : "<ul>#{ lis.join("\n") }</ul>"
end
def watchers_checkboxes(object, users, checked=nil)
users.map do |user|
c = checked.nil? ? object.watched_by?(user) : checked
tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil
content_tag 'label', "#{tag} #{h(user)}".html_safe,
:id => "issue_watcher_user_ids_#{user.id}",
:class => "floating"
end.join.html_safe
end end
end end
+2 -4
View File
@@ -1,7 +1,5 @@
# encoding: utf-8 # redMine - project management software
# # Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+4 -12
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,23 +19,17 @@ module WikiHelper
def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0) def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
pages = pages.group_by(&:parent) unless pages.is_a?(Hash) pages = pages.group_by(&:parent) unless pages.is_a?(Hash)
s = ''.html_safe s = ''
if pages.has_key?(parent) if pages.has_key?(parent)
pages[parent].each do |page| pages[parent].each do |page|
attrs = "value='#{page.id}'" attrs = "value='#{page.id}'"
attrs << " selected='selected'" if selected == page attrs << " selected='selected'" if selected == page
indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : '' indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : nil
s << content_tag('option', (indent + h(page.pretty_title)).html_safe, :value => page.id.to_s, :selected => selected == page) + s << "<option #{attrs}>#{indent}#{h page.pretty_title}</option>\n" +
wiki_page_options_for_select(pages, selected, page, level + 1) wiki_page_options_for_select(pages, selected, page, level + 1)
end end
end end
s s
end end
def wiki_page_breadcrumb(page)
breadcrumb(page.ancestors.reverse.collect {|parent|
link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project, :version => nil})
})
end
end end
+1 -23
View File
@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 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
@@ -18,24 +16,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module WorkflowsHelper module WorkflowsHelper
def field_required?(field)
field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
end
def field_permission_tag(permissions, status, field, role)
name = field.is_a?(CustomField) ? field.id.to_s : field
options = [["", ""], [l(:label_readonly), "readonly"]]
options << [l(:label_required), "required"] unless field_required?(field)
html_options = {}
selected = permissions[status.id][name]
hidden = field.is_a?(CustomField) && !field.visible? && !role.custom_fields.to_a.include?(field)
if hidden
options[0][0] = l(:label_hidden)
selected = ''
html_options[:disabled] = true
end
select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, selected), html_options)
end
end end
+49 -184
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it 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,17 +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.
require "digest/md5" require "digest/md5"
require "fileutils"
class Attachment < ActiveRecord::Base class Attachment < ActiveRecord::Base
belongs_to :container, :polymorphic => true belongs_to :container, :polymorphic => true
belongs_to :author, :class_name => "User", :foreign_key => "author_id" belongs_to :author, :class_name => "User", :foreign_key => "author_id"
validates_presence_of :filename, :author validates_presence_of :container, :filename, :author
validates_length_of :filename, :maximum => 255 validates_length_of :filename, :maximum => 255
validates_length_of :disk_filename, :maximum => 255 validates_length_of :disk_filename, :maximum => 255
validates_length_of :description, :maximum => 255
validate :validate_max_file_size
acts_as_event :title => :filename, acts_as_event :title => :filename,
:url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}} :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
@@ -46,25 +43,11 @@ class Attachment < ActiveRecord::Base
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"} "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
cattr_accessor :storage_path cattr_accessor :storage_path
@@storage_path = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, "files") @@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{RAILS_ROOT}/files"
cattr_accessor :thumbnails_storage_path def validate
@@thumbnails_storage_path = File.join(Rails.root, "tmp", "thumbnails") if self.filesize > Setting.attachment_max_size.to_i.kilobytes
errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
before_save :files_to_final_location
after_destroy :delete_from_disk
# Returns an unsaved copy of the attachment
def copy(attributes=nil)
copy = self.class.new
copy.attributes = self.attributes.dup.except("id", "downloads")
copy.attributes = attributes if attributes
copy
end
def validate_max_file_size
if @temp_file && self.filesize > Setting.attachment_max_size.to_i.kilobytes
errors.add(:base, l(:error_attachment_too_big, :max_size => Setting.attachment_max_size.to_i.kilobytes))
end end
end end
@@ -72,14 +55,10 @@ class Attachment < ActiveRecord::Base
unless incoming_file.nil? unless incoming_file.nil?
@temp_file = incoming_file @temp_file = incoming_file
if @temp_file.size > 0 if @temp_file.size > 0
if @temp_file.respond_to?(:original_filename) self.filename = sanitize_filename(@temp_file.original_filename)
self.filename = @temp_file.original_filename self.disk_filename = Attachment.disk_filename(filename)
self.filename.force_encoding("UTF-8") if filename.respond_to?(:force_encoding) self.content_type = @temp_file.content_type.to_s.chomp
end if content_type.blank?
if @temp_file.respond_to?(:content_type)
self.content_type = @temp_file.content_type.to_s.chomp
end
if content_type.blank? && filename.present?
self.content_type = Redmine::MimeType.of(filename) self.content_type = Redmine::MimeType.of(filename)
end end
self.filesize = @temp_file.size self.filesize = @temp_file.size
@@ -91,62 +70,35 @@ class Attachment < ActiveRecord::Base
nil nil
end end
def filename=(arg)
write_attribute :filename, sanitize_filename(arg.to_s)
filename
end
# Copies the temporary file to its final location # Copies the temporary file to its final location
# and computes its MD5 hash # and computes its MD5 hash
def files_to_final_location def before_save
if @temp_file && (@temp_file.size > 0) if @temp_file && (@temp_file.size > 0)
self.disk_directory = target_directory logger.debug("saving '#{self.diskfile}'")
self.disk_filename = Attachment.disk_filename(filename, disk_directory)
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") if logger
path = File.dirname(diskfile)
unless File.directory?(path)
FileUtils.mkdir_p(path)
end
md5 = Digest::MD5.new md5 = Digest::MD5.new
File.open(diskfile, "wb") do |f| File.open(diskfile, "wb") do |f|
if @temp_file.respond_to?(:read) buffer = ""
buffer = "" while (buffer = @temp_file.read(8192))
while (buffer = @temp_file.read(8192)) f.write(buffer)
f.write(buffer) md5.update(buffer)
md5.update(buffer)
end
else
f.write(@temp_file)
md5.update(@temp_file)
end end
end end
self.digest = md5.hexdigest self.digest = md5.hexdigest
end end
@temp_file = nil
# Don't save the content type if it's longer than the authorized length # Don't save the content type if it's longer than the authorized length
if self.content_type && self.content_type.length > 255 if self.content_type && self.content_type.length > 255
self.content_type = nil self.content_type = nil
end end
end end
# Deletes the file from the file system if it's not referenced by other attachments # Deletes file on the disk
def delete_from_disk def after_destroy
if Attachment.where("disk_filename = ? AND id <> ?", disk_filename, id).empty? File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
delete_from_disk!
end
end end
# Returns file's location on disk # Returns file's location on disk
def diskfile def diskfile
File.join(self.class.storage_path, disk_directory.to_s, disk_filename.to_s) "#{@@storage_path}/#{self.disk_filename}"
end
def title
title = filename.to_s
if description.present?
title << " (#{description})"
end
title
end end
def increment_download def increment_download
@@ -154,63 +106,19 @@ class Attachment < ActiveRecord::Base
end end
def project def project
container.try(:project) container.project
end end
def visible?(user=User.current) def visible?(user=User.current)
if container_id container.attachments_visible?(user)
container && container.attachments_visible?(user)
else
author == user
end
end end
def deletable?(user=User.current) def deletable?(user=User.current)
if container_id container.attachments_deletable?(user)
container && container.attachments_deletable?(user)
else
author == user
end
end end
def image? def image?
!!(self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i) self.filename =~ /\.(jpe?g|gif|png)$/i
end
def thumbnailable?
image?
end
# Returns the full path the attachment thumbnail, or nil
# if the thumbnail cannot be generated.
def thumbnail(options={})
if thumbnailable? && readable?
size = options[:size].to_i
if size > 0
# Limit the number of thumbnails per image
size = (size / 50) * 50
# Maximum thumbnail size
size = 800 if size > 800
else
size = Setting.thumbnails_size.to_i
end
size = 100 unless size > 0
target = File.join(self.class.thumbnails_storage_path, "#{id}_#{digest}_#{size}.thumb")
begin
Redmine::Thumbnail.generate(self.diskfile, target, size)
rescue => e
logger.error "An error occured while generating thumbnail for #{disk_filename} to #{target}\nException was: #{e.message}" if logger
return nil
end
end
end
# Deletes all thumbnails
def self.clear_thumbnails
Dir.glob(File.join(thumbnails_storage_path, "*.thumb")).each do |file|
File.delete file
end
end end
def is_text? def is_text?
@@ -226,89 +134,46 @@ class Attachment < ActiveRecord::Base
File.readable?(diskfile) File.readable?(diskfile)
end end
# Returns the attachment token
def token
"#{id}.#{digest}"
end
# Finds an attachment that matches the given token and that has no container
def self.find_by_token(token)
if token.to_s =~ /^(\d+)\.([0-9a-f]+)$/
attachment_id, attachment_digest = $1, $2
attachment = Attachment.where(:id => attachment_id, :digest => attachment_digest).first
if attachment && attachment.container.nil?
attachment
end
end
end
# Bulk attaches a set of files to an object # Bulk attaches a set of files to an object
# #
# Returns a Hash of the results: # Returns a Hash of the results:
# :files => array of the attached files # :files => array of the attached files
# :unsaved => array of the files that could not be attached # :unsaved => array of the files that could not be attached
def self.attach_files(obj, attachments) def self.attach_files(obj, attachments)
result = obj.save_attachments(attachments, User.current) attached = []
obj.attach_saved_attachments if attachments && attachments.is_a?(Hash)
result attachments.each_value do |attachment|
end file = attachment['file']
next unless file && file.size > 0
a = Attachment.create(:container => obj,
:file => file,
:description => attachment['description'].to_s.strip,
:author => User.current)
def self.latest_attach(attachments, filename) if a.new_record?
attachments.sort_by(&:created_on).reverse.detect { obj.unsaved_attachments ||= []
|att| att.filename.downcase == filename.downcase obj.unsaved_attachments << a
} else
end attached << a
end
def self.prune(age=1.day)
Attachment.where("created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age).destroy_all
end
# Moves an existing attachment to its target directory
def move_to_target_directory!
if !new_record? & readable?
src = diskfile
self.disk_directory = target_directory
dest = diskfile
if src != dest && FileUtils.mkdir_p(File.dirname(dest)) && FileUtils.mv(src, dest)
update_column :disk_directory, disk_directory
end end
end end
{:files => attached, :unsaved => obj.unsaved_attachments}
end end
# Moves existing attachments that are stored at the root of the files private
# directory (ie. created before Redmine 2.3) to their target subdirectories
def self.move_from_root_to_target_directory
Attachment.where("disk_directory IS NULL OR disk_directory = ''").find_each do |attachment|
attachment.move_to_target_directory!
end
end
private
# Physically deletes the file from the file system
def delete_from_disk!
if disk_filename.present? && File.exist?(diskfile)
File.delete(diskfile)
end
end
def sanitize_filename(value) def sanitize_filename(value)
# get only the filename, not the whole path # get only the filename, not the whole path
just_filename = value.gsub(/\A.*(\\|\/)/m, '') just_filename = value.gsub(/^.*(\\|\/)/, '')
# NOTE: File.basename doesn't work right with Windows paths on Unix
# INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
# Finally, replace invalid characters with underscore # Finally, replace all non alphanumeric, hyphens or periods with underscore
@filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>\n\r]+/, '_') @filename = just_filename.gsub(/[^\w\.\-]/,'_')
end end
# Returns the subdirectory in which the attachment will be saved # Returns an ASCII or hashed filename
def target_directory def self.disk_filename(filename)
time = created_on || DateTime.now
time.strftime("%Y/%m")
end
# Returns an ASCII or hashed filename that do not
# exists yet in the given subdirectory
def self.disk_filename(filename, directory=nil)
timestamp = DateTime.now.strftime("%y%m%d%H%M%S") timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
ascii = '' ascii = ''
if filename =~ %r{^[a-zA-Z0-9_\.\-]*$} if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
@@ -318,7 +183,7 @@ class Attachment < ActiveRecord::Base
# keep the extension if any # keep the extension if any
ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$} ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
end end
while File.exist?(File.join(storage_path, directory.to_s, "#{timestamp}_#{ascii}")) while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
timestamp.succ! timestamp.succ!
end end
"#{timestamp}_#{ascii}" "#{timestamp}_#{ascii}"
+3 -27
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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
@@ -15,13 +15,7 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Generic exception for when the AuthSource can not be reached
# (eg. can not connect to the LDAP)
class AuthSourceException < Exception; end
class AuthSourceTimeoutException < AuthSourceException; end
class AuthSource < ActiveRecord::Base class AuthSource < ActiveRecord::Base
include Redmine::SubclassFactory
include Redmine::Ciphering include Redmine::Ciphering
has_many :users has_many :users
@@ -48,24 +42,6 @@ class AuthSource < ActiveRecord::Base
write_ciphered_attribute(:account_password, arg) write_ciphered_attribute(:account_password, arg)
end end
def searchable?
false
end
def self.search(q)
results = []
AuthSource.all.each do |source|
begin
if source.searchable?
results += source.search(q)
end
rescue AuthSourceException => e
logger.error "Error while searching users in #{source.name}: #{e.message}"
end
end
results
end
def allow_password_changes? def allow_password_changes?
self.class.allow_password_changes? self.class.allow_password_changes?
end end
@@ -77,7 +53,7 @@ class AuthSource < ActiveRecord::Base
# Try to authenticate a user not yet registered against available sources # Try to authenticate a user not yet registered against available sources
def self.authenticate(login, password) def self.authenticate(login, password)
AuthSource.where(:onthefly_register => true).all.each do |source| AuthSource.find(:all, :conditions => ["onthefly_register=?", true]).each do |source|
begin begin
logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug? logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
attrs = source.authenticate(login, password) attrs = source.authenticate(login, password)
+20 -90
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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,113 +16,47 @@
# 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 'net/ldap' require 'net/ldap'
require 'net/ldap/dn' require 'iconv'
require 'timeout'
class AuthSourceLdap < AuthSource class AuthSourceLdap < AuthSource
validates_presence_of :host, :port, :attr_login validates_presence_of :host, :port, :attr_login
validates_length_of :name, :host, :maximum => 60, :allow_nil => true validates_length_of :name, :host, :maximum => 60, :allow_nil => true
validates_length_of :account, :account_password, :base_dn, :filter, :maximum => 255, :allow_blank => true validates_length_of :account, :account_password, :base_dn, :maximum => 255, :allow_nil => true
validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
validates_numericality_of :port, :only_integer => true validates_numericality_of :port, :only_integer => true
validates_numericality_of :timeout, :only_integer => true, :allow_blank => true
validate :validate_filter
before_validation :strip_ldap_attributes before_validation :strip_ldap_attributes
def initialize(attributes=nil, *args) def after_initialize
super
self.port = 389 if self.port == 0 self.port = 389 if self.port == 0
end end
def authenticate(login, password) def authenticate(login, password)
return nil if login.blank? || password.blank? return nil if login.blank? || password.blank?
attrs = get_user_dn(login)
with_timeout do if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
attrs = get_user_dn(login, password) logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password) return attrs.except(:dn)
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
return attrs.except(:dn)
end
end end
rescue Net::LDAP::LdapError => e rescue Net::LDAP::LdapError => text
raise AuthSourceException.new(e.message) raise "LdapError: " + text
end end
# test the connection to the LDAP # test the connection to the LDAP
def test_connection def test_connection
with_timeout do ldap_con = initialize_ldap_con(self.account, self.account_password)
ldap_con = initialize_ldap_con(self.account, self.account_password) ldap_con.open { }
ldap_con.open { } rescue Net::LDAP::LdapError => text
end raise "LdapError: " + text
rescue Net::LDAP::LdapError => e
raise AuthSourceException.new(e.message)
end end
def auth_method_name def auth_method_name
"LDAP" "LDAP"
end end
# Returns true if this source can be searched for users
def searchable?
!account.to_s.include?("$login") && %w(login firstname lastname mail).all? {|a| send("attr_#{a}?")}
end
# Searches the source for users and returns an array of results
def search(q)
q = q.to_s.strip
return [] unless searchable? && q.present?
results = []
search_filter = base_filter & Net::LDAP::Filter.begins(self.attr_login, q)
ldap_con = initialize_ldap_con(self.account, self.account_password)
ldap_con.search(:base => self.base_dn,
:filter => search_filter,
:attributes => ['dn', self.attr_login, self.attr_firstname, self.attr_lastname, self.attr_mail],
:size => 10) do |entry|
attrs = get_user_attributes_from_ldap_entry(entry)
attrs[:login] = AuthSourceLdap.get_attr(entry, self.attr_login)
results << attrs
end
results
rescue Net::LDAP::LdapError => e
raise AuthSourceException.new(e.message)
end
private private
def with_timeout(&block)
timeout = self.timeout
timeout = 20 unless timeout && timeout > 0
Timeout.timeout(timeout) do
return yield
end
rescue Timeout::Error => e
raise AuthSourceTimeoutException.new(e.message)
end
def ldap_filter
if filter.present?
Net::LDAP::Filter.construct(filter)
end
rescue Net::LDAP::LdapError
nil
end
def base_filter
filter = Net::LDAP::Filter.eq("objectClass", "*")
if f = ldap_filter
filter = filter & f
end
filter
end
def validate_filter
if filter.present? && ldap_filter.nil?
errors.add(:filter, :invalid)
end
end
def strip_ldap_attributes def strip_ldap_attributes
[:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr| [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr|
write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil? write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil?
@@ -166,18 +100,14 @@ class AuthSourceLdap < AuthSource
end end
# Get the user's dn and any attributes for them, given their login # Get the user's dn and any attributes for them, given their login
def get_user_dn(login, password) def get_user_dn(login)
ldap_con = nil ldap_con = initialize_ldap_con(self.account, self.account_password)
if self.account && self.account.include?("$login") login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password) object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
else
ldap_con = initialize_ldap_con(self.account, self.account_password)
end
attrs = {} attrs = {}
search_filter = base_filter & Net::LDAP::Filter.eq(self.attr_login, login)
ldap_con.search( :base => self.base_dn, ldap_con.search( :base => self.base_dn,
:filter => search_filter, :filter => object_filter & login_filter,
:attributes=> search_attributes) do |entry| :attributes=> search_attributes) do |entry|
if onthefly_register? if onthefly_register?
+3 -43
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2013 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,43 +16,25 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Board < ActiveRecord::Base class Board < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :project belongs_to :project
has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC" has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC" has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC"
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
acts_as_tree :dependent => :nullify acts_as_list :scope => :project_id
acts_as_list :scope => '(project_id = #{project_id} AND parent_id #{parent_id ? "= #{parent_id}" : "IS NULL"})'
acts_as_watchable acts_as_watchable
validates_presence_of :name, :description validates_presence_of :name, :description
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_length_of :description, :maximum => 255 validates_length_of :description, :maximum => 255
validate :validate_board
scope :visible, lambda {|*args|
includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args))
}
safe_attributes 'name', 'description', 'parent_id', 'move_to'
def visible?(user=User.current) def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_messages, project) !user.nil? && user.allowed_to?(:view_messages, project)
end end
def reload(*args)
@valid_parents = nil
super
end
def to_s def to_s
name name
end end
def valid_parents
@valid_parents ||= project.boards - self_and_descendants
end
def reset_counters! def reset_counters!
self.class.reset_counters!(id) self.class.reset_counters!(id)
end end
@@ -65,26 +47,4 @@ class Board < ActiveRecord::Base
" last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})", " last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})",
["id = ?", board_id]) ["id = ?", board_id])
end end
def self.board_tree(boards, parent_id=nil, level=0)
tree = []
boards.select {|board| board.parent_id == parent_id}.sort_by(&:position).each do |board|
tree << [board, level]
tree += board_tree(boards, board.id, level+1)
end
if block_given?
tree.each do |board, level|
yield board, level
end
end
tree
end
protected
def validate_board
if parent_id && parent_id_changed?
errors.add(:parent_id, :invalid) unless valid_parents.include?(parent)
end
end
end end

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