Compare commits

..

332 Commits

Author SHA1 Message Date
Eric Davis
99ea01ec93 Added test coverage for Redmine::Plugin.add_hook 2008-07-28 17:30:32 -07:00
Eric Davis
e87140a719 More unit tests to cover hook_registered? 2008-07-28 17:12:51 -07:00
Eric Davis
ca8fb4026e More unit tests for the plugins. Fixed bug where a plugin's hook method could return nil. 2008-07-28 17:10:22 -07:00
Eric Davis
e14b86453e Implementing more unit tests for the plugin hooks 2008-07-28 17:02:23 -07:00
Eric Davis
cb485c92ef Added Redmine::Plugin::Hook::Manager.clear_listeners to remove all hook listeners. 2008-07-28 17:01:59 -07:00
Eric Davis
e7309d8c57 Added tests for Redmine::Plugin::Hook::Base 2008-07-28 16:40:18 -07:00
Eric Davis
d6808130dc Added test stubs for testing the Plugin API 2008-07-28 16:26:46 -07:00
Eric Davis
c5242b4386 Merge branch 'master' into plugin-hooks
Conflicts:

	lib/redmine/plugin.rb
2008-07-28 16:14:10 -07:00
Jean-Philippe Lang
b91bdf8798 Fixed: tokens not escaped in highlight_tokens regexp (#1702).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1709 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-28 21:11:49 +00:00
Jean-Philippe Lang
b26e4932a2 Smaller font in context menu.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1708 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-28 18:01:05 +00:00
Jean-Philippe Lang
c3f9575eaf Adds category to the issue context menu (#1684).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1707 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-28 18:00:30 +00:00
Jean-Philippe Lang
198a8c602d Adds support for wiki links with anchor (#1647).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1706 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-28 17:20:31 +00:00
Jean-Philippe Lang
2dbc3d2943 Adds Trac-Like anchors on wiki headings (#1647).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1705 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-28 17:08:16 +00:00
Jean-Philippe Lang
b20281f151 Follows r1703.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1704 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-27 19:18:35 +00:00
Jean-Philippe Lang
3a4855d070 Activity provider example in sample plugin.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1703 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-27 19:10:56 +00:00
Jean-Philippe Lang
d05bcda2ba Adds #activity_provider shortcut method to the plugin API.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1702 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-27 18:38:31 +00:00
Jean-Philippe Lang
a774c5c48b Activity refactoring.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1701 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-27 17:54:09 +00:00
Jean-Philippe Lang
1721376542 Fixes tests (r1693).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1700 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-27 10:23:07 +00:00
Jean-Philippe Lang
ec7d135930 Adds child_pages macro for wiki pages (#528).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1699 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-26 12:54:54 +00:00
Jean-Philippe Lang
60d066f943 Wiki page hierarchy (#528). Parent page can be assigned on Rename screen.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1698 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-26 11:46:24 +00:00
Jean-Philippe Lang
b68fd4c04b When moving an issue to another project, reassign it to the category with same name if any (#1653).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1697 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-26 09:27:07 +00:00
Jean-Philippe Lang
6ddea3396b Adds estimated hours to issue filters (#1678).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1696 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-26 09:05:26 +00:00
Jean-Philippe Lang
9f92554319 Redirect user to the previous page after logging in (#1679).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1695 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-26 08:46:33 +00:00
Eric Davis
6615002df2 Added Number helper 2008-07-23 21:04:56 -07:00
Eric Davis
08058e6a02 Added documentation 2008-07-23 21:04:51 -07:00
Eric Davis
8995245a0c Added Base class for Plugin Hooks. #1296 2008-07-23 21:03:10 -07:00
Eric Davis
04434cd6ef Changed Hook API to use a Manager class. #1296 2008-07-23 21:01:43 -07:00
Eric Davis
fe22ef95a8 Added new hook for the issues_helper.show_details 2008-07-23 17:30:27 -07:00
Eric Davis
355143ca18 Added hooks for the member page. #1147 2008-07-23 17:27:06 -07:00
Eric Davis
5e1bcc6b24 Added support for saving a bulk edit. #1147 2008-07-23 17:27:01 -07:00
Eric Davis
00659ab8c5 Added hooks to issue_edit, issue_bulk_edit, and issue_show. #1147 2008-07-23 17:26:53 -07:00
Eric Davis
404e6164cb Adding Redmine::Plugin::Hook class to register and use hooks
#1147
2008-07-23 17:26:43 -07:00
Jean-Philippe Lang
5564dfbbd5 TOC rendered as an unordered list.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1693 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-22 19:40:47 +00:00
Jean-Philippe Lang
a17b62b455 Fixes hard-coded table names in queries.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1692 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-22 19:07:43 +00:00
Jean-Philippe Lang
d84d38983a Adds boolean and list custom fields for time entries as criteria on timelog report.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1691 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-22 19:06:13 +00:00
Jean-Philippe Lang
f54c2d812d Adds custom fields to the time entries csv export.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1690 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-22 19:02:40 +00:00
Jean-Philippe Lang
898fac293b Adds custom fields on time entries (#772).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1689 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-22 18:52:00 +00:00
Jean-Philippe Lang
590a829a06 Removed unused exception definition (r1678).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1688 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-22 18:24:40 +00:00
Jean-Philippe Lang
a9932e3dbd Adds mailto link on the user administration list (#1670).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1687 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-22 17:59:45 +00:00
Jean-Philippe Lang
9b579de9e2 Appends the filename to the attachment url so that clients that ignore content-disposition http header get the real filename (#1649).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1686 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-22 17:55:19 +00:00
Jean-Philippe Lang
8a7bfc72b2 Move VersionsController#download to AttachmentsController.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1685 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-22 17:20:02 +00:00
Jean-Philippe Lang
aaca2c50e5 Fixed: 'search titles only' box ignored after one search is done on titles only.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1684 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-21 19:19:06 +00:00
Jean-Philippe Lang
9d3dfea1ac Adds username to the password reminder email (#1668).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1683 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-21 19:13:46 +00:00
Jean-Philippe Lang
c39161a6fc Fixed: searchable model can't be loaded if table is not yet created (#1421).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1682 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-20 17:31:11 +00:00
Jean-Philippe Lang
be2b8a62f4 Search engine: display total results count (#906) and count by result type.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1681 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-20 17:26:07 +00:00
Jean-Philippe Lang
83baccb71a Strikethru closed issue links (#1127).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1680 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-20 09:50:33 +00:00
Jean-Philippe Lang
1ddd9bb55b Fixed: dependency on ruby 1.8.7 introduced in r1660 (#1643).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1679 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-20 09:25:30 +00:00
Jean-Philippe Lang
eb1d969237 Improved on-the-fly account creation. If some attributes are missing (eg. not present in the LDAP) or are invalid, the registration form is displayed so that the user is able to fill or fix these attributes.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1678 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-19 10:47:19 +00:00
Jean-Philippe Lang
93201e7386 Fixed: Wiki Linking Fails on News Item Preview (#1661).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1677 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-19 07:29:05 +00:00
Jean-Philippe Lang
324495643b Small fix to gloc error messages translation.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1676 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-19 07:19:55 +00:00
Jean-Philippe Lang
1701e0bd79 Fixed: default configuration can not be loaded for :it, :pt and :ro languages (#1660).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1675 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-17 16:27:29 +00:00
Jean-Philippe Lang
795220a1e6 Adds links to the user page on various views.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1674 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-16 19:33:15 +00:00
Jean-Philippe Lang
aef25a5e32 Fixes r1672.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1673 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-16 19:22:09 +00:00
Jean-Philippe Lang
83385cbca8 Adds timelog link to the issue context menu (#1645).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1672 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-16 19:10:29 +00:00
Jean-Philippe Lang
7c6f191cf5 Fixed: Context menu overwritten by calendar on My Page (#1644).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1671 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-16 18:48:50 +00:00
Jean-Philippe Lang
025581bb28 Fixes boolean custom fields tags (broken by r1592) (#1640).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1668 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-14 17:29:06 +00:00
Jean-Philippe Lang
6d3c0dab01 Javascript fix (#1636).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1667 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-14 15:46:02 +00:00
Jean-Philippe Lang
fc07ba2a99 Clear changesets and changes with raw sql when deleting a repository (#1627).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1666 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-13 21:55:13 +00:00
Jean-Philippe Lang
0eba42423a Set order on wiki pages association.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1665 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-13 21:44:38 +00:00
Jean-Philippe Lang
9ba4075c88 Fixes search tests for Postgresql.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1664 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-13 21:01:38 +00:00
Jean-Philippe Lang
937cee7269 Prevent blank menu caption.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1663 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-13 13:25:37 +00:00
Jean-Philippe Lang
ad497bdcba Admin and Help links at the end of top menu.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1661 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-13 12:25:01 +00:00
Jean-Philippe Lang
7b8a4fc28b Menu mapper: add support for :before, :after and :last options to #push method and add #delete method.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1660 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-13 12:12:58 +00:00
Jean-Philippe Lang
c4eef6314e Menu item caption can be a Proc.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1659 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-13 11:02:42 +00:00
Jean-Philippe Lang
591407c5c8 Adds auto links tests.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1658 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-12 11:12:33 +00:00
Jean-Philippe Lang
4e7336dac9 Fixes engines assets mirroring.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1657 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-12 11:04:41 +00:00
Jean-Philippe Lang
a01a562261 Fixed: Plugin's setting page is broken after upgrading to rails 2.1.0 (#1620).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1656 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-12 10:31:41 +00:00
Jean-Philippe Lang
0b1343834d Translations updates.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1655 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-12 10:28:09 +00:00
Jean-Philippe Lang
c28cbd5790 Adds engines 2.1.0 plugin.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1654 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-12 10:17:14 +00:00
Jean-Philippe Lang
b5444b5fcd Fixed: no :author method error on projects atom feed (#1623).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1653 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-12 09:42:18 +00:00
Jean-Philippe Lang
622b6121f4 Fixes nil error when svn binary version is unknown (#1607).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1652 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-12 09:06:19 +00:00
Jean-Philippe Lang
9ff53f97ee Do not use partial in PDF templates (not supported when using rfpdf with Rails 2.1) (#1619).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1651 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-10 16:23:42 +00:00
Jean-Philippe Lang
d7eb689c74 Fixed: trailing period should not be included in redmine links of type class:id (#1612).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1650 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-10 13:36:28 +00:00
Jean-Philippe Lang
be4cc2f99e Fixed: search engine may reveal private projects (#1613).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1649 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-10 12:31:49 +00:00
Jean-Philippe Lang
de3d5a88e4 Fixes links to entries on the revision view (Rails 2.1 compatibility) (#1600).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1648 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-09 17:25:19 +00:00
Jean-Philippe Lang
bb76561ca6 Fixes Gantt chart with ruby 1.8.7 (#1606).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1647 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-09 17:16:37 +00:00
Jean-Philippe Lang
40efaae6d5 Mail handler: more control over issue attributes (#1110).
Tracker, category and priority attributes can be specified in command line arguments and/or individually specified as overridable by email body keywords.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1643 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-06 16:26:25 +00:00
Jean-Philippe Lang
bfba84d526 Better naming of activity feed if only one kind of event is displayed (#1323).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1642 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-06 13:57:10 +00:00
Jean-Philippe Lang
d4f55a86fd Adds project name to issues feed title (#1323).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1641 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-06 13:47:59 +00:00
Jean-Philippe Lang
8c6c3eab5c Fixes "source:" links URLs (r1617).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1640 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-06 12:43:51 +00:00
Jean-Philippe Lang
bc9a8494d2 Merged CHANGELOG from 0.7 stable.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1639 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-06 12:24:14 +00:00
Jean-Philippe Lang
29fb8db936 Adds MercurialAdapter.client_version and prevent @hg --version@ to be called on each request.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1628 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-05 09:39:00 +00:00
Jean-Philippe Lang
12fbd06c02 Display svn properties in the browser, svn >= 1.5.0 only (#1581).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1627 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-05 08:59:04 +00:00
Jean-Philippe Lang
1e6b8a482a Removes calls to TimeZone#adjust (#1584).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1626 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-05 07:39:07 +00:00
Jean-Philippe Lang
fc42dd2cef Email delivery configuration moved to an unversioned YAML file (config/email.yml, see the sample file) (#1412).
Email delivery is disabled. It's automatically turned on when configuration is found.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1625 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-04 18:55:45 +00:00
Jean-Philippe Lang
5d0b53544c Updated INSTALL doc.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1624 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-04 18:03:43 +00:00
Jean-Philippe Lang
7cdd88a6ce Merged Rails 2.1 compatibility branch.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1623 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-04 17:58:14 +00:00
Jean-Philippe Lang
22558f7709 Fixes test broken by r1610.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1616 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-03 16:22:51 +00:00
Jean-Philippe Lang
9703f576d9 Escapes HTML tags.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1612 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-07-02 17:27:16 +00:00
Jean-Philippe Lang
be57c20cd8 Fixes quick jump to a revision.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1611 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-30 21:52:29 +00:00
Jean-Philippe Lang
72076d391e Fixed: issues always created with default tracker (#1553).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1610 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-30 17:57:53 +00:00
Jean-Philippe Lang
87a16f395a Fixes relative paths to images in wiki_syntax.html (#1218).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1609 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-30 17:35:40 +00:00
Jean-Philippe Lang
ee608f24f4 Fixed: Comment too long message when updating issue (#1550).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1608 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-30 17:31:50 +00:00
Jean-Philippe Lang
b29b39290a Redirects back after clicking watch/unwatch links without javascript (#1337).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1607 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 19:56:20 +00:00
Jean-Philippe Lang
0f5a34b7ab Removes no longer needed helper method (r1605).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1606 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 19:51:14 +00:00
Jean-Philippe Lang
47064c02f1 Makes issue update link work without javascript (#1337).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1605 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 19:50:07 +00:00
Jean-Philippe Lang
5d6e9494ad Translations updates:
* Traditional Chinese (ChunChang Luo)
* Finnish (Jani Tiainen)
* Lithuanian (Sergej Jegorov)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1604 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 12:23:07 +00:00
Jean-Philippe Lang
32fa1a4e90 Do not silently ignore timelog validation failure on issue edit.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1603 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 12:16:58 +00:00
Jean-Philippe Lang
1424b0f215 Addq "please select" to activity select box if no activity is set as default (#937).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1602 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 12:01:20 +00:00
Jean-Philippe Lang
94cf4f258f Wider SVG graphs in repository stats.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1601 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 11:13:10 +00:00
Jean-Philippe Lang
a0f707e54b Fixed: Entourage (and some old client) fails to correctly render notification styles (#1425).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1600 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 09:50:42 +00:00
Jean-Philippe Lang
00593f2f34 Reduces memory usage when importing large git repositories (#1482).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1599 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 09:41:42 +00:00
Jean-Philippe Lang
a40add57de Adds HTML titles to forums related views.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1598 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 09:08:58 +00:00
Jean-Philippe Lang
0344719482 Makes importer work with Trac 0.8.x (#1540).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1597 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 09:03:03 +00:00
Jean-Philippe Lang
22c9a43899 Fixed: associated revisions are displayed in wrong order on issue view (#1546).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1596 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 08:55:37 +00:00
Jean-Philippe Lang
fa88a592fd Fixed #1545: migration on fresh install fails (broken by r1592).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1595 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-29 08:14:13 +00:00
Jean-Philippe Lang
75a5dbd01d Upgraded to Prototype 1.6.0.1.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1594 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-28 11:34:53 +00:00
Jean-Philippe Lang
bd3191f80d New rake task name in comments for creating sessions migration (#1520).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1593 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-27 20:18:23 +00:00
Jean-Philippe Lang
ce6cf66f6c Custom fields refactoring: most of code moved from controllers to models (using new module ActsAsCustomizable).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1592 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-27 20:13:56 +00:00
Jean-Philippe Lang
a4a8b6381e Adds a key in lang files (general_csv_decimal_separator) to set the decimal separator (point or comma) in csv exports (#1372).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1591 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-26 19:46:57 +00:00
Jean-Philippe Lang
864ac367e8 Translations updates:
* Simplified Chinese (chaoqun zou)
* Hungarian (Gábor Takács)
* Russian (Denis Tomashenko)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1590 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-26 19:29:05 +00:00
Jean-Philippe Lang
f79f19f84c Fixed: timelog redirects inappropriately when :back_url is blank (#1524).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1589 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-26 19:11:07 +00:00
Jean-Philippe Lang
0fd9b102be Adds anchor to atom feed messages links.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1588 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-26 19:04:58 +00:00
Jean-Philippe Lang
b3939fb134 Link to view specific file on revision view fails with Subversion repository subdirectory (#1525).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1587 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-26 17:02:09 +00:00
Jean-Philippe Lang
ad7816cea4 Tests: create tmp/test if it doesn't exist.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1586 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-26 14:59:03 +00:00
Jean-Philippe Lang
31e3d180c0 Add project name to cross-project Atom feeds (#1527).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1585 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-26 14:54:45 +00:00
Jean-Philippe Lang
25bba80c9e Adds a simple API and a standalone script that can be used to forward emails from a local or remote email server to Redmine (#1110).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1584 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-25 19:25:28 +00:00
Jean-Philippe Lang
4d6f50d3fe Encoding set to utf8 in example database.yml (#1506).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1583 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-24 18:39:49 +00:00
Jean-Philippe Lang
c97a5efde9 Fixed: private method 'gsub' called for nil:NilClass on activity (#1519).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1582 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-24 16:52:41 +00:00
Jean-Philippe Lang
8474d05e99 Fixes test broken by r1578.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1581 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-24 16:49:51 +00:00
Jean-Philippe Lang
3121e0b5d0 Smaller font size for activity events and search results descriptions.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1580 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-23 17:12:12 +00:00
Jean-Philippe Lang
810ec643f8 Strip pre/code tags content from activity view events.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1579 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-23 17:06:58 +00:00
Jean-Philippe Lang
dc57b06b6c Adds a class ('me') to events of the activity view created by current user.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1578 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-23 17:01:21 +00:00
Jean-Philippe Lang
28c094f50e Turn ftp urls into links (#1514).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1577 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-23 16:51:13 +00:00
Jean-Philippe Lang
0d5b03bab7 Add filters on cross-project issue list for custom fields marked as 'For all projects'.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1576 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-22 15:35:11 +00:00
Jean-Philippe Lang
b025b63111 Hide 'Target version' filter if no version is defined.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1575 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-22 15:20:59 +00:00
Jean-Philippe Lang
b14aa23c8c Revision view: do not display links for deleted files.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1574 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-22 14:40:45 +00:00
Jean-Philippe Lang
c107fee54e Use new image instead of expand.png
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1573 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-22 14:35:56 +00:00
Jean-Philippe Lang
8b6dd3fdcd IMAP: fetch unseen messages only.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1572 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-22 13:14:45 +00:00
Jean-Philippe Lang
8b33565b3e IMAP: Mark emails as Seen.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1571 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-22 12:56:10 +00:00
Jean-Philippe Lang
268165a013 Fixes reply attachments handling.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1570 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-22 12:37:24 +00:00
Jean-Philippe Lang
3d8d4fa0d6 Adds a rake task (redmine:email:receive_imap) to read emails from an IMAP server (#1110).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1569 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-22 12:27:00 +00:00
Jean-Philippe Lang
a01f976b4c Adds basic support for issue creation via email (#1110).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1568 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-22 10:45:03 +00:00
Jean-Philippe Lang
bb1edda6e8 Display issue notes in the activity view (#1509).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1567 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-21 12:32:47 +00:00
Jean-Philippe Lang
ba8a36a39b Removes spaces before colons on issue detail view (#1512).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1566 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-21 10:15:07 +00:00
Jean-Philippe Lang
9cfa233001 Track project and tracker changes in issue history.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1565 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-19 18:52:20 +00:00
Jean-Philippe Lang
0870f6267f Add target version to the issue list context menu.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1564 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-18 20:15:51 +00:00
Jean-Philippe Lang
fda7bcdfb6 Mercurial adapter tests fix (#1469).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1563 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-18 17:02:39 +00:00
Jean-Philippe Lang
af734b1b38 Enable syntax highlight on issues, messages and news (#1473, #1466).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1562 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-18 16:49:40 +00:00
Jean-Philippe Lang
7d143bc3f7 Allow dot in firstnames and lastnames (closes #1426).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1561 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-17 20:14:31 +00:00
Jean-Philippe Lang
062a2d6f5d Adds atom feed on time entries details (#1479).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1560 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-17 20:01:15 +00:00
Jean-Philippe Lang
d991e46f12 Fixed: urls containing @ are parsed as email adress by the wiki formatter (#1456).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1559 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-17 19:27:03 +00:00
Jean-Philippe Lang
3c95f761e6 Ability to remove enumerations (activities, priorities, document categories) that are in use. Associated objects can be reassigned to another value (#1467).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1558 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-17 19:10:54 +00:00
Jean-Philippe Lang
f4e0c77c83 Prevent unwanted textile link parsing at end of line.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1557 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-16 19:37:09 +00:00
Jean-Philippe Lang
0223b87612 RepositoriesController cleanup with rescue_from.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1555 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 16:11:07 +00:00
Jean-Philippe Lang
9737beecc4 Adds a field on the repository view to browse at specific revision (#1443).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1554 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 15:56:47 +00:00
Jean-Philippe Lang
ca6e69ec24 Fixed: view file at given revision with CVS.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1553 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 15:47:28 +00:00
Jean-Philippe Lang
93b3dba926 Makes changes link to entries on the revision view.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1552 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 14:40:05 +00:00
Jean-Philippe Lang
7c1c2e1ba2 Fixes test broken by r1549.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1551 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 14:39:52 +00:00
Jean-Philippe Lang
8a54c455b8 Fixes test broken by r1549.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1550 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 14:34:23 +00:00
Jean-Philippe Lang
5051566e51 Prettier url for changesets (#1443).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1549 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 14:19:06 +00:00
Jean-Philippe Lang
576130f363 Doc update for 0.7.2 release.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1546 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 12:25:48 +00:00
Jean-Philippe Lang
597c1e6c09 Adds links to repository directories in the browser (#1094).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1544 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 11:40:13 +00:00
Jean-Philippe Lang
11e9891425 Fixed: TOC does not remove colorization markups (#1423).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1542 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 11:00:40 +00:00
Jean-Philippe Lang
8b2105e20a Translation updates.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1540 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 09:49:16 +00:00
Jean-Philippe Lang
622798a5ed Close statement handler in Redmine.pm (#1433).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1539 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 09:43:53 +00:00
Jean-Philippe Lang
2b3009d91d Hungarian translation update (closes #1408).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1538 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-15 09:38:02 +00:00
Jean-Philippe Lang
b1a8790a36 File size display with Bazaar repositories (#1149).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1537 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-14 15:44:36 +00:00
Jean-Philippe Lang
34bcd5b80b Fixed: Logtime info lost when there's an error updating an issue (#1400).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1535 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-14 14:10:47 +00:00
Jean-Philippe Lang
846045fd05 Fixed: time entries created with the default activity even if a different one is specified (#1302).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1533 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-14 12:31:32 +00:00
Jean-Philippe Lang
40a9bbe946 CVS: add support for modules names with spaces (#1434).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1527 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-14 11:42:50 +00:00
Jean-Philippe Lang
d2ad4edc86 Fixed: page has no title when adding a project (#1436).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1526 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-14 11:15:57 +00:00
Jean-Philippe Lang
b42b697ffb Fixed: unexpected nil when viewing differences on CVS (#1444).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1525 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-14 10:51:15 +00:00
Jean-Philippe Lang
cc9b8f7878 Fixed: SVG::Graph raises an error when using external stylesheet (#1402).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1524 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-09 20:05:32 +00:00
Jean-Philippe Lang
53b5703981 Slight changes to diff view and style.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1523 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-09 19:15:40 +00:00
Jean-Philippe Lang
efe790a8d3 Removed inconsistent revision numbers on diff view.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1522 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-09 19:02:22 +00:00
Jean-Philippe Lang
19cb6f96f4 Log the user in after registration if account activation is not needed.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1521 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-09 18:59:15 +00:00
Jean-Philippe Lang
80a7486f95 File viewer for attached text files.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1520 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-09 18:40:59 +00:00
Jean-Philippe Lang
aa0beecad0 Move the file viewer to a partial.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1519 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-09 17:02:55 +00:00
Jean-Philippe Lang
e46b56d7fc "New Project" link on Projects page for admin users (#1082).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1518 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 22:45:22 +00:00
Jean-Philippe Lang
0389c60129 Fixed: notextile tag has no effect.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1517 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 20:31:36 +00:00
Jean-Philippe Lang
d77c1d2829 Unified diff viewer for attached files with .patch or .diff extension (#1403).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1516 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 18:26:39 +00:00
Jean-Philippe Lang
e833cab30e Move diff viewer to a partial.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1515 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 18:10:49 +00:00
Jean-Philippe Lang
b78b62df8d SCM browser: ability to download raw unified diffs.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1514 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 16:48:21 +00:00
Jean-Philippe Lang
ec0525d497 Move unified diff parser out of the scm abstract adapter so it can be reused for viewing attached diffs (#1403).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1513 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 16:28:42 +00:00
Jean-Philippe Lang
731d15fe9b Don't search for the changeset if revision identifier is nil.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1512 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 15:47:55 +00:00
Jean-Philippe Lang
0aba4255f5 Don't display the table headers if there is no changeset to display.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1511 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 15:46:15 +00:00
Jean-Philippe Lang
a0d1414606 Filesystem adapter: read files in binary mode.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1510 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 15:44:23 +00:00
Jean-Philippe Lang
5dea200774 Filesystem adapter: negative size is displayed for large files under win32.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1509 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 15:43:32 +00:00
Jean-Philippe Lang
e69b4647f2 Adds Filesystem adapter (patch #1393 by Paul R).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1508 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 15:40:24 +00:00
Jean-Philippe Lang
dfe62d7f51 Ability to disable unused SCM adapters in application settings.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1507 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 14:59:26 +00:00
Jean-Philippe Lang
05cd95987f Fixes tabulation with Firefox 2 on tabular forms.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1506 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-08 10:10:54 +00:00
Jean-Philippe Lang
2e8b2d5e13 Display status change before subject of issue on the activity view otherwise it may be truncated.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1505 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-07 20:06:44 +00:00
Jean-Philippe Lang
383da1e6d6 Fixes functional tests broken by r1501.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1504 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-07 15:14:42 +00:00
Jean-Philippe Lang
d446469f5d Removes constraint on enumerations name (#1384).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1503 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-07 15:08:59 +00:00
Jean-Philippe Lang
042da97f54 Trac importer: read session_attribute table to find user's email and real name (#1340).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1502 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-07 14:55:32 +00:00
Jean-Philippe Lang
956ad0a3f9 Fixed: Reply/Quote Function Newline Issue in Internet Explorer (#1362).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1501 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-07 11:42:37 +00:00
Jean-Philippe Lang
6d5db302ee Subversion adapter: ignore directories with no commit date (#1370).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1500 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-07 10:08:11 +00:00
Jean-Philippe Lang
aa9d04a4a7 Mercurial adapter improvements (patch #1199 by Pierre Paysant-Le Roux).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1499 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-07 09:19:50 +00:00
Jean-Philippe Lang
a6da479a63 Translations updates.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1498 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-07 09:03:20 +00:00
Jean-Philippe Lang
f4aa04b96e Fixes Chinese pdf export when the issue description is too long (#1170).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1497 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-07 08:39:06 +00:00
Jean-Philippe Lang
9f901a0ba2 Fixed: Atom link on saved query does not include query_id (#1390).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1496 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-06 15:20:08 +00:00
Jean-Philippe Lang
a0f83e42e4 Hides the "Replies" heading below a message if there is no reply (#1350).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1495 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-06 15:14:09 +00:00
Jean-Philippe Lang
8aa57058cf Trac importer: improves wiki link conversion (#1287).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1494 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-06 15:02:47 +00:00
Jean-Philippe Lang
a5ee8f8986 Fixed: SVN errors lead to svn username/password being displayed to end users (#1368).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1493 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-06 14:37:49 +00:00
Jean-Philippe Lang
cd824c6ecf Redmine links regexp fix (#1369, url hash turned into a ticket link).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1492 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-04 21:30:12 +00:00
Jean-Philippe Lang
4db45b8ced Fixed: changesets titles should not be multiline in atom feeds (#1356).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1491 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-04 18:13:14 +00:00
Jean-Philippe Lang
735db3dae5 Allow empty cells in wiki tables.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1490 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-04 17:12:59 +00:00
Jean-Philippe Lang
a77fb3b591 Missing strings (r1488).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1489 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-03 18:41:36 +00:00
Jean-Philippe Lang
7042879811 Make the 'duplicates of' relation asymmetric:
* closing a issue will close its duplicates
* closing a duplicate won't close the main issue

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1488 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-03 18:30:29 +00:00
Jean-Philippe Lang
891a71ad47 Fixed: new line at the end of a file is not displayed in diff.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1487 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-02 20:14:06 +00:00
Jean-Philippe Lang
36162c6cf2 Diff: adds some space between 2 changes in the same file and reduces html size.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1486 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-02 19:18:17 +00:00
Jean-Philippe Lang
870e8654b4 Fixed: Atom feeds don't provide author section for repository revisions (#1348).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1485 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-02 18:16:56 +00:00
Jean-Philippe Lang
4cc3ba88a8 Fixed: Link to PDF doesn't work after creating new issue (#1346).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1484 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-02 17:01:02 +00:00
Jean-Philippe Lang
a06d2c41d5 Fixed: Reminder emails shouldn't include archived projects (#1351).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1483 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-06-02 16:59:15 +00:00
Jean-Philippe Lang
50c338b229 Additional test for r1481.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1482 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-30 18:42:56 +00:00
Jean-Philippe Lang
9894a3781e Fixed: browser's accept-language subcodes ignored (#1320).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1481 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-30 18:40:02 +00:00
Jean-Philippe Lang
5d2abb84bd Adds a Reply link to each issue note (#739). Reply is pre-filled with the quoted note.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1480 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-30 17:42:25 +00:00
Jean-Philippe Lang
88dea1a06d Adds multi-levels blockquotes support by using > at the beginning of lines.
Textile is preserved inside quoted text.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1479 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-30 16:35:36 +00:00
Jean-Philippe Lang
4311ecbc04 Fixed: Incorrect weekend definition in Hebrew calendar locale (#1328).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1478 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-30 15:34:53 +00:00
Jean-Philippe Lang
ffea044699 Trac importer: migrate attachments descriptions (#1326).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1477 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-29 19:31:21 +00:00
Jean-Philippe Lang
6ebdf848a5 Fixed: Unable to use angular braces after include word in c code highlighting (#1230).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1476 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-29 19:12:17 +00:00
Jean-Philippe Lang
e1a86106d0 Fixed: GitAdapter#get_rev should use current branch instead of hardwiring @master@ (#1319).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1475 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-29 18:48:00 +00:00
Jean-Philippe Lang
39216f327c Fixed: can not access old projects created with a numeric identifier (#1322).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1473 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-29 17:42:10 +00:00
Jean-Philippe Lang
eb0e218603 Adds new projects atom feed (#1290).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1465 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-27 16:49:18 +00:00
Jean-Philippe Lang
744d866926 Moved ProjectsController#list to ProjectsController#index.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1464 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-26 20:10:32 +00:00
Jean-Philippe Lang
aa87a73e41 No multiline text for textile links.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1463 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-26 19:39:51 +00:00
Jean-Philippe Lang
54138d2b17 Fixed roadmap links to versions (#1297).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1462 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-26 19:12:29 +00:00
Jean-Philippe Lang
d6daeca40a Fixed: IssueController#edit doesn't set default Activity as default (#1302).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1461 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-26 17:12:11 +00:00
Jean-Philippe Lang
b3e5f1a1c3 Fixed zh lang file.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1460 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-25 20:49:07 +00:00
Jean-Philippe Lang
0c81052770 Adds a rake task to send reminders. An email is sent to each user with a list of the issues due in the next days, if any.
See @rake -D redmine:send_reminders@ for options.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1459 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-25 17:31:50 +00:00
Jean-Philippe Lang
d99dc4070a Remove edit step from Status context menu (#1197).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1458 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-25 15:09:42 +00:00
Jean-Philippe Lang
dfcc8e1492 Change projects homepage limit to 255 chars (#663, #1095).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1457 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-25 13:37:29 +00:00
Jean-Philippe Lang
c7d83be613 Test for striked through wiki link (#199).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1456 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-25 13:30:54 +00:00
Jean-Philippe Lang
a92749ef93 Gantt chart: display issues that don't have a due date if they are assigned to a version with a date (#184).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1455 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-25 13:26:21 +00:00
Jean-Philippe Lang
03f0236a6e Prevents NoMethodError on @available_filters.has_key? in query.rb (#1178).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1454 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-25 12:50:33 +00:00
Jean-Philippe Lang
02757f5892 Translations updates.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1453 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-25 12:42:56 +00:00
Jean-Philippe Lang
fbafff8363 Hungarian translation added (#1198 by Gábor Takács).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1452 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-25 12:18:49 +00:00
Jean-Philippe Lang
e8d092e46a Fixed: using '*' as keyword for repository referencing keywords doesn't work when issue id is at the beginning of a line (#1253).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1451 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-25 11:43:20 +00:00
Jean-Philippe Lang
39fc8f38b8 Prevent admin users from making themselves non-administrator (#1276).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1449 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-25 11:22:53 +00:00
Jean-Philippe Lang
7f134a8c67 Prevent admin users from locking their own account (#1276).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1448 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-24 18:42:53 +00:00
Jean-Philippe Lang
0e5edccaf2 Fixed: Issue number display clipped on 'my issues' (#1291).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1447 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-24 18:37:06 +00:00
Jean-Philippe Lang
193b2450f4 Fixed: View differences for individual file of a changeset fails if the subversion repository URL doesn't point to the repository root (#1209, #1262, #1275).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1446 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-24 17:58:34 +00:00
Jean-Philippe Lang
9d4e71adf3 Fixed: error when previewing a new wiki page (#1292) introduced in r1415.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1445 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-23 16:29:40 +00:00
Jean-Philippe Lang
e02e047dd4 Fixed: TypeError in WikiController#destroy_attachment (#1281).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1444 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-22 18:26:43 +00:00
Jean-Philippe Lang
82c12d09e9 Fixed: non member or anonymous permissions change is effective only after an application restart (#1280).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1443 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-22 18:08:54 +00:00
Jean-Philippe Lang
62125cad33 Fixed: encoding problem when adding non-ASCII issue category in the new issue page (#1286).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1442 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-22 17:02:29 +00:00
Jean-Philippe Lang
e5bc1d370a Use display: block; for activity item descriptions.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1441 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-20 20:49:06 +00:00
Jean-Philippe Lang
03308028c9 Slight changes to the search results.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1440 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-20 20:45:53 +00:00
Jean-Philippe Lang
b0be3b95aa Ability to search a project and its subprojects (#1264).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1439 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-20 20:31:04 +00:00
Liwiusz Ociepa
94dbf641ff Memory leak (postgres -> zlib + ssl) has been fixed by apache developers.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1438 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-19 12:41:40 +00:00
Jean-Philippe Lang
82f204d8ac Adds icons on search results.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1436 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-18 16:33:50 +00:00
Jean-Philippe Lang
073818f8bc Ability to search all projects or the projects the user belongs to (#791).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1435 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-18 16:15:22 +00:00
Jean-Philippe Lang
1907c31138 Fixed: bold, italics, underline not working within parentheses (#1225).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1434 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-18 08:24:31 +00:00
Jean-Philippe Lang
439c697237 Fixed: possible error when attachment's filename is non-ASCII (#747, #1243, #1089).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1433 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-17 11:03:43 +00:00
Jean-Philippe Lang
9e225cc63f Fixed: private subprojects are listed on the issues view (#1217).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1432 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-14 18:19:37 +00:00
Jean-Philippe Lang
7ee38a95a0 Fixed: Calendar and Gantt show private subprojects even if current user is not a member of them (#1217).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1431 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-14 18:01:13 +00:00
Jean-Philippe Lang
06e44b8e64 Do not toggle assignable check box when using un/check all permissions links on role form.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1430 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-14 17:26:13 +00:00
Jean-Philippe Lang
4c6b9d9ce5 Fixed: Check All / Uncheck All in Email Settings doesn't work (#1180).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1429 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-14 17:23:40 +00:00
Jean-Philippe Lang
835cc7ed7c Fixed: Redmine::Scm::Adapters::GitAdapter#get_rev ignored GIT_BIN constant (#1207).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1428 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-13 17:54:51 +00:00
Jean-Philippe Lang
75d25b7e61 Fixed: Update issue form: comment field from log time end out of screen (#1227).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1427 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-13 17:51:55 +00:00
Jean-Philippe Lang
77e87eb0ba Fixed: some labels overflow the form box (#1228).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1426 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-13 16:56:34 +00:00
Liwiusz Ociepa
c4560c4f3b Merge changes from branch swistak.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1425 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-13 09:41:19 +00:00
Jean-Philippe Lang
0476669735 Wiki page protection (#851, patch #1146 by Mateo Murphy with slight changes).
New permission added: protect wiki pages. Once a page is protected, it can be edited/renamed/deleted only by users who have this permission.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1415 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-04 15:05:38 +00:00
Jean-Philippe Lang
556df99456 Doc update for 0.7.1 release.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1412 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-04 10:20:16 +00:00
Jean-Philippe Lang
9e5624c362 Prevent "can't convert nil into String" error when :sort_order param is not present.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1410 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-03 15:09:06 +00:00
Jean-Philippe Lang
35321d938b Translations updates (closes #1128, #1129, #1135, #1148, #1163).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1409 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-03 10:41:28 +00:00
Jean-Philippe Lang
251b1f75e9 Fixed: error on Trac import when :due attribute is nil (#1164).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1406 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-02 16:48:09 +00:00
Jean-Philippe Lang
c65d696f41 Fixed: error when using upcase language name in coderay (#1162) (Windows specific).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1405 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-02 15:35:41 +00:00
Jean-Philippe Lang
63951812a1 Split user edit screen into tabs.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1404 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-02 15:21:21 +00:00
Jean-Philippe Lang
7a969dafac Escape HTML comment tags (#1160).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1403 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-02 15:16:17 +00:00
Jean-Philippe Lang
7f8d959171 Show the project hierarchy in the drop down list for new membership on user administration screen.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1401 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-01 13:50:39 +00:00
Jean-Philippe Lang
f705b889d9 Hide the 'Latest projects' box on the welcome screen if there are no projects (#1156).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1400 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-01 13:01:23 +00:00
Jean-Philippe Lang
2d11265ec5 Fixed: private subprojects names are revealed on the project overview (#1152).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1399 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-01 12:56:59 +00:00
Jean-Philippe Lang
6d637ad982 Add Redcloth's :block_markdown_rule to allow horizontal rules in wiki (#967).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1389 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-05-01 08:44:40 +00:00
Jean-Philippe Lang
b2abe48592 Use GET instead of POST on roadmap (#718), gantt and calendar forms.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1388 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-30 12:14:15 +00:00
Jean-Philippe Lang
f1ae453688 Fixed: Updating tickets add a time log with zero hours (#1147).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1385 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-30 09:09:28 +00:00
Jean-Philippe Lang
4525a7429a Fixed: Home, Logout, Login links are absolute (#1122, #1145).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1384 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-30 08:51:12 +00:00
Jean-Philippe Lang
4403e300ff Thai translation added (Gampol Thitinilnithi, #1136).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1383 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-30 08:47:14 +00:00
Jean-Philippe Lang
5f58e2ced2 Fixed: Search for target version of "none" fails with postgres 8.3 (#1134).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1379 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-29 09:57:47 +00:00
Jean-Philippe Lang
05d39eeb35 Changelog update.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1369 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-28 10:26:05 +00:00
Jean-Philippe Lang
e55c1d82e6 Notify project members when a message is posted if they want to receive notifications for everything on the project (#1079).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1368 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-28 09:25:51 +00:00
Jean-Philippe Lang
67e7758185 Translation updates (closes #1123, #1124):
* Spanish (Gumer Coronel)
* Norvegian (Kai Olav Fredriksen)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1367 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-28 08:52:35 +00:00
Jean-Philippe Lang
c72b53a8fa Forces Redmine to use rails 2.0.2 gem when vendor/rails is not present.
If you don't want to upgrade rails gem, freeze Redmine by running the following command in your Redmine directory:

  rake rails:freeze:edge TAG=rel_2-0-2

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1366 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-27 16:34:42 +00:00
Jean-Philippe Lang
a73f68a185 Fixed: Links to repository directories don't work (#1119).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1365 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-27 10:12:15 +00:00
Jean-Philippe Lang
6a3236daea Include subprojects versions on calendar and gantt (#1116).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1364 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-27 08:20:53 +00:00
Jean-Philippe Lang
ffbdc6b25b Postgresql 8.3 compatibility fix (#834).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1363 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-26 17:56:26 +00:00
Jean-Philippe Lang
64474802f9 Fixes custom field filters behaviour (#1078).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1362 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-26 16:55:24 +00:00
Jean-Philippe Lang
a6311a9603 Estimated time recognizes improved time formats (#1092).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1361 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-26 11:59:51 +00:00
Jean-Philippe Lang
1d570a40ff Fixed: ActiveRecord::StaleObjectError exception on closing a set of circular duplicate issues (#1105).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1360 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-26 10:54:46 +00:00
Jean-Philippe Lang
76b92fb999 Warn user that subprojects are also deleted when deleting a project (#1111) and add a checkbox to confirm the deletion.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1359 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-26 10:20:48 +00:00
Jean-Philippe Lang
a9f86444fc Fixed: Modules selection not remembered when new project creation fails (#1109).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1358 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-26 09:49:16 +00:00
Jean-Philippe Lang
596de073f9 Commits per author graph: remove email adress in usernames (#1066).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1357 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-24 18:35:56 +00:00
Jean-Philippe Lang
2b412e9e07 Fixed: trying to preview a new issue raises an exception (closes #984).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1356 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-24 17:35:04 +00:00
Jean-Philippe Lang
76b8d3eff2 CVS duplicate key violation fix (#996, #1098).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1355 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-24 17:28:55 +00:00
Jean-Philippe Lang
1a4f81163d Redirected user to where he is coming from after logging hours (#1062).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1354 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-16 17:27:53 +00:00
Jean-Philippe Lang
7061f73708 ApplicationHelperTest fix.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1353 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-15 20:27:10 +00:00
Jean-Philippe Lang
277bee62eb Issue list shows all tickets when going back to a global query inside a project (closes #1055).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1352 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-14 19:21:58 +00:00
Jean-Philippe Lang
741ddfb50b Hide export links in print media css.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1351 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-14 19:13:22 +00:00
Jean-Philippe Lang
0329094f01 Include macro can include a page of another project wiki using !{{include(projectname:Foo)}} (#1052).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1350 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-13 16:22:55 +00:00
Jean-Philippe Lang
e644435715 Add css class for ticket changes (#1032).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1349 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-13 13:24:03 +00:00
Jean-Philippe Lang
97fe797ad3 Replace closing html tags with html entity (#910).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1348 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-13 12:45:17 +00:00
Jean-Philippe Lang
8b9fb29d95 Translation updates (closes #1010, #1017, #1047):
* Finnish (Antti Perkiömäki)
* Spanish (Gumer Coronel)
* Czech (Maxim Krušina)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1347 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-13 12:25:22 +00:00
Jean-Philippe Lang
56683967da Left align filter buttons (closes #872) and reduce filters spacing.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1346 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-13 11:18:09 +00:00
Jean-Philippe Lang
a340d8c957 Better error message and AR errors in log for failed LDAP on-the-fly user creation (closes #932, #1042).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1345 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-13 09:12:43 +00:00
Jean-Philippe Lang
8eb86396e1 Fixed: Double dot displayed on ticket view when ticket not updated (closes #813).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1344 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-12 17:25:06 +00:00
Jean-Philippe Lang
7aaa538fd9 Fixed: error when browsing an empty Mercurial repository (#1046).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1343 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-12 17:13:17 +00:00
Jean-Philippe Lang
6d2a89142a Add an icon to each event on the activity view.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1342 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-12 16:54:14 +00:00
Jean-Philippe Lang
85f040c536 Fixed: preview fails when updating an issue (broken by r1322). Patch #1027.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1341 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-09 17:52:41 +00:00
Jean-Philippe Lang
a3c89d4f69 Custom fields (list and boolean) can be used as criteria in time report (#1012).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1340 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-09 17:45:39 +00:00
Jean-Philippe Lang
f025b16be7 Fix date range filter js behaviour and css fix for IE.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1339 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-07 17:56:32 +00:00
Jean-Philippe Lang
5d34548539 CSV export added to timelog report (#1009).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1338 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-07 17:18:09 +00:00
Jean-Philippe Lang
fc1a295d8a Redmine.pm doc update
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1337 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-07 17:03:02 +00:00
Jean-Philippe Lang
2bcb782087 Redmine.pm for webdav authentication:
* make Authen::Simple::LDAP module optional
* handle TLS flag set in Redmine

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1336 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-06 17:36:26 +00:00
Jean-Philippe Lang
246e8f67c5 Redmine.pm support for LDAP authentication (patch by Liwiusz Ociepa). Closes #879, #918.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1335 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-06 17:29:09 +00:00
Jean-Philippe Lang
0249ae5f50 Preserve status filter and page number when using lock/unlock/activate links on the users list (closes #998).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1334 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-06 13:15:09 +00:00
Jean-Philippe Lang
beff2c54bc Mercurial: display working directory files sizes unless browsing a specific revision (#999).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1333 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-06 12:22:59 +00:00
Jean-Philippe Lang
db7f890030 Mercurial: Get proper file action on revision (#983).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1332 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-06 12:00:11 +00:00
Jean-Philippe Lang
a7ca01a8de Translation updates:
* Norwegian (Kai Olav Fredriksen)
* German (Thomas Löber)
* Bulgarian (Nikolay Solakov)
* Hebrew (Ficoos Bangaly)
* Russian (Michael Pirogov)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1331 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-06 11:07:16 +00:00
Jean-Philippe Lang
fe74ab8c06 Make the project files list sortable (#997).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1330 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-06 10:53:18 +00:00
Jean-Philippe Lang
154f60edd3 Fix repository browsing at given revision for various scm and add tests for this.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1329 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-06 10:35:55 +00:00
Jean-Philippe Lang
c37abb6415 Inline images alt attribute set to the attachment description.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1328 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-05 18:56:08 +00:00
Jean-Philippe Lang
c6271d8361 Fixed: inline image not displayed when including a wiki page (closes #1001).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1327 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-05 17:39:19 +00:00
Jean-Philippe Lang
4e961f44ef Various timelog report enhancements:
* report can be done using days as columns (#993)
* add a total column to the time report
* replace upper-right links by tabs to switch between details and report
* preserve date range filter when switching between details and report


git-svn-id: http://redmine.rubyforge.org/svn/trunk@1326 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-05 16:40:26 +00:00
Jean-Philippe Lang
8d4aa6f9ad Fixed: single file 'View difference' links do not work because of duplicate slashes in url.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1325 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-03 22:29:58 +00:00
Jean-Philippe Lang
14a2b7e9b5 Verify rev and rev_to params format in RepositoriesController. And turn revision arguments into integers in SubversionAdapter.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1324 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-03 16:50:53 +00:00
Jean-Philippe Lang
f6ce427a00 Display the context menu above and/or to the left of the click if needed (patch by Mike Duchene, closes #960).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1323 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-03 16:38:06 +00:00
Jean-Philippe Lang
7f0aa56119 Fixed: trying to preview a new issue raises an exception with postgresql (close #984).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1322 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-03 13:16:51 +00:00
Jean-Philippe Lang
6348eeaf8a Attachment model clean up: fixed some inconsistent indentation and an inaccurate comment (closes patch #903 by Rocco Stanzione).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1321 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-02 21:30:32 +00:00
Jean-Philippe Lang
4cbe6b626e Accept the following formats for the timelog "hours" field: 1h, 1 h, 1 hour, 2 hours, 30m, 30min, 1h30, 1h30m, 1:30.
Also accept 1,5 for 1.5 hour (closes #975). Note that 1.5 is still equal to 1h30 and not 1h50 (#947).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1320 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-02 19:52:12 +00:00
Jean-Philippe Lang
467f74510e Time report can be done at issue level (closes #970) + timelog views xhtml validation.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1319 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-01 22:42:10 +00:00
Jean-Philippe Lang
043cb37b16 Add predefined date ranges to the time report in the same way as the details view (closes #972). It nows defaults to 'All time'.
This patch also fixes time report periods (columns) computation.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1318 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-01 19:40:40 +00:00
Jean-Philippe Lang
e4da9d6f10 Prevent NoMethodError on nil class if custom_fields params is not present in IssuesController#new (#969).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1317 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-01 17:48:11 +00:00
Jean-Philippe Lang
80a5ad7ff3 Fixed: Boards are not deleted when project is deleted (closes #963).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1316 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-04-01 17:43:20 +00:00
Jean-Philippe Lang
68fe7856c9 Move repetitive calendar include code from views into helper (patch #966 by Peter Suschlik).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1315 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-03-31 21:48:01 +00:00
Jean-Philippe Lang
0d9e419646 Add overflow:auto to the content div (closes #676, #748, #961).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1314 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-03-31 20:51:56 +00:00
Jean-Philippe Lang
da641f4122 Global queries can be saved from the global issue list (follows r1311 and closes #897).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1312 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-03-30 14:20:07 +00:00
Jean-Philippe Lang
287d86e363 Queries can be marked as 'For all projects'. Such queries will be available on all projects and on the global issue list (#897, closes #671).
Only admin users can create/edit queries that are public and for all projects.
Note: this change does not allow to save a query from the global issue list. You have to be inside a project but then you can mark the query as 'For all projects'.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1311 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-03-30 12:29:07 +00:00
Jean-Philippe Lang
faf1f1e812 Fixed: Feed content limit setting has no effect (closes #954).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1310 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-03-30 08:33:04 +00:00
Jean-Philippe Lang
cd64338a7f Fixed: Priorities not ordered when displayed as a filter in issue list (#956).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1309 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-03-30 08:19:56 +00:00
Jean-Philippe Lang
1b8c5d4058 Fixed: can not display attached images inline in message replies.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1308 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-03-29 22:35:04 +00:00
Jean-Philippe Lang
5ccbeba5c2 Use #blank? instead of #empty? in news/show view.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1307 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-03-29 20:03:20 +00:00
389 changed files with 15523 additions and 5534 deletions

View File

@@ -44,7 +44,16 @@ class AccountController < ApplicationController
else
# Authenticate user
user = User.try_to_login(params[:username], params[:password])
if user
if user.nil?
# Invalid credentials
flash.now[:error] = l(:notice_account_invalid_creditentials)
elsif user.new_record?
# Onthefly creation failed, display the registration form to fill/fix attributes
@user = user
session[:auth_source_registration] = {:login => user.login, :auth_source_id => user.auth_source_id }
render :action => 'register'
else
# Valid user
self.logged_user = user
# generate a key and set cookie if autologin
if params[:autologin] && Setting.autologin?
@@ -52,12 +61,8 @@ class AccountController < ApplicationController
cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now }
end
redirect_back_or_default :controller => 'my', :action => 'page'
else
flash.now[:error] = l(:notice_account_invalid_creditentials)
end
end
rescue User::OnTheFlyCreationFailure
flash.now[:error] = 'Redmine could not retrieve the required information from the LDAP to create your account. Please, contact your Redmine administrator.'
end
# Log out current user and redirect to welcome page
@@ -107,43 +112,52 @@ class AccountController < ApplicationController
# User self-registration
def register
redirect_to(home_url) && return unless Setting.self_registration?
redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
if request.get?
session[:auth_source_registration] = nil
@user = User.new(:language => Setting.default_language)
@custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
else
@user = User.new(params[:user])
@user.admin = false
@user.login = params[:user][:login]
@user.status = User::STATUS_REGISTERED
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
@custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x,
:customized => @user,
:value => (params["custom_fields"] ? params["custom_fields"][x.id.to_s] : nil)) }
@user.custom_values = @custom_values
case Setting.self_registration
when '1'
# Email activation
token = Token.new(:user => @user, :action => "register")
if @user.save and token.save
Mailer.deliver_register(token)
flash[:notice] = l(:notice_account_register_done)
redirect_to :action => 'login'
end
when '3'
# Automatic activation
if session[:auth_source_registration]
@user.status = User::STATUS_ACTIVE
@user.login = session[:auth_source_registration][:login]
@user.auth_source_id = session[:auth_source_registration][:auth_source_id]
if @user.save
session[:auth_source_registration] = nil
self.logged_user = @user
flash[:notice] = l(:notice_account_activated)
redirect_to :action => 'login'
redirect_to :controller => 'my', :action => 'account'
end
else
# Manual activation by the administrator
if @user.save
# Sends an email to the administrators
Mailer.deliver_account_activation_request(@user)
flash[:notice] = l(:notice_account_pending)
redirect_to :action => 'login'
@user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
case Setting.self_registration
when '1'
# Email activation
token = Token.new(:user => @user, :action => "register")
if @user.save and token.save
Mailer.deliver_register(token)
flash[:notice] = l(:notice_account_register_done)
redirect_to :action => 'login'
end
when '3'
# Automatic activation
@user.status = User::STATUS_ACTIVE
if @user.save
self.logged_user = @user
flash[:notice] = l(:notice_account_activated)
redirect_to :controller => 'my', :action => 'account'
end
else
# Manual activation by the administrator
if @user.save
# Sends an email to the administrators
Mailer.deliver_account_activation_request(@user)
flash[:notice] = l(:notice_account_pending)
redirect_to :action => 'login'
end
end
end
end

View File

@@ -15,6 +15,8 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'uri'
class ApplicationController < ActionController::Base
before_filter :user_setup, :check_if_login_required, :set_localization
filter_parameter_logging :password
@@ -61,11 +63,11 @@ class ApplicationController < ActionController::Base
def set_localization
User.current.language = nil unless User.current.logged?
lang = begin
if !User.current.language.blank? and GLoc.valid_languages.include? User.current.language.to_sym
if !User.current.language.blank? && GLoc.valid_language?(User.current.language)
User.current.language
elsif request.env['HTTP_ACCEPT_LANGUAGE']
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
if !accept_lang.blank? && (GLoc.valid_language?(accept_lang) || GLoc.valid_language?(accept_lang = accept_lang.split('-').first))
User.current.language = accept_lang
end
end
@@ -77,8 +79,7 @@ class ApplicationController < ActionController::Base
def require_login
if !User.current.logged?
store_location
redirect_to :controller => "account", :action => "login"
redirect_to :controller => "account", :action => "login", :back_url => request.request_uri
return false
end
true
@@ -115,20 +116,16 @@ class ApplicationController < ActionController::Base
end
end
# store current uri in session.
# return to this location by calling redirect_back_or_default
def store_location
session[:return_to_params] = params
end
# move to the last store_location call or to the passed default one
def redirect_back_or_default(default)
if session[:return_to_params].nil?
redirect_to default
else
redirect_to session[:return_to_params]
session[:return_to_params] = nil
back_url = params[:back_url]
if !back_url.blank?
uri = URI.parse(back_url)
# do not redirect user to another host
if uri.relative? || (uri.host == request.host)
redirect_to(back_url) and return
end
end
redirect_to default
end
def render_403

View File

@@ -17,23 +17,40 @@
class AttachmentsController < ApplicationController
layout 'base'
before_filter :find_project, :check_project_privacy
before_filter :find_project
def show
if @attachment.is_diff?
@diff = File.new(@attachment.diskfile, "rb").read
render :action => 'diff'
elsif @attachment.is_text?
@content = File.new(@attachment.diskfile, "rb").read
render :action => 'file'
elsif
download
end
end
def download
@attachment.increment_download if @attachment.container.is_a?(Version)
# images are sent inline
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
:type => @attachment.content_type,
:disposition => (@attachment.image? ? 'inline' : 'attachment')
rescue
# in case the disk file was deleted
render_404
end
private
def find_project
@attachment = Attachment.find(params[:id])
# Show 404 if the filename in the url is wrong
raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename
@project = @attachment.project
rescue
permission = @attachment.container.is_a?(Version) ? :view_files : "view_#{@attachment.container.class.name.underscore.pluralize}".to_sym
allowed = User.current.allowed_to?(permission, @project)
allowed ? true : (User.current.logged? ? render_403 : require_login)
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -39,6 +39,8 @@ class CustomFieldsController < ApplicationController
@custom_field = UserCustomField.new(params[:custom_field])
when "ProjectCustomField"
@custom_field = ProjectCustomField.new(params[:custom_field])
when "TimeEntryCustomField"
@custom_field = TimeEntryCustomField.new(params[:custom_field])
else
redirect_to :action => 'list'
return

View File

@@ -65,15 +65,6 @@ class DocumentsController < ApplicationController
@document.destroy
redirect_to :controller => 'documents', :action => 'index', :project_id => @project
end
def download
@attachment = @document.attachments.find(params[:attachment_id])
@attachment.increment_download
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
:type => @attachment.content_type
rescue
render_404
end
def add_attachment
attachments = attach_files(@document, params[:attachments])

View File

@@ -75,11 +75,20 @@ class EnumerationsController < ApplicationController
end
def destroy
Enumeration.find(params[:id]).destroy
flash[:notice] = l(:notice_successful_delete)
redirect_to :action => 'list'
rescue
flash[:error] = "Unable to delete enumeration"
redirect_to :action => 'list'
@enumeration = Enumeration.find(params[:id])
if !@enumeration.in_use?
# No associated objects
@enumeration.destroy
redirect_to :action => 'index'
elsif params[:reassign_to_id]
if reassign_to = Enumeration.find_by_opt_and_id(@enumeration.opt, params[:reassign_to_id])
@enumeration.destroy(reassign_to)
redirect_to :action => 'index'
end
end
@enumerations = Enumeration.get_values(@enumeration.opt) - [@enumeration]
#rescue
# flash[:error] = 'Unable to delete enumeration'
# redirect_to :action => 'index'
end
end

View File

@@ -19,7 +19,7 @@ class IssuesController < ApplicationController
layout 'base'
menu_item :new_issue, :only => :new
before_filter :find_issue, :only => [:show, :edit, :destroy_attachment]
before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment]
before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
before_filter :find_project, :only => [:new, :update_form, :preview]
before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu]
@@ -43,6 +43,7 @@ class IssuesController < ApplicationController
helper :sort
include SortHelper
include IssuesHelper
helper :timelog
def index
sort_init "#{Issue.table_name}.id", "desc"
@@ -65,7 +66,7 @@ class IssuesController < ApplicationController
:offset => @issue_pages.current.offset
respond_to do |format|
format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
end
@@ -94,14 +95,13 @@ class IssuesController < ApplicationController
end
def show
@custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
@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.reverse! if User.current.wants_comments_in_reverse_order?
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@activities = Enumeration::get_values('ACTI')
@priorities = Enumeration::get_values('IPRI')
@time_entry = TimeEntry.new
respond_to do |format|
format.html { render :template => 'issues/show.rhtml' }
format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
@@ -112,15 +112,18 @@ class IssuesController < ApplicationController
# Add a new issue
# The new issue will be created from an existing one if copy_from parameter is given
def new
@issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
@issue = Issue.new
@issue.copy_from(params[:copy_from]) if params[:copy_from]
@issue.project = @project
@issue.author = User.current
@issue.tracker ||= @project.trackers.find(params[:tracker_id] ? params[:tracker_id] : :first)
# Tracker must be set before custom field values
@issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
if @issue.tracker.nil?
flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
render :nothing => true, :layout => true
return
end
@issue.attributes = params[:issue]
@issue.author = User.current
default_status = IssueStatus.default
unless default_status
@@ -133,22 +136,15 @@ class IssuesController < ApplicationController
if request.get? || request.xhr?
@issue.start_date ||= Date.today
@custom_values = @issue.custom_values.empty? ?
@project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
@issue.custom_values
else
requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
# Check that the user is allowed to apply the requested status
@issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
@custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x,
:customized => @issue,
:value => (params[:custom_fields] ? params[:custom_fields][x.id.to_s] : nil)) }
@issue.custom_values = @custom_values
if @issue.save
attach_files(@issue, params[:attachments])
flash[:notice] = l(:notice_successful_create)
Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
redirect_to :controller => 'issues', :action => 'show', :id => @issue, :project_id => @project
redirect_to :controller => 'issues', :action => 'show', :id => @issue
return
end
end
@@ -162,10 +158,9 @@ class IssuesController < ApplicationController
def edit
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@activities = Enumeration::get_values('ACTI')
@priorities = Enumeration::get_values('IPRI')
@custom_values = []
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@time_entry = TimeEntry.new
@notes = params[:notes]
journal = @issue.init_journal(User.current, @notes)
@@ -177,21 +172,14 @@ class IssuesController < ApplicationController
@issue.attributes = attrs
end
if request.get?
@custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
else
# Update custom fields if user has :edit permission
if @edit_allowed && params[:custom_fields]
@custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
@issue.custom_values = @custom_values
end
if request.post?
@time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
@time_entry.attributes = params[:time_entry]
attachments = attach_files(@issue, params[:attachments])
attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
if @issue.save
if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
# Log spend time
if current_role.allowed_to?(:log_time)
@time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
@time_entry.attributes = params[:time_entry]
@time_entry.save
end
if !journal.new_record?
@@ -207,6 +195,26 @@ class IssuesController < ApplicationController
flash.now[:error] = l(:notice_locking_conflict)
end
def reply
journal = Journal.find(params[:journal_id]) if params[:journal_id]
if journal
user = journal.user
text = journal.notes
else
user = @issue.author
text = @issue.description
end
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.<< "$('notes').value = \"#{content}\";"
page.show 'update'
page << "Form.Element.focus('notes');"
page << "Element.scrollTo('update');"
page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
}
end
# Bulk edit a set of issues
def bulk_edit
if request.post?
@@ -215,7 +223,6 @@ class IssuesController < ApplicationController
assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
unsaved_issue_ids = []
@issues.each do |issue|
journal = issue.init_journal(User.current, params[:notes])
@@ -226,6 +233,9 @@ class IssuesController < ApplicationController
issue.start_date = params[:start_date] unless params[:start_date].blank?
issue.due_date = params[:due_date] unless params[:due_date].blank?
issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
Redmine::Plugin::Hook::Manager.call_hook(:issue_bulk_edit_save, {:params => params, :issue => issue })
# Don't save any change to the issue if the user is not authorized to apply the requested status
if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
# Send notification for each issue (if changed)
@@ -264,6 +274,7 @@ class IssuesController < ApplicationController
new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
unsaved_issue_ids = []
@issues.each do |issue|
issue.init_journal(User.current)
unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
end
if unsaved_issue_ids.empty?
@@ -325,6 +336,7 @@ class IssuesController < ApplicationController
@project = projects.first if projects.size == 1
@can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
:log_time => (@project && User.current.allowed_to?(:log_time, @project)),
:update => (@issue && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && !@allowed_statuses.empty?))),
:move => (@project && User.current.allowed_to?(:move_issues, @project)),
:copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),

View File

@@ -0,0 +1,44 @@
# redMine - project management software
# Copyright (C) 2006-2008 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 MailHandlerController < ActionController::Base
before_filter :check_credential
verify :method => :post,
:only => :index,
:render => { :nothing => true, :status => 405 }
# Submits an incoming email to MailHandler
def index
options = params.dup
email = options.delete(:email)
if MailHandler.receive(email, options)
render :nothing => true, :status => :created
else
render :nothing => true, :status => :unprocessable_entity
end
end
private
def check_credential
User.current = nil
unless Setting.mail_handler_api_enabled? && params[:key] == Setting.mail_handler_api_key
render :nothing => true, :status => 403
end
end
end

View File

@@ -18,7 +18,7 @@
class NewsController < ApplicationController
layout 'base'
before_filter :find_news, :except => [:new, :index, :preview]
before_filter :find_project, :only => :new
before_filter :find_project, :only => [:new, :preview]
before_filter :authorize, :except => [:index, :preview]
before_filter :find_optional_project, :only => :index
accept_key_auth :index

View File

@@ -44,37 +44,36 @@ class ProjectsController < ApplicationController
include RepositoriesHelper
include ProjectsHelper
def index
list
render :action => 'list' unless request.xhr?
end
# Lists visible projects
def list
def index
projects = Project.find :all,
:conditions => Project.visible_by(User.current),
:include => :parent
@project_tree = projects.group_by {|p| p.parent || p}
@project_tree.each_key {|p| @project_tree[p] -= [p]}
respond_to do |format|
format.html {
@project_tree = projects.group_by {|p| p.parent || p}
@project_tree.keys.each {|p| @project_tree[p] -= [p]}
}
format.atom {
render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
:title => "#{Setting.app_title}: #{l(:label_project_latest)}")
}
end
end
# Add a new project
def add
@custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@root_projects = Project.find(:all,
:conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
:order => 'name')
@project = Project.new(params[:project])
if request.get?
@custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
@project.trackers = Tracker.all
@project.is_public = Setting.default_projects_public?
@project.enabled_module_names = Redmine::AccessControl.available_project_modules
else
@project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
@custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
@project.custom_values = @custom_values
@project.enabled_module_names = params[:enabled_modules]
if @project.save
flash[:notice] = l(:notice_successful_create)
@@ -85,7 +84,6 @@ class ProjectsController < ApplicationController
# Show @project
def show
@custom_values = @project.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
@members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
@subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@@ -112,11 +110,10 @@ class ProjectsController < ApplicationController
@root_projects = Project.find(:all,
:conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
:order => 'name')
@custom_fields = IssueCustomField.find(:all)
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@issue_category ||= IssueCategory.new
@member ||= @project.members.new
@trackers = Tracker.all
@custom_values ||= ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
@repository ||= @project.repository
@wiki ||= @project.wiki
end
@@ -124,10 +121,6 @@ class ProjectsController < ApplicationController
# Edit @project
def edit
if request.post?
if params[:custom_fields]
@custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
@project.custom_values = @custom_values
end
@project.attributes = params[:project]
if @project.save
flash[:notice] = l(:notice_successful_update)
@@ -233,91 +226,23 @@ class ProjectsController < ApplicationController
@date_to ||= Date.today + 1
@date_from = @date_to - @days
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
@event_types = %w(issues news files documents changesets wiki_pages messages)
if @project
@event_types.delete('wiki_pages') unless @project.wiki
@event_types.delete('changesets') unless @project.repository
@event_types.delete('messages') unless @project.boards.any?
# only show what the user is allowed to view
@event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
end
@scope = @event_types.select {|t| params["show_#{t}"]}
# default events if none is specified in parameters
@scope = (@event_types - %w(wiki_pages messages))if @scope.empty?
@events = []
if @scope.include?('issues')
cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects))
cond.add(["#{Issue.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
@events += Issue.find(:all, :include => [:project, :author, :tracker], :conditions => cond.conditions)
cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects))
cond.add(["#{Journal.table_name}.journalized_type = 'Issue' AND #{JournalDetail.table_name}.prop_key = 'status_id' AND #{Journal.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
@events += Journal.find(:all, :include => [{:issue => :project}, :details, :user], :conditions => cond.conditions)
end
if @scope.include?('news')
cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_news, :project => @project, :with_subprojects => @with_subprojects))
cond.add(["#{News.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
@events += News.find(:all, :include => [:project, :author], :conditions => cond.conditions)
end
if @scope.include?('files')
cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_files, :project => @project, :with_subprojects => @with_subprojects))
cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
@events += Attachment.find(:all, :select => "#{Attachment.table_name}.*",
:joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
"LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id",
:conditions => cond.conditions)
end
if @scope.include?('documents')
cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects))
cond.add(["#{Document.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
@events += Document.find(:all, :include => :project, :conditions => cond.conditions)
cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects))
cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
@events += Attachment.find(:all, :select => "#{Attachment.table_name}.*",
:joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id",
:conditions => cond.conditions)
end
if @scope.include?('wiki_pages')
select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
"#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
"#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
"#{WikiContent.versioned_table_name}.id"
joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
"LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
"LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"
@activity = Redmine::Activity::Fetcher.new(User.current, :project => @project, :with_subprojects => @with_subprojects)
@activity.scope_select {|t| !params["show_#{t}"].nil?}
@activity.default_scope! if @activity.scope.empty?
cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_wiki_pages, :project => @project, :with_subprojects => @with_subprojects))
cond.add(["#{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?", @date_from, @date_to])
@events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => cond.conditions)
end
if @scope.include?('changesets')
cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_changesets, :project => @project, :with_subprojects => @with_subprojects))
cond.add(["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
@events += Changeset.find(:all, :include => {:repository => :project}, :conditions => cond.conditions)
end
if @scope.include?('messages')
cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_messages, :project => @project, :with_subprojects => @with_subprojects))
cond.add(["#{Message.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to])
@events += Message.find(:all, :include => [{:board => :project}, :author], :conditions => cond.conditions)
end
@events_by_day = @events.group_by(&:event_date)
events = @activity.events(@date_from, @date_to)
respond_to do |format|
format.html { render :layout => false if request.xhr? }
format.atom { render_feed(@events, :title => "#{@project || Setting.app_title}: #{l(:label_activity)}") }
format.html {
@events_by_day = events.group_by(&:event_date)
render :layout => false if request.xhr?
}
format.atom {
title = (@scope.size == 1) ? l("label_#{@scope.first.singularize}_plural") : l(:label_activity)
render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
}
end
end
@@ -382,11 +307,18 @@ class ProjectsController < ApplicationController
@events = []
@project.issues_with_subprojects(@with_subprojects) do
# Issues that have start and due dates
@events += Issue.find(:all,
:order => "start_date, due_date",
:include => [:tracker, :status, :assigned_to, :priority, :project],
:conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
) unless @selected_tracker_ids.empty?
# Issues that don't have a due date but that are assigned to a version with a date
@events += Issue.find(:all,
:order => "start_date, effective_date",
:include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
:conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
) unless @selected_tracker_ids.empty?
@events += Version.find(:all, :include => :project,
:conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
end

View File

@@ -30,13 +30,15 @@ class RepositoriesController < ApplicationController
before_filter :authorize
accept_key_auth :revisions
rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
def edit
@repository = @project.repository
if !@repository
@repository = Repository.factory(params[:repository_scm])
@repository.project = @project
@repository.project = @project if @repository
end
if request.post?
if request.post? && @repository
@repository.attributes = params[:repository]
@repository.save
end
@@ -56,8 +58,6 @@ class RepositoriesController < ApplicationController
# latest changesets
@changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
show_error_not_found unless @entries || @changesets.any?
rescue Redmine::Scm::Adapters::CommandFailed => e
show_error_command_failed(e.message)
end
def browse
@@ -66,18 +66,16 @@ class RepositoriesController < ApplicationController
@entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
else
show_error_not_found and return unless @entries
@properties = @repository.properties(@path, @rev)
render :action => 'browse'
end
rescue Redmine::Scm::Adapters::CommandFailed => e
show_error_command_failed(e.message)
end
def changes
@entry = @repository.scm.entry(@path, @rev)
@entry = @repository.entry(@path, @rev)
show_error_not_found and return unless @entry
@changesets = @repository.changesets_for_path(@path)
rescue Redmine::Scm::Adapters::CommandFailed => e
show_error_command_failed(e.message)
@properties = @repository.properties(@path, @rev)
end
def revisions
@@ -96,13 +94,13 @@ class RepositoriesController < ApplicationController
end
def entry
@entry = @repository.scm.entry(@path, @rev)
@entry = @repository.entry(@path, @rev)
show_error_not_found and return unless @entry
# If the entry is a dir, show the browser
browse and return if @entry.is_dir?
@content = @repository.scm.cat(@path, @rev)
@content = @repository.cat(@path, @rev)
show_error_not_found and return unless @content
if 'raw' == params[:format] || @content.is_binary_data?
# Force the download if it's a binary file
@@ -110,16 +108,12 @@ class RepositoriesController < ApplicationController
else
# Prevent empty lines when displaying a file with Windows style eol
@content.gsub!("\r\n", "\n")
end
rescue Redmine::Scm::Adapters::CommandFailed => e
show_error_command_failed(e.message)
end
end
def annotate
@annotate = @repository.scm.annotate(@path, @rev)
render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
rescue Redmine::Scm::Adapters::CommandFailed => e
show_error_command_failed(e.message)
end
def revision
@@ -137,27 +131,33 @@ class RepositoriesController < ApplicationController
end
rescue ChangesetNotFound
show_error_not_found
rescue Redmine::Scm::Adapters::CommandFailed => e
show_error_command_failed(e.message)
end
def diff
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
@diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
# Save diff type as user preference
if User.current.logged? && @diff_type != User.current.pref[:diff_type]
User.current.pref[:diff_type] = @diff_type
User.current.preference.save
if params[:format] == 'diff'
@diff = @repository.diff(@path, @rev, @rev_to)
show_error_not_found and return unless @diff
filename = "changeset_r#{@rev}"
filename << "_r#{@rev_to}" if @rev_to
send_data @diff.join, :filename => "#{filename}.diff",
:type => 'text/x-patch',
:disposition => 'attachment'
else
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
@diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
# Save diff type as user preference
if User.current.logged? && @diff_type != User.current.pref[:diff_type]
User.current.pref[:diff_type] = @diff_type
User.current.preference.save
end
@cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
unless read_fragment(@cache_key)
@diff = @repository.diff(@path, @rev, @rev_to)
show_error_not_found unless @diff
end
end
@cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
unless read_fragment(@cache_key)
@diff = @repository.diff(@path, @rev, @rev_to, @diff_type)
show_error_not_found unless @diff
end
rescue Redmine::Scm::Adapters::CommandFailed => e
show_error_command_failed(e.message)
end
def stats
@@ -207,8 +207,9 @@ private
render_error l(:error_scm_not_found)
end
def show_error_command_failed(msg)
render_error l(:error_scm_command_failed, msg)
# Handler for Redmine::Scm::Adapters::CommandFailed exception
def show_error_command_failed(exception)
render_error l(:error_scm_command_failed, exception.message)
end
def graph_commits_per_month(repository)
@@ -229,7 +230,7 @@ private
graph = SVG::Graph::Bar.new(
:height => 300,
:width => 500,
:width => 800,
:fields => fields.reverse,
:stack => :side,
:scale_integers => true,
@@ -271,8 +272,8 @@ private
fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
graph = SVG::Graph::BarHorizontal.new(
:height => 300,
:width => 500,
:height => 400,
:width => 800,
:fields => fields,
:stack => :side,
:scale_integers => true,

View File

@@ -29,6 +29,18 @@ class SearchController < ApplicationController
@all_words = params[:all_words] || (params[:submit] ? false : true)
@titles_only = !params[:titles_only].nil?
projects_to_search =
case params[:scope]
when 'all'
nil
when 'my_projects'
User.current.memberships.collect(&:project)
when 'subprojects'
@project ? ([ @project ] + @project.active_children) : nil
else
@project
end
offset = nil
begin; offset = params[:offset].to_time if params[:offset]; rescue; end
@@ -38,16 +50,16 @@ class SearchController < ApplicationController
return
end
if @project
@object_types = %w(issues news documents changesets wiki_pages messages projects)
if projects_to_search.is_a? Project
# don't search projects
@object_types.delete('projects')
# only show what the user is allowed to view
@object_types = %w(issues news documents changesets wiki_pages messages)
@object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
@scope = @object_types.select {|t| params[t]}
@scope = @object_types if @scope.empty?
else
@object_types = @scope = %w(projects)
@object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)}
end
@scope = @object_types.select {|t| params[t]}
@scope = @object_types if @scope.empty?
# extract tokens from the question
# eg. hello "bye bye" => ["hello", "bye bye"]
@@ -60,39 +72,34 @@ class SearchController < ApplicationController
@tokens.slice! 5..-1 if @tokens.size > 5
# strings used in sql like statement
like_tokens = @tokens.collect {|w| "%#{w.downcase}%"}
@results = []
@results_by_type = Hash.new {|h,k| h[k] = 0}
limit = 10
if @project
@scope.each do |s|
@results += s.singularize.camelcase.constantize.search(like_tokens, @project,
:all_words => @all_words,
:titles_only => @titles_only,
:limit => (limit+1),
:offset => offset,
:before => params[:previous].nil?)
end
@results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime}
if params[:previous].nil?
@pagination_previous_date = @results[0].event_datetime if offset && @results[0]
if @results.size > limit
@pagination_next_date = @results[limit-1].event_datetime
@results = @results[0, limit]
end
else
@pagination_next_date = @results[-1].event_datetime if offset && @results[-1]
if @results.size > limit
@pagination_previous_date = @results[-(limit)].event_datetime
@results = @results[-(limit), limit]
end
@scope.each do |s|
r, c = s.singularize.camelcase.constantize.search(like_tokens, projects_to_search,
:all_words => @all_words,
:titles_only => @titles_only,
:limit => (limit+1),
:offset => offset,
:before => params[:previous].nil?)
@results += r
@results_by_type[s] += c
end
@results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime}
if params[:previous].nil?
@pagination_previous_date = @results[0].event_datetime if offset && @results[0]
if @results.size > limit
@pagination_next_date = @results[limit-1].event_datetime
@results = @results[0, limit]
end
else
operator = @all_words ? ' AND ' : ' OR '
@results += Project.find(:all,
:limit => limit,
:conditions => [ (["(#{Project.visible_by(User.current)}) AND (LOWER(name) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort]
) if @scope.include? 'projects'
# if only one project is found, user is redirected to its overview
redirect_to :controller => 'projects', :action => 'show', :id => @results.first and return if @results.size == 1
@pagination_next_date = @results[-1].event_datetime if offset && @results[-1]
if @results.size > limit
@pagination_previous_date = @results[-(limit)].event_datetime
@results = @results[-(limit), limit]
end
end
else
@question = ""

View File

@@ -39,6 +39,7 @@ class SettingsController < ApplicationController
end
@options = {}
@options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] }
@deliveries = ActionMailer::Base.perform_deliveries
end
def plugin
@@ -49,7 +50,7 @@ class SettingsController < ApplicationController
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'plugin', :id => params[:id]
end
@partial = "../../vendor/plugins/#{plugin_id}/app/views/" + @plugin.settings[:partial]
@partial = @plugin.settings[:partial]
@settings = Setting["plugin_#{plugin_id}"]
end
end

View File

@@ -54,8 +54,15 @@ class TimelogController < ApplicationController
}
# Add list and boolean custom fields as available criterias
@project.all_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM custom_values c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = issues.id)",
@project.all_issue_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
# 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
@@ -154,6 +161,14 @@ class TimelogController < ApplicationController
render :layout => !request.xhr?
}
format.atom {
entries = TimeEntry.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))
}
format.csv {
# Export all entries
@entries = TimeEntry.find(:all,
@@ -172,10 +187,9 @@ class TimelogController < ApplicationController
@time_entry.attributes = params[:time_entry]
if request.post? and @time_entry.save
flash[:notice] = l(:notice_successful_update)
redirect_to(params[:back_url] || {:action => 'details', :project_id => @time_entry.project})
redirect_to(params[:back_url].blank? ? {:action => 'details', :project_id => @time_entry.project} : params[:back_url])
return
end
@activities = Enumeration::get_values('ACTI')
end
def destroy

View File

@@ -52,14 +52,11 @@ class UsersController < ApplicationController
def add
if request.get?
@user = User.new(:language => Setting.default_language)
@custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
else
@user = User.new(params[:user])
@user.admin = params[:user][:admin] || false
@user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
@custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
@user.custom_values = @custom_values
if @user.save
Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
flash[:notice] = l(:notice_successful_create)
@@ -71,16 +68,10 @@ class UsersController < ApplicationController
def edit
@user = User.find(params[:id])
if request.get?
@custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @user.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
else
if request.post?
@user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
if params[:custom_fields]
@custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
@user.custom_values = @custom_values
end
if @user.update_attributes(params[:user])
flash[:notice] = l(:notice_successful_update)
# Give a string to redirect_to otherwise it would use status param as the response code
@@ -91,23 +82,20 @@ class UsersController < ApplicationController
@roles = Role.find_all_givable
@projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects
@membership ||= Member.new
@memberships = @user.memberships
end
def edit_membership
@user = User.find(params[:id])
@membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:user => @user)
@membership.attributes = params[:membership]
if request.post? and @membership.save
flash[:notice] = l(:notice_successful_update)
end
redirect_to :action => 'edit', :id => @user and return
@membership.save if request.post?
redirect_to :action => 'edit', :id => @user, :tab => 'memberships'
end
def destroy_membership
@user = User.find(params[:id])
if request.post? and Member.find(params[:membership_id]).destroy
flash[:notice] = l(:notice_successful_update)
end
redirect_to :action => 'edit', :id => @user and return
Member.find(params[:membership_id]).destroy if request.post?
redirect_to :action => 'edit', :id => @user, :tab => 'memberships'
end
end

View File

@@ -37,15 +37,6 @@ class VersionsController < ApplicationController
flash[:error] = "Unable to delete version"
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end
def download
@attachment = @version.attachments.find(params[:attachment_id])
@attachment.increment_download
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
:type => @attachment.content_type
rescue
render_404
end
def destroy_file
@version.attachments.find(params[:attachment_id]).destroy

View File

@@ -23,18 +23,22 @@ class WatchersController < ApplicationController
user = User.current
@watched.add_watcher(user)
respond_to do |format|
format.html { render :text => 'Watcher added.', :layout => true }
format.html { redirect_to :back }
format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
end
rescue RedirectBackError
render :text => 'Watcher added.', :layout => true
end
def remove
user = User.current
@watched.remove_watcher(user)
respond_to do |format|
format.html { render :text => 'Watcher removed.', :layout => true }
format.html { redirect_to :back }
format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
end
rescue RedirectBackError
render :text => 'Watcher removed.', :layout => true
end
private

View File

@@ -21,7 +21,7 @@ class WikiController < ApplicationController
layout 'base'
before_filter :find_wiki, :authorize
verify :method => :post, :only => [:destroy, :destroy_attachment], :redirect_to => { :action => :index }
verify :method => :post, :only => [:destroy, :destroy_attachment, :protect], :redirect_to => { :action => :index }
helper :attachments
include AttachmentsHelper
@@ -48,12 +48,14 @@ class WikiController < ApplicationController
send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
return
end
@editable = editable?
render :action => 'show'
end
# edit an existing page or a new one
def edit
@page = @wiki.find_or_new_page(params[:page])
return render_403 unless editable?
@page.content = WikiContent.new(:page => @page) if @page.new_record?
@content = @page.content_for_version(params[:version])
@@ -82,7 +84,8 @@ class WikiController < ApplicationController
# rename a page
def rename
@page = @wiki.find_page(params[:page])
@page = @wiki.find_page(params[:page])
return render_403 unless editable?
@page.redirect_existing_links = true
# used to display the *original* title if some AR validation errors occur
@original_title = @page.pretty_title
@@ -92,6 +95,12 @@ class WikiController < ApplicationController
end
end
def protect
page = @wiki.find_page(params[:page])
page.update_attribute :protected, params[:protected]
redirect_to :action => 'index', :id => @project, :page => page.title
end
# show page history
def history
@page = @wiki.find_page(params[:page])
@@ -122,6 +131,7 @@ class WikiController < ApplicationController
# remove a wiki page and its history
def destroy
@page = @wiki.find_page(params[:page])
return render_403 unless editable?
@page.destroy if @page
redirect_to :action => 'special', :id => @project, :page => 'Page_index'
end
@@ -137,6 +147,7 @@ class WikiController < ApplicationController
:joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id",
:order => 'title'
@pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
@pages_by_parent_id = @pages.group_by(&:parent_id)
# export wiki to a single html file
when 'export'
@pages = @wiki.pages.find :all, :order => 'title'
@@ -152,19 +163,26 @@ class WikiController < ApplicationController
def preview
page = @wiki.find_page(params[:page])
@attachements = page.attachments if page
# page is nil when previewing a new page
return render_403 unless page.nil? || editable?(page)
if page
@attachements = page.attachments
@previewed = page.content
end
@text = params[:content][:text]
render :partial => 'common/preview'
end
def add_attachment
@page = @wiki.find_page(params[:page])
return render_403 unless editable?
attach_files(@page, params[:attachments])
redirect_to :action => 'index', :page => @page.title
end
def destroy_attachment
@page = @wiki.find_page(params[:page])
return render_403 unless editable?
@page.attachments.find(params[:attachment_id]).destroy
redirect_to :action => 'index', :page => @page.title
end
@@ -178,4 +196,9 @@ private
rescue ActiveRecord::RecordNotFound
render_404
end
# Returns true if the current user is allowed to edit the page, otherwise false
def editable?(page = @page)
page.editable_by?(User.current)
end
end

View File

@@ -15,6 +15,9 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'coderay'
require 'coderay/helpers/file_type'
module ApplicationHelper
include Redmine::WikiFormatting::Macros::Definitions
@@ -38,9 +41,23 @@ module ApplicationHelper
end
def link_to_issue(issue, options={})
options[:class] ||= ''
options[:class] << ' issue'
options[:class] << ' closed' if issue.closed?
link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
end
# Generates a link to an attachment.
# Options:
# * :text - Link text (default to attachment filename)
# * :download - Force download (default: false)
def link_to_attachment(attachment, options={})
text = options.delete(:text) || attachment.filename
action = options.delete(:download) ? 'download' : 'show'
link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
end
def toggle_link(name, id, options={})
onclick = "Element.toggle('#{id}'); "
onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
@@ -48,14 +65,6 @@ module ApplicationHelper
link_to(name, "#", :onclick => onclick)
end
def show_and_goto_link(name, id, options={})
onclick = "Element.show('#{id}'); "
onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
onclick << "Element.scrollTo('#{id}'); "
onclick << "return false;"
link_to(name, "#", options.merge(:onclick => onclick))
end
def image_to_function(name, function, html_options = {})
html_options.symbolize_keys!
tag(:input, html_options.merge({
@@ -80,23 +89,25 @@ module ApplicationHelper
return nil unless time
time = time.to_time if time.is_a?(String)
zone = User.current.time_zone
if time.utc?
local = zone ? zone.adjust(time) : time.getlocal
else
local = zone ? zone.adjust(time.getutc) : time
end
local = zone ? time.in_time_zone(zone) : (time.utc? ? time.utc_to_local : time)
@date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
@time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
end
# Truncates and returns the string as a single line
def truncate_single_line(string, *args)
truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
end
def html_hours(text)
text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
end
def authoring(created, author)
time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
l(:label_added_time_by, author || 'Anonymous', time_tag)
author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
l(:label_added_time_by, author_tag, time_tag)
end
def l_or_humanize(s)
@@ -111,6 +122,15 @@ module ApplicationHelper
l(:actionview_datehelper_select_month_names).split(',')[month-1]
end
def syntax_highlight(name, content)
type = CodeRay::FileType[name]
type ? CodeRay.scan(content, type).html : h(content)
end
def to_path_param(path)
path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
end
def pagination_links_full(paginator, count=nil, options={})
page_param = options.delete(:page_param) || :page
url_param = params.dup
@@ -157,7 +177,8 @@ module ApplicationHelper
end
def breadcrumb(*args)
content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb')
elements = args.flatten
elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
end
def html_title(*args)
@@ -185,7 +206,7 @@ module ApplicationHelper
options = args.last.is_a?(Hash) ? args.pop : {}
case args.size
when 1
obj = nil
obj = options[:object]
text = args.shift
when 2
obj = args.shift
@@ -225,12 +246,12 @@ module ApplicationHelper
case options[:wiki_links]
when :local
# used for local links to html files
format_wiki_link = Proc.new {|project, title| "#{title}.html" }
format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
when :anchor
# used for single-file wiki export
format_wiki_link = Proc.new {|project, title| "##{title}" }
format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
else
format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
end
project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
@@ -256,9 +277,14 @@ module ApplicationHelper
end
if link_project && link_project.wiki
# extract anchor
anchor = nil
if page =~ /^(.+?)\#(.+)$/
page, anchor = $1, $2
end
# check if page exists
wiki_page = link_project.wiki.find_page(page)
link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
:class => ('wiki-page' + (wiki_page ? '' : ' new')))
else
# project or wiki doesn't exist
@@ -293,7 +319,7 @@ module ApplicationHelper
# source:some/file#L120 -> Link to line 120 of the file
# source:some/file@52#L120 -> Link to line 120 of the file's revision 52
# export:some/file -> Force the download of the file
text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
link = nil
if esc.nil?
@@ -301,7 +327,7 @@ module ApplicationHelper
if project && (changeset = project.changesets.find_by_revision(oid))
link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
:class => 'changeset',
:title => truncate(changeset.comments, 100))
:title => truncate_single_line(changeset.comments, 100))
end
elsif sep == '#'
oid = oid.to_i
@@ -340,13 +366,16 @@ module ApplicationHelper
end
when 'commit'
if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, :class => 'changeset', :title => truncate(changeset.comments, 100)
link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
:class => 'changeset',
:title => truncate_single_line(changeset.comments, 100)
end
when 'source', 'export'
if project && project.repository
name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
path, rev, anchor = $1, $3, $5
link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :path => path,
link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
:path => to_path_param(path),
:rev => rev,
:anchor => anchor,
:format => (prefix == 'export' ? 'raw' : nil)},
@@ -428,7 +457,8 @@ module ApplicationHelper
end
def back_url_hidden_field_tag
hidden_field_tag 'back_url', (params[:back_url] || request.env['HTTP_REFERER'])
back_url = params[:back_url] || request.env['HTTP_REFERER']
hidden_field_tag('back_url', back_url) unless back_url.blank?
end
def check_all_links(form_name)

View File

@@ -22,4 +22,8 @@ module AttachmentsHelper
render :partial => 'attachments/links', :locals => {:attachments => attachments, :options => options}
end
end
def to_utf8(str)
str
end
end

View File

@@ -19,43 +19,47 @@ module CustomFieldsHelper
def custom_fields_tabs
tabs = [{:name => 'IssueCustomField', :label => :label_issue_plural},
{:name => 'TimeEntryCustomField', :label => :label_spent_time},
{:name => 'ProjectCustomField', :label => :label_project_plural},
{:name => 'UserCustomField', :label => :label_user_plural}
]
end
# Return custom field html tag corresponding to its format
def custom_field_tag(custom_value)
def custom_field_tag(name, custom_value)
custom_field = custom_value.custom_field
field_name = "custom_fields[#{custom_field.id}]"
field_id = "custom_fields_#{custom_field.id}"
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_id = "#{name}_custom_field_values_#{custom_field.id}"
case custom_field.field_format
when "date"
text_field('custom_value', 'value', :name => field_name, :id => field_id, :size => 10) +
text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
calendar_for(field_id)
when "text"
text_area 'custom_value', 'value', :name => field_name, :id => field_id, :rows => 3, :style => 'width:99%'
text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
when "bool"
check_box 'custom_value', 'value', :name => field_name, :id => field_id
check_box_tag(field_name, '1', custom_value.true?, :id => field_id) + hidden_field_tag(field_name, '0')
when "list"
select 'custom_value', 'value', custom_field.possible_values, { :include_blank => true }, :name => field_name, :id => field_id
blank_option = custom_field.is_required? ?
(custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
'<option></option>'
select_tag(field_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id)
else
text_field 'custom_value', 'value', :name => field_name, :id => field_id
text_field_tag(field_name, custom_value.value, :id => field_id)
end
end
# Return custom field label tag
def custom_field_label_tag(custom_value)
def custom_field_label_tag(name, custom_value)
content_tag "label", custom_value.custom_field.name +
(custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : ""),
:for => "custom_fields_#{custom_value.custom_field.id}",
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
:class => (custom_value.errors.empty? ? nil : "error" )
end
# Return custom field tag with its label tag
def custom_field_tag_with_label(custom_value)
custom_field_label_tag(custom_value) + custom_field_tag(custom_value)
def custom_field_tag_with_label(name, custom_value)
custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
end
# Return a string used to display a custom value

View File

@@ -54,9 +54,15 @@ module IssuesHelper
when 'due_date', 'start_date'
value = format_date(detail.value.to_date) if detail.value
old_value = format_date(detail.old_value.to_date) if detail.old_value
when 'project_id'
p = Project.find_by_id(detail.value) and value = p.name if detail.value
p = Project.find_by_id(detail.old_value) and old_value = p.name if detail.old_value
when 'status_id'
s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
when 'tracker_id'
t = Tracker.find_by_id(detail.value) and value = t.name if detail.value
t = Tracker.find_by_id(detail.old_value) and old_value = t.name if detail.old_value
when 'assigned_to_id'
u = User.find_by_id(detail.value) and value = u.name if detail.value
u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
@@ -80,7 +86,9 @@ module IssuesHelper
when 'attachment'
label = l(:label_attachment)
end
Redmine::Plugin::Hook::Manager.call_hook(:issues_helper_show_details, {:detail => detail, :label => label, :value => value, :old_value => old_value })
label ||= detail.prop_key
value ||= detail.value
old_value ||= detail.old_value
@@ -89,9 +97,9 @@ module IssuesHelper
label = content_tag('strong', label)
old_value = content_tag("i", h(old_value)) if detail.old_value
old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
if detail.property == 'attachment' && !value.blank? && Attachment.find_by_id(detail.prop_key)
if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
# Link to the attachment if it has not been removed
value = link_to(value, :controller => 'attachments', :action => 'download', :id => detail.prop_key)
value = link_to_attachment(a)
else
value = content_tag("i", h(value)) if value
end
@@ -120,6 +128,7 @@ module IssuesHelper
def issues_to_csv(issues, project = nil)
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
decimal_separator = l(:general_csv_decimal_separator)
export = StringIO.new
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
# csv header fields
@@ -142,7 +151,7 @@ module IssuesHelper
]
# 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_custom_fields
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)
@@ -162,7 +171,7 @@ module IssuesHelper
format_date(issue.start_date),
format_date(issue.due_date),
issue.done_ratio,
issue.estimated_hours,
issue.estimated_hours.to_s.gsub('.', decimal_separator),
format_time(issue.created_on),
format_time(issue.updated_on)
]

View File

@@ -19,13 +19,16 @@ module JournalsHelper
def render_notes(journal, options={})
content = ''
editable = journal.editable_by?(User.current)
if editable && !journal.notes.blank?
links = []
links = []
if !journal.notes.blank?
links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes",
{ :controller => 'journals', :action => 'edit', :id => journal },
:title => l(:button_edit))
content << content_tag('div', links.join(' '), :class => 'contextual')
:title => l(:button_edit)) if editable
links << link_to_remote(image_tag('comment.png'),
{ :url => {:controller => 'issues', :action => 'reply', :id => journal.journalized, :journal_id => journal} },
:title => l(:button_reply)) if options[:reply_links]
end
content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty?
content << textilizable(journal, :notes)
content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => (editable ? 'wiki editable' : 'wiki'))
end

View File

@@ -0,0 +1,19 @@
# redMine - project management software
# Copyright (C) 2006-2008 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 MailHandlerHelper
end

View File

@@ -21,12 +21,16 @@ module ProjectsHelper
link_to h(version.name), { :controller => 'versions', :action => 'show', :id => version }, options
end
def format_activity_title(text)
h(truncate_single_line(text, 100))
end
def format_activity_day(date)
date == Date.today ? l(:label_today).titleize : format_date(date)
end
def format_activity_description(text)
h(truncate(text, 250))
h(truncate(text.to_s, 250).gsub(%r{<(pre|code)>.*$}m, '...'))
end
def project_settings_tabs

View File

@@ -15,20 +15,23 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'coderay'
require 'coderay/helpers/file_type'
require 'iconv'
module RepositoriesHelper
def syntax_highlight(name, content)
type = CodeRay::FileType[name]
type ? CodeRay.scan(content, type).html : h(content)
end
def format_revision(txt)
txt.to_s[0,8]
end
def render_properties(properties)
unless properties.nil? || properties.empty?
content = ''
properties.keys.sort.each do |property|
content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>")
end
content_tag('ul', content, :class => 'properties')
end
end
def to_utf8(str)
return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
@encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
@@ -48,10 +51,13 @@ module RepositoriesHelper
end
def scm_select_tag(repository)
container = [[]]
REDMINE_SUPPORTED_SCM.each {|scm| container << ["Repository::#{scm}".constantize.scm_name, scm]}
scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
REDMINE_SUPPORTED_SCM.each do |scm|
scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
end
select_tag('repository_scm',
options_for_select(container, repository.class.name.demodulize),
options_for_select(scm_options, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?),
:onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
)
@@ -95,4 +101,8 @@ module RepositoriesHelper
def bazaar_field_tags(form, repository)
content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
end
def filesystem_field_tags(form, repository)
content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
end
end

View File

@@ -18,7 +18,8 @@
module SearchHelper
def highlight_tokens(text, tokens)
return text unless text && tokens && !tokens.empty?
regexp = Regexp.new "(#{tokens.join('|')})", Regexp::IGNORECASE
re_tokens = tokens.collect {|t| Regexp.escape(t)}
regexp = Regexp.new "(#{re_tokens.join('|')})", Regexp::IGNORECASE
result = ''
text.split(regexp).each_with_index do |words, i|
if result.length > 1200
@@ -35,4 +36,28 @@ module SearchHelper
end
result
end
def type_label(t)
l("label_#{t.singularize}_plural")
end
def project_select_tag
options = [[l(:label_project_all), 'all']]
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.active_children.empty?
options << [@project.name, ''] unless @project.nil?
select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1
end
def render_results_by_type(results_by_type)
links = []
# Sorts types by results count
results_by_type.keys.sort {|a, b| results_by_type[b] <=> results_by_type[a]}.each do |t|
c = results_by_type[t]
next if c == 0
text = "#{type_label(t)} (#{c})"
links << link_to(text, :q => params[:q], :titles_only => params[:title_only], :all_words => params[:all_words], :scope => params[:scope], t => 1)
end
('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
end
end

View File

@@ -21,6 +21,7 @@ module SettingsHelper
{:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication},
{:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking},
{:name => 'notifications', :partial => 'settings/notifications', :label => l(:field_mail_notification)},
{:name => 'mail_handler', :partial => 'settings/mail_handler', :label => l(:label_incoming_emails)},
{:name => 'repositories', :partial => 'settings/repositories', :label => :label_repository_plural}
]
end

View File

@@ -16,6 +16,14 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module TimelogHelper
def activity_collection_for_select_options
activities = Enumeration::get_values('ACTI')
collection = []
collection << [ "--- #{l(:actionview_instancetag_blank_option)} ---", '' ] unless activities.detect(&:is_default)
activities.each { |a| collection << [a.name, a.id] }
collection
end
def select_hours(data, criteria, value)
data.select {|row| row[criteria] == value}
end
@@ -44,6 +52,8 @@ module TimelogHelper
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 = StringIO.new
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
# csv header fields
@@ -57,6 +67,9 @@ module TimelogHelper
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|
@@ -67,9 +80,11 @@ module TimelogHelper
(entry.issue ? entry.issue.id : nil),
(entry.issue ? entry.issue.tracker : nil),
(entry.issue ? entry.issue.subject : nil),
entry.hours,
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

View File

@@ -23,6 +23,20 @@ module UsersHelper
[l(:status_locked), 3]], selected)
end
# Options for the new membership projects combo-box
def projects_options_for_select(projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
projects_by_root = projects.group_by(&:root)
projects_by_root.keys.sort.each do |root|
options << content_tag('option', h(root.name), :value => root.id, :disabled => (!projects.include?(root)))
projects_by_root[root].sort.each do |project|
next if project == root
options << content_tag('option', '&#187; ' + h(project.name), :value => project.id)
end
end
options
end
def change_status_link(user)
url = {:action => 'edit', :id => user, :page => params[:page], :status => params[:status]}
@@ -30,8 +44,14 @@ module UsersHelper
link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :post, :class => 'icon icon-unlock'
elsif user.registered?
link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :post, :class => 'icon icon-unlock'
else
elsif user != User.current
link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :post, :class => 'icon icon-lock'
end
end
def user_settings_tabs
tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
{:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
]
end
end

View File

@@ -17,6 +17,22 @@
module WikiHelper
def render_page_hierarchy(pages, node=nil)
content = ''
if pages[node]
content << "<ul class=\"pages-hierarchy\">\n"
pages[node].each do |page|
content << "<li>"
content << link_to(h(page.pretty_title), {:action => 'index', :page => page.title},
:title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
content << "</li>\n"
end
content << "</ul>\n"
end
content
end
def html_diff(wdiff)
words = wdiff.words.collect{|word| h(word)}
words_add = 0

View File

@@ -26,7 +26,19 @@ class Attachment < ActiveRecord::Base
validates_length_of :disk_filename, :maximum => 255
acts_as_event :title => :filename,
:url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id}}
:url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
acts_as_activity_provider :type => 'files',
:permission => :view_files,
:find_options => {:select => "#{Attachment.table_name}.*",
:joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
"LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id"}
acts_as_activity_provider :type => 'documents',
:permission => :view_documents,
:find_options => {:select => "#{Attachment.table_name}.*",
:joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
cattr_accessor :storage_path
@@storage_path = "#{RAILS_ROOT}/files"
@@ -40,7 +52,7 @@ class Attachment < ActiveRecord::Base
@temp_file = incoming_file
if @temp_file.size > 0
self.filename = sanitize_filename(@temp_file.original_filename)
self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
self.disk_filename = Attachment.disk_filename(filename)
self.content_type = @temp_file.content_type.to_s.chomp
self.filesize = @temp_file.size
end
@@ -68,9 +80,7 @@ class Attachment < ActiveRecord::Base
# Deletes file on the disk
def after_destroy
if self.filename?
File.delete(diskfile) if File.exist?(diskfile)
end
File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
end
# Returns file's location on disk
@@ -90,6 +100,14 @@ class Attachment < ActiveRecord::Base
self.filename =~ /\.(jpe?g|gif|png)$/i
end
def is_text?
Redmine::MimeType.is_type?('text', filename)
end
def is_diff?
self.filename =~ /\.(patch|diff)$/i
end
private
def sanitize_filename(value)
# get only the filename, not the whole path
@@ -100,4 +118,17 @@ private
# Finally, replace all non alphanumeric, hyphens or periods with underscore
@filename = just_filename.gsub(/[^\w\.\-]/,'_')
end
# Returns an ASCII or hashed filename
def self.disk_filename(filename)
df = DateTime.now.strftime("%y%m%d%H%M%S") + "_"
if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
df << filename
else
df << Digest::MD5.hexdigest(filename)
# keep the extension if any
df << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
end
df
end
end

View File

@@ -20,10 +20,7 @@ class AuthSource < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :host, :maximum => 60
validates_length_of :account_password, :maximum => 60, :allow_nil => true
validates_length_of :account, :base_dn, :maximum => 255
validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30
validates_length_of :name, :maximum => 60
def authenticate(login, password)
end

View File

@@ -20,7 +20,10 @@ require 'iconv'
class AuthSourceLdap < AuthSource
validates_presence_of :host, :port, :attr_login
validates_presence_of :attr_firstname, :attr_lastname, :attr_mail, :if => Proc.new { |a| a.onthefly_register? }
validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true
validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true
validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
validates_numericality_of :port, :only_integer => true
def after_initialize
self.port = 389 if self.port == 0

View File

@@ -19,4 +19,8 @@ class Change < ActiveRecord::Base
belongs_to :changeset
validates_presence_of :changeset_id, :action, :path
def relative_path
changeset.repository.relative_path(path)
end
end

View File

@@ -27,9 +27,12 @@ class Changeset < ActiveRecord::Base
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
acts_as_searchable :columns => 'comments',
:include => :repository,
:include => {:repository => :project},
:project_key => "#{Repository.table_name}.project_id",
:date_column => 'committed_on'
acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
:find_options => {:include => {:repository => :project}}
validates_presence_of :repository_id, :revision, :committed_on, :commit_date
validates_uniqueness_of :revision, :scope => :repository_id
@@ -75,7 +78,7 @@ class Changeset < ActiveRecord::Base
if ref_keywords.delete('*')
# find any issue ID in the comments
target_issue_ids = []
comments.scan(%r{([\s\(,-^])#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
comments.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
end

View File

@@ -25,6 +25,11 @@ class CustomValue < ActiveRecord::Base
end
end
# Returns true if the boolean custom value is true
def true?
self.value == '1'
end
protected
def validate
if value.blank?

View File

@@ -20,11 +20,12 @@ class Document < ActiveRecord::Base
belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
has_many :attachments, :as => :container, :dependent => :destroy
acts_as_searchable :columns => ['title', 'description']
acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"},
:author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC")) ? a.author : nil },
:url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}}
acts_as_activity_provider :find_options => {:include => :project}
validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60
end

View File

@@ -23,12 +23,12 @@ class Enumeration < ActiveRecord::Base
validates_presence_of :opt, :name
validates_uniqueness_of :name, :scope => [:opt]
validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
# Single table inheritance would be an option
OPTIONS = {
"IPRI" => :enumeration_issue_priorities,
"DCAT" => :enumeration_doc_categories,
"ACTI" => :enumeration_activities
"IPRI" => {:label => :enumeration_issue_priorities, :model => Issue, :foreign_key => :priority_id},
"DCAT" => {:label => :enumeration_doc_categories, :model => Document, :foreign_key => :category_id},
"ACTI" => {:label => :enumeration_activities, :model => TimeEntry, :foreign_key => :activity_id}
}.freeze
def self.get_values(option)
@@ -40,13 +40,32 @@ class Enumeration < ActiveRecord::Base
end
def option_name
OPTIONS[self.opt]
OPTIONS[self.opt][:label]
end
def before_save
Enumeration.update_all("is_default = #{connection.quoted_false}", {:opt => opt}) if is_default?
end
def objects_count
OPTIONS[self.opt][:model].count(:conditions => "#{OPTIONS[self.opt][:foreign_key]} = #{id}")
end
def in_use?
self.objects_count != 0
end
alias :destroy_without_reassign :destroy
# Destroy the enumeration
# If a enumeration is specified, objects are reassigned
def destroy(reassign_to = nil)
if reassign_to && reassign_to.is_a?(Enumeration)
OPTIONS[self.opt][:model].update_all("#{OPTIONS[self.opt][:foreign_key]} = #{reassign_to.id}", "#{OPTIONS[self.opt][:foreign_key]} = #{id}")
end
destroy_without_reassign
end
def <=>(enumeration)
position <=> enumeration.position
end
@@ -55,13 +74,6 @@ class Enumeration < ActiveRecord::Base
private
def check_integrity
case self.opt
when "IPRI"
raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id])
when "DCAT"
raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id])
when "ACTI"
raise "Can't delete enumeration" if TimeEntry.find(:first, :conditions => ["activity_id=?", self.id])
end
raise "Can't delete enumeration" if self.in_use?
end
end

View File

@@ -28,23 +28,26 @@ class Issue < ActiveRecord::Base
has_many :journals, :as => :journalized, :dependent => :destroy
has_many :attachments, :as => :container, :dependent => :destroy
has_many :time_entries, :dependent => :delete_all
has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :custom_fields, :through => :custom_values
has_and_belongs_to_many :changesets, :order => "revision ASC"
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
acts_as_customizable
acts_as_watchable
acts_as_searchable :columns => ['subject', 'description'], :with => {:journal => :issue}
acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
:include => [:project, :journals],
# sort by id so that limited eager loading doesn't break with postgresql
:order_column => "#{table_name}.id"
acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
:url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}
acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]}
validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status
validates_length_of :subject, :maximum => 255
validates_inclusion_of :done_ratio, :in => 0..100
validates_numericality_of :estimated_hours, :allow_nil => true
validates_associated :custom_values, :on => :update
def after_initialize
if new_record?
@@ -54,6 +57,11 @@ class Issue < ActiveRecord::Base
end
end
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
def available_custom_fields
(project && tracker) ? project.all_issue_custom_fields.select {|c| tracker.custom_fields.include? c } : []
end
def copy_from(arg)
issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
self.attributes = issue.attributes.dup
@@ -71,7 +79,9 @@ class Issue < ActiveRecord::Base
self.relations_to.clear
end
# issue is moved to another project
self.category = nil
# reassign to the category with same name if any
new_category = category.nil? ? nil : new_project.issue_categories.find_by_name(category.name)
self.category = new_category
self.fixed_version = nil
self.project = new_project
end
@@ -168,11 +178,6 @@ class Issue < ActiveRecord::Base
end
end
def custom_value_for(custom_field)
self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
return nil
end
def init_journal(user, notes = "")
@current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
@issue_before_change = self.clone
@@ -225,9 +230,15 @@ class Issue < ActiveRecord::Base
dependencies
end
# Returns an array of the duplicate issues
# Returns an array of issues that duplicate this one
def duplicates
relations.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.other_issue(self)}
relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
end
# Returns the due date or the target due date if any
# Used on gantt chart
def due_before
due_date || (fixed_version ? fixed_version.effective_date : nil)
end
def duration

View File

@@ -25,7 +25,7 @@ class IssueRelation < ActiveRecord::Base
TYPE_PRECEDES = "precedes"
TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 },
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicates, :order => 2 },
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 },
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 },
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 },
}.freeze

View File

@@ -25,17 +25,18 @@ class Journal < ActiveRecord::Base
has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
attr_accessor :indice
acts_as_searchable :columns => 'notes',
:include => :issue,
:project_key => "#{Issue.table_name}.project_id",
:date_column => "#{Issue.table_name}.created_on"
acts_as_event :title => Proc.new {|o| "#{o.issue.tracker.name} ##{o.issue.id}: #{o.issue.subject}" + ((s = o.new_status) ? " (#{s})" : '') },
acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" },
:description => :notes,
:author => :user,
:type => Proc.new {|o| (s = o.new_status) && s.is_closed? ? 'issue-closed' : 'issue-edit' },
:type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' },
:url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}}
acts_as_activity_provider :type => 'issues',
:permission => :view_issues,
:find_options => {:include => [{:issue => :project}, :details, :user],
:conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
" (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
def save
# Do not save an empty journal
(details.empty? && notes.blank?) ? false : super

View File

@@ -16,25 +16,130 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class MailHandler < ActionMailer::Base
class UnauthorizedAction < StandardError; end
class MissingInformation < StandardError; end
attr_reader :email, :user
def self.receive(email, options={})
@@handler_options = options.dup
@@handler_options[:issue] ||= {}
@@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
@@handler_options[:allow_override] ||= []
# Project needs to be overridable if not specified
@@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
super email
end
# Processes incoming emails
# Currently, it only supports adding a note to an existing issue
# by replying to the initial notification message
def receive(email)
# find related issue by parsing the subject
m = email.subject.match %r{\[.*#(\d+)\]}
return unless m
issue = Issue.find_by_id(m[1])
return unless issue
# find user
user = User.find_active(:first, :conditions => {:mail => email.from.first})
return unless user
@email = email
@user = User.find_active(:first, :conditions => {:mail => email.from.first})
unless @user
# Unknown user => the email is ignored
# TODO: ability to create the user's account
logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
return false
end
User.current = @user
dispatch
end
private
ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
def dispatch
if m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
receive_issue_update(m[1].to_i)
else
receive_issue
end
rescue ActiveRecord::RecordInvalid => e
# TODO: send a email to the user
logger.error e.message if logger
false
rescue MissingInformation => e
logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
false
rescue UnauthorizedAction => e
logger.error "MailHandler: unauthorized attempt from #{user}" if logger
false
end
# Creates a new issue
def receive_issue
project = target_project
tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
# check permission
return unless user.allowed_to?(:add_issue_notes, issue.project)
raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
issue.subject = email.subject.chomp
issue.description = email.plain_text_body.chomp
issue.save!
add_attachments(issue)
logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
issue
end
def target_project
# TODO: other ways to specify project:
# * parse the email To field
# * specific project (eg. Setting.mail_handler_target_project)
target = Project.find_by_identifier(get_keyword(:project))
raise MissingInformation.new('Unable to determine target project') if target.nil?
target
end
# Adds a note to an existing issue
def receive_issue_update(issue_id)
issue = Issue.find_by_id(issue_id)
return unless issue
# check permission
raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
# add the note
issue.init_journal(user, email.body.chomp)
issue.save
journal = issue.init_journal(user, email.plain_text_body.chomp)
add_attachments(issue)
issue.save!
logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
journal
end
def add_attachments(obj)
if email.has_attachments?
email.attachments.each do |attachment|
Attachment.create(:container => obj,
:file => attachment,
:author => user,
:content_type => attachment.content_type)
end
end
end
def get_keyword(attr)
if @@handler_options[:allow_override].include?(attr.to_s) && email.plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i
$1.strip
elsif !@@handler_options[:issue][attr].blank?
@@handler_options[:issue][attr]
end
end
end
class TMail::Mail
# Returns body of the first plain text part found if any
def plain_text_body
return @plain_text_body unless @plain_text_body.nil?
p = self.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
plain = p.detect {|c| c.content_type == 'text/plain'}
@plain_text_body = plain.nil? ? self.body : plain.body
end
end

View File

@@ -51,6 +51,15 @@ class Mailer < ActionMailer::Base
:issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
end
def reminder(user, issues, days)
set_language_if_valid user.language
recipients user.mail
subject l(:mail_subject_reminder, issues.size)
body :issues => issues,
:days => days,
:issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'issues.due_date', :sort_order => 'asc')
end
def document_added(document)
redmine_headers 'Project' => document.project.identifier
recipients document.project.recipients
@@ -144,6 +153,30 @@ class Mailer < ActionMailer::Base
(bcc.nil? || bcc.empty?)
super
end
# Sends reminders to issue assignees
# Available options:
# * :days => how many days in the future to remind about (defaults to 7)
# * :tracker => id of tracker for filtering issues (defaults to all trackers)
# * :project => id or identifier of project to process (defaults to all projects)
def self.reminders(options={})
days = options[:days] || 7
project = options[:project] ? Project.find(options[:project]) : nil
tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
s << "#{Issue.table_name}.project_id = #{project.id}" if project
s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
:conditions => s.conditions
).group_by(&:assigned_to)
issues_by_assignee.each do |assignee, issues|
deliver_reminder(assignee, issues, days) unless assignee.nil?
end
end
private
def initialize_defaults(method_name)

View File

@@ -23,14 +23,17 @@ class Message < ActiveRecord::Base
belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id'
acts_as_searchable :columns => ['subject', 'content'],
:include => :board,
:include => {:board, :project},
:project_key => 'project_id',
:date_column => 'created_on'
:date_column => "#{table_name}.created_on"
acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
:description => :content,
:type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'},
:url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id, :id => o.id}}
:url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} :
{:id => o.parent_id, :anchor => "message-#{o.id}"})}
acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]}
attr_protected :locked, :sticky
validates_presence_of :subject, :content
validates_length_of :subject, :maximum => 255

View File

@@ -24,9 +24,10 @@ class News < ActiveRecord::Base
validates_length_of :title, :maximum => 60
validates_length_of :summary, :maximum => 255
acts_as_searchable :columns => ['title', 'description']
acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
acts_as_activity_provider :find_options => {:include => [:project, :author]}
# returns latest news for projects visible by user
def self.latest(user=nil, count=5)
find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")

View File

@@ -22,7 +22,6 @@ class Project < ActiveRecord::Base
has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
has_many :users, :through => :members
has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :enabled_modules, :dependent => :delete_all
has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
@@ -38,7 +37,7 @@ class Project < ActiveRecord::Base
has_many :changesets, :through => :repository
has_one :wiki, :dependent => :destroy
# Custom field for the project issues
has_and_belongs_to_many :custom_fields,
has_and_belongs_to_many :issue_custom_fields,
:class_name => 'IssueCustomField',
:order => "#{CustomField.table_name}.position",
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
@@ -46,18 +45,19 @@ class Project < ActiveRecord::Base
acts_as_tree :order => "name", :counter_cache => true
acts_as_searchable :columns => ['name', 'description'], :project_key => 'id'
acts_as_customizable
acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
:url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}}
:url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
:author => nil
attr_protected :status, :enabled_module_names
validates_presence_of :name, :identifier
validates_uniqueness_of :name, :identifier
validates_associated :custom_values, :on => :update
validates_associated :repository, :wiki
validates_length_of :name, :maximum => 30
validates_length_of :homepage, :maximum => 60
validates_length_of :homepage, :maximum => 255
validates_length_of :identifier, :in => 3..20
validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
@@ -73,9 +73,9 @@ class Project < ActiveRecord::Base
def issues_with_subprojects(include_subprojects=false)
conditions = nil
if include_subprojects && !active_children.empty?
ids = [id] + active_children.collect {|c| c.id}
conditions = ["#{Project.table_name}.id IN (#{ids.join(',')})"]
if include_subprojects
ids = [id] + child_ids
conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
end
conditions ||= ["#{Project.table_name}.id = ?", id]
# Quick and dirty fix for Rails 2 compatibility
@@ -93,6 +93,7 @@ class Project < ActiveRecord::Base
end
def self.visible_by(user=nil)
user ||= User.current
if user && user.admin?
return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
elsif user && user.memberships.any?
@@ -112,16 +113,18 @@ class Project < ActiveRecord::Base
end
if user.admin?
# no restriction
elsif user.logged?
statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id}
statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
elsif Role.anonymous.allowed_to?(permission)
# anonymous user allowed on public project
statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
else
# anonymous user is not authorized
statements << "1=0"
if user.logged?
statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id}
statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
elsif Role.anonymous.allowed_to?(permission)
# anonymous user allowed on public project
statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
else
# anonymous user is not authorized
end
end
statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
end
@@ -143,7 +146,8 @@ class Project < ActiveRecord::Base
end
def to_param
identifier
# id is used for projects with a numeric identifier (compatibility)
@to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
end
def active?
@@ -193,12 +197,12 @@ class Project < ActiveRecord::Base
# Returns an array of all custom fields enabled for project issues
# (explictly associated custom fields and custom fields enabled for all projects)
def custom_fields_for_issues(tracker)
all_custom_fields.select {|c| tracker.custom_fields.include? c }
def all_issue_custom_fields
@all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq
end
def all_custom_fields
@all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
def project
self
end
def <=>(project)

View File

@@ -88,7 +88,7 @@ class Query < ActiveRecord::Base
:date_past => [ ">t-", "<t-", "t-", "t", "w" ],
:string => [ "=", "~", "!", "!~" ],
:text => [ "~", "!~" ],
:integer => [ "=", ">=", "<=" ] }
:integer => [ "=", ">=", "<=", "!*", "*" ] }
cattr_reader :operators_by_filter_type
@@ -152,7 +152,8 @@ class Query < ActiveRecord::Base
"updated_on" => { :type => :date_past, :order => 10 },
"start_date" => { :type => :date, :order => 11 },
"due_date" => { :type => :date, :order => 12 },
"done_ratio" => { :type => :integer, :order => 13 }}
"estimated_hours" => { :type => :integer, :order => 13 },
"done_ratio" => { :type => :integer, :order => 14 }}
user_values = []
user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
@@ -166,29 +167,20 @@ class Query < ActiveRecord::Base
@available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
if project
# project specific filters
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
# project specific filters
unless @project.issue_categories.empty?
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
end
unless @project.versions.empty?
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
end
unless @project.active_children.empty?
@available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
end
@project.all_custom_fields.select(&:is_filter?).each do |field|
case field.field_format
when "text"
options = { :type => :text, :order => 20 }
when "list"
options = { :type => :list_optional, :values => field.possible_values, :order => 20}
when "date"
options = { :type => :date, :order => 20 }
when "bool"
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
else
options = { :type => :string, :order => 20 }
end
@available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
end
# remove category filter if no category defined
@available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
add_custom_fields_filters(@project.all_issue_custom_fields)
else
# global filters for cross project issue list
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
end
@available_filters
end
@@ -227,7 +219,7 @@ class Query < ActiveRecord::Base
end
def label_for(field)
label = @available_filters[field][:name] if @available_filters.has_key?(field)
label = available_filters[field][:name] if available_filters.has_key?(field)
label ||= field.gsub(/\_id$/, "")
end
@@ -235,7 +227,7 @@ class Query < ActiveRecord::Base
return @available_columns if @available_columns
@available_columns = Query.available_columns
@available_columns += (project ?
project.all_custom_fields :
project.all_issue_custom_fields :
IssueCustomField.find(:all, :conditions => {:is_for_all => true})
).collect {|cf| QueryCustomFieldColumn.new(cf) }
end
@@ -265,7 +257,7 @@ class Query < ActiveRecord::Base
def statement
# project/subprojects clause
clause = ''
project_clauses = []
if project && !@project.active_children.empty?
ids = [project.id]
if has_filter?("subproject_id")
@@ -277,17 +269,16 @@ class Query < ActiveRecord::Base
# main project only
else
# all subprojects
ids += project.active_children.collect{|p| p.id}
ids += project.child_ids
end
elsif Setting.display_subprojects_issues?
ids += project.active_children.collect{|p| p.id}
ids += project.child_ids
end
clause << "#{Issue.table_name}.project_id IN (%s)" % ids.join(',')
project_clauses << "#{Issue.table_name}.project_id IN (%s)" % ids.join(',')
elsif project
clause << "#{Issue.table_name}.project_id = %d" % project.id
else
clause << Project.visible_by(User.current)
project_clauses << "#{Issue.table_name}.project_id = %d" % project.id
end
project_clauses << Project.visible_by(User.current)
# filters clauses
filters_clauses = []
@@ -365,8 +356,28 @@ class Query < ActiveRecord::Base
filters_clauses << sql
end if filters and valid?
clause << ' AND ' unless clause.empty?
clause << filters_clauses.join(' AND ') unless filters_clauses.empty?
clause
(project_clauses + filters_clauses).join(' AND ')
end
private
def add_custom_fields_filters(custom_fields)
@available_filters ||= {}
custom_fields.select(&:is_filter?).each do |field|
case field.field_format
when "text"
options = { :type => :text, :order => 20 }
when "list"
options = { :type => :list_optional, :values => field.possible_values, :order => 20}
when "date"
options = { :type => :date, :order => 20 }
when "bool"
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
else
options = { :type => :string, :order => 20 }
end
@available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
end
end
end

View File

@@ -17,9 +17,16 @@
class Repository < ActiveRecord::Base
belongs_to :project
has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
has_many :changes, :through => :changesets
# Raw SQL to delete changesets and changes in the database
# has_many :changesets, :dependent => :destroy is too slow for big repositories
before_destroy :clear_changesets
# Checks if the SCM is enabled when creating a repository
validate_on_create { |r| r.errors.add(:type, :activerecord_error_invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
# Removes leading and trailing whitespace
def url=(arg)
write_attribute(:url, arg ? arg.to_s.strip : nil)
@@ -48,12 +55,24 @@ class Repository < ActiveRecord::Base
scm.supports_annotate?
end
def entry(path=nil, identifier=nil)
scm.entry(path, identifier)
end
def entries(path=nil, identifier=nil)
scm.entries(path, identifier)
end
def diff(path, rev, rev_to, type)
scm.diff(path, rev, rev_to, type)
def properties(path, identifier=nil)
scm.properties(path, identifier)
end
def cat(path, identifier=nil)
scm.cat(path, identifier)
end
def diff(path, rev, rev_to)
scm.diff(path, rev, rev_to)
end
# Default behaviour: we search in cached changesets
@@ -64,6 +83,11 @@ class Repository < ActiveRecord::Base
:order => "committed_on DESC, #{Changeset.table_name}.id DESC").collect(&:changeset)
end
# Returns a path relative to the url of the repository
def relative_path(path)
path
end
def latest_changeset
@latest_changeset ||= changesets.find(:first)
end
@@ -107,4 +131,9 @@ class Repository < ActiveRecord::Base
root_url.strip!
true
end
def clear_changesets
connection.delete("DELETE FROM changes WHERE changes.changeset_id IN (SELECT changesets.id FROM changesets WHERE changesets.repository_id = #{id})")
connection.delete("DELETE FROM changesets WHERE changesets.repository_id = #{id}")
end
end

View File

@@ -34,6 +34,11 @@ class Repository::Bazaar < Repository
if entries
entries.each do |e|
next if e.lastrev.revision.blank?
# Set the filesize unless browsing a specific revision
if identifier.nil? && e.is_file?
full_path = File.join(root_url, e.path)
e.size = File.stat(full_path).size if File.file?(full_path)
end
c = Change.find(:first,
:include => :changeset,
:conditions => ["#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", e.lastrev.revision, id],

View File

@@ -29,9 +29,9 @@ class Repository::Cvs < Repository
'CVS'
end
def entry(path, identifier)
e = entries(path, identifier)
e ? e.first : nil
def entry(path=nil, identifier=nil)
rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
scm.entry(path, rev.nil? ? nil : rev.committed_on)
end
def entries(path=nil, identifier=nil)
@@ -53,7 +53,12 @@ class Repository::Cvs < Repository
entries
end
def diff(path, rev, rev_to, type)
def cat(path, identifier=nil)
rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
scm.cat(path, rev.nil? ? nil : rev.committed_on)
end
def diff(path, rev, rev_to)
#convert rev to revision. CVS can't handle changesets here
diff=[]
changeset_from=changesets.find_by_revision(rev)
@@ -76,7 +81,8 @@ class Repository::Cvs < Repository
unless revision_to
revision_to=scm.get_previous_revision(revision_from)
end
diff=diff+scm.diff(change_from.path, revision_from, revision_to, type)
file_diff = scm.diff(change_from.path, revision_from, revision_to)
diff = diff + file_diff unless file_diff.nil?
end
end
return diff

View File

@@ -46,14 +46,14 @@ class Repository::Darcs < Repository
entries
end
def diff(path, rev, rev_to, type)
def diff(path, rev, rev_to)
patch_from = changesets.find_by_revision(rev)
return nil if patch_from.nil?
patch_to = changesets.find_by_revision(rev_to) if rev_to
if path.blank?
path = patch_from.changes.collect{|change| change.path}.join(' ')
end
patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil, type) : nil
patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil) : nil
end
def fetch_changesets

View File

@@ -0,0 +1,43 @@
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
#
# FileSystem adapter
# File written by Paul Rivier, at Demotera.
#
# 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.
require 'redmine/scm/adapters/filesystem_adapter'
class Repository::Filesystem < Repository
attr_protected :root_url
validates_presence_of :url
def scm_adapter
Redmine::Scm::Adapters::FilesystemAdapter
end
def self.scm_name
'Filesystem'
end
def entries(path=nil, identifier=nil)
scm.entries(path, identifier)
end
def fetch_changesets
nil
end
end

View File

@@ -44,10 +44,8 @@ class Repository::Git < Repository
scm_revision = scm_info.lastrev.scmid
unless changesets.find_by_scmid(scm_revision)
revisions = scm.revisions('', db_revision, nil)
transaction do
revisions.reverse_each do |revision|
scm.revisions('', db_revision, nil, :reverse => true) do |revision|
transaction do
changeset = Changeset.create(:repository => self,
:revision => revision.identifier,
:scmid => revision.scmid,

View File

@@ -35,6 +35,11 @@ class Repository::Subversion < Repository
revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC") : []
end
# Returns a path relative to the url of the repository
def relative_path(path)
path.gsub(Regexp.new("^\/?#{Regexp.escape(relative_url)}"), '')
end
def fetch_changesets
scm_info = scm.info
if scm_info
@@ -71,4 +76,14 @@ class Repository::Subversion < Repository
end
end
end
private
# Returns the relative url of the repository
# Eg: root_url = file:///var/svn/foo
# url = file:///var/svn/foo/bar
# => returns /bar
def relative_url
@relative_url ||= url.gsub(Regexp.new("^#{Regexp.escape(root_url)}"), '')
end
end

View File

@@ -24,11 +24,25 @@ class TimeEntry < ActiveRecord::Base
belongs_to :activity, :class_name => 'Enumeration', :foreign_key => :activity_id
attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
acts_as_customizable
acts_as_event :title => Proc.new {|o| "#{o.user}: #{lwr(:label_f_hour, o.hours)} (#{(o.issue || o.project).event_title})"},
:url => Proc.new {|o| {:controller => 'timelog', :action => 'details', :project_id => o.project}},
:author => :user,
:description => :comments
validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
validates_numericality_of :hours, :allow_nil => true
validates_length_of :comments, :maximum => 255
validates_length_of :comments, :maximum => 255, :allow_nil => true
def after_initialize
if new_record? && self.activity.nil?
if default_activity = Enumeration.default('ACTI')
self.activity_id = default_activity.id
end
end
end
def before_validation
self.project = issue.project if issue && project.nil?
end

View File

@@ -0,0 +1,23 @@
# redMine - project management software
# Copyright (C) 2008 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 TimeEntryCustomField < CustomField
def type_name
:label_spent_time
end
end

View File

@@ -19,8 +19,6 @@ require "digest/sha1"
class User < ActiveRecord::Base
class OnTheFlyCreationFailure < Exception; end
# Account statuses
STATUS_ANONYMOUS = 0
STATUS_ACTIVE = 1
@@ -37,12 +35,13 @@ class User < ActiveRecord::Base
has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name", :dependent => :delete_all
has_many :projects, :through => :memberships
has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
belongs_to :auth_source
acts_as_customizable
attr_accessor :password, :password_confirmation
attr_accessor :last_before_login_on
# Prevents unauthorized assignments
@@ -54,13 +53,12 @@ class User < ActiveRecord::Base
# Login must contain lettres, numbers, underscores only
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
validates_length_of :login, :maximum => 30
validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-]*$/i
validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
validates_length_of :firstname, :lastname, :maximum => 30
validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
validates_length_of :mail, :maximum => 60, :allow_nil => true
validates_length_of :password, :minimum => 4, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true
validates_associated :custom_values, :on => :update
def before_create
self.mail_notification = false
@@ -103,19 +101,16 @@ class User < ActiveRecord::Base
# user is not yet registered, try to authenticate with available sources
attrs = AuthSource.authenticate(login, password)
if attrs
onthefly = new(*attrs)
onthefly.login = login
onthefly.language = Setting.default_language
if onthefly.save
user = find(:first, :conditions => ["login=?", login])
user = new(*attrs)
user.login = login
user.language = Setting.default_language
if user.save
user.reload
logger.info("User '#{user.login}' created from the LDAP") if logger
else
logger.error("User '#{onthefly.login}' found in LDAP but could not be created (#{onthefly.errors.full_messages.join(', ')})") if logger
raise OnTheFlyCreationFailure.new
end
end
end
user.update_attribute(:last_login_on, Time.now) if user
user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
user
rescue => text
raise text
@@ -201,6 +196,10 @@ class User < ActiveRecord::Base
true
end
def anonymous?
!logged?
end
# Return user's role for project
def role_for_project(project)
# No role on archived projects
@@ -258,13 +257,12 @@ class User < ActiveRecord::Base
end
def self.anonymous
return @anonymous_user if @anonymous_user
anonymous_user = AnonymousUser.find(:first)
if anonymous_user.nil?
anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
end
@anonymous_user = anonymous_user
anonymous_user
end
private
@@ -281,6 +279,10 @@ class AnonymousUser < User
errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
end
def available_custom_fields
[]
end
# Overrides a few properties
def logged?; false end
def admin; false end

View File

@@ -42,8 +42,10 @@ class UserPreference < ActiveRecord::Base
if attribute_present? attr_name
super
else
self.others ||= {}
self.others.store attr_name, value
h = read_attribute(:others).dup || {}
h.update(attr_name => value)
write_attribute(:others, h)
value
end
end

View File

@@ -17,7 +17,7 @@
class Wiki < ActiveRecord::Base
belongs_to :project
has_many :pages, :class_name => 'WikiPage', :dependent => :destroy
has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title'
has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all
validates_presence_of :start_page

View File

@@ -35,6 +35,17 @@ class WikiContent < ActiveRecord::Base
:type => 'wiki-page',
:url => Proc.new {|o| {:controller => 'wiki', :id => o.page.wiki.project_id, :page => o.page.title, :version => o.version}}
acts_as_activity_provider :type => 'wiki_pages',
:timestamp => "#{WikiContent.versioned_table_name}.updated_on",
:permission => :view_wiki_pages,
:find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
"#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
"#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
"#{WikiContent.versioned_table_name}.id",
:joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
"LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
"LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"}
def text=(plain)
case Setting.wiki_compression
when 'gzip'

View File

@@ -22,14 +22,15 @@ class WikiPage < ActiveRecord::Base
belongs_to :wiki
has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
has_many :attachments, :as => :container, :dependent => :destroy
acts_as_tree :order => 'title'
acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
:description => :text,
:datetime => :created_on,
:url => Proc.new {|o| {:controller => 'wiki', :id => o.wiki.project_id, :page => o.title}}
acts_as_searchable :columns => ['title', 'text'],
:include => [:wiki, :content],
:include => [{:wiki => :project}, :content],
:project_key => "#{Wiki.table_name}.project_id"
attr_accessor :redirect_existing_links
@@ -105,6 +106,29 @@ class WikiPage < ActiveRecord::Base
def text
content.text if content
end
# Returns true if usr is allowed to edit the page, otherwise false
def editable_by?(usr)
!protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
end
def parent_title
@parent_title || (self.parent && self.parent.pretty_title)
end
def parent_title=(t)
@parent_title = t
parent_page = t.blank? ? nil : self.wiki.find_page(t)
self.parent = parent_page
end
protected
def validate
errors.add(:parent_title, :activerecord_error_invalid) if !@parent_title.blank? && parent.nil?
errors.add(:parent_title, :activerecord_error_circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
errors.add(:parent_title, :activerecord_error_not_same_project) if parent && (parent.wiki_id != wiki_id)
end
end
class WikiDiff

View File

@@ -1,5 +1,6 @@
<div id="login-form">
<% form_tag({:action=> "login"}) do %>
<%= back_url_hidden_field_tag %>
<table>
<tr>
<td align="right"><label for="username"><%=l(:field_login)%>:</label></td>

View File

@@ -5,8 +5,9 @@
<div class="box">
<!--[form:user]-->
<% if @user.auth_source_id.nil? %>
<p><label for="user_login"><%=l(:field_login)%> <span class="required">*</span></label>
<%= text_field 'user', 'login', :size => 25 %></p>
<%= text_field 'user', 'login', :size => 25 %></p>
<p><label for="password"><%=l(:field_password)%> <span class="required">*</span></label>
<%= password_field_tag 'password', nil, :size => 25 %><br />
@@ -14,6 +15,7 @@
<p><label for="password_confirmation"><%=l(:field_password_confirmation)%> <span class="required">*</span></label>
<%= password_field_tag 'password_confirmation', nil, :size => 25 %></p>
<% end %>
<p><label for="user_firstname"><%=l(:field_firstname)%> <span class="required">*</span></label>
<%= text_field 'user', 'firstname' %></p>
@@ -27,8 +29,8 @@
<p><label for="user_language"><%=l(:field_language)%></label>
<%= select("user", "language", lang_options_for_select) %></p>
<% for @custom_value in @custom_values %>
<p><%= custom_field_tag_with_label @custom_value %></p>
<% @user.custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :user, value %></p>
<% end %>
<!--[eoform:user]-->
</div>

View File

@@ -1,7 +1,7 @@
<h2><%=h @user.name %></h2>
<p>
<%= mail_to @user.mail unless @user.pref.hide_mail %>
<%= mail_to(h(@user.mail)) unless @user.pref.hide_mail %>
<ul>
<li><%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %></li>
<% for custom_value in @custom_values %>
@@ -16,8 +16,8 @@
<h3><%=l(:label_project_plural)%></h3>
<ul>
<% for membership in @memberships %>
<li><%= link_to membership.project.name, :controller => 'projects', :action => 'show', :id => membership.project %>
(<%= membership.role.name %>, <%= format_date(membership.created_on) %>)</li>
<li><%= link_to(h(membership.project.name), :controller => 'projects', :action => 'show', :id => membership.project) %>
(<%=h membership.role.name %>, <%= format_date(membership.created_on) %>)</li>
<% end %>
</ul>
<% end %>

View File

@@ -1,6 +1,6 @@
<div class="attachments">
<% for attachment in attachments %>
<p><%= link_to attachment.filename, {:controller => 'attachments', :action => 'download', :id => attachment }, :class => 'icon icon-attachment' -%>
<p><%= link_to_attachment attachment, :class => 'icon icon-attachment' -%>
<%= h(" - #{attachment.description}") unless attachment.description.blank? %>
<span class="size">(<%= number_to_human_size attachment.filesize %>)</span>
<% if options[:delete_url] %>

View File

@@ -0,0 +1,15 @@
<h2><%=h @attachment.filename %></h2>
<div class="attachments">
<p><%= h("#{@attachment.description} - ") unless @attachment.description.blank? %>
<span class="author"><%= @attachment.author %>, <%= format_time(@attachment.created_on) %></span></p>
<p><%= link_to_attachment @attachment, :text => l(:button_download), :download => true -%>
<span class="size">(<%= number_to_human_size @attachment.filesize %>)</span></p>
</div>
&nbsp;
<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type} %>
<% content_for :header_tags do -%>
<%= stylesheet_link_tag "scm" -%>
<% end -%>

View File

@@ -0,0 +1,15 @@
<h2><%=h @attachment.filename %></h2>
<div class="attachments">
<p><%= h("#{@attachment.description} - ") unless @attachment.description.blank? %>
<span class="author"><%= @attachment.author %>, <%= format_time(@attachment.created_on) %></span></p>
<p><%= link_to_attachment @attachment, :text => l(:button_download), :download => true -%>
<span class="size">(<%= number_to_human_size @attachment.filesize %>)</span></p>
</div>
&nbsp;
<%= render :partial => 'common/file', :locals => {:content => @content, :filename => @attachment.filename} %>
<% content_for :header_tags do -%>
<%= stylesheet_link_tag "scm" -%>
<% end -%>

View File

@@ -22,14 +22,12 @@
<p><label for="auth_source_base_dn"><%=l(:field_base_dn)%> <span class="required">*</span></label>
<%= text_field 'auth_source', 'base_dn', :size => 60 %></p>
</div>
<div class="box">
<p><label for="auth_source_onthefly_register"><%=l(:field_onthefly)%></label>
<%= check_box 'auth_source', 'onthefly_register' %></p>
</div>
<p>
<fieldset><legend><%=l(:label_attribute_plural)%></legend>
<fieldset class="box"><legend><%=l(:label_attribute_plural)%></legend>
<p><label for="auth_source_attr_login"><%=l(:field_login)%> <span class="required">*</span></label>
<%= text_field 'auth_source', 'attr_login', :size => 20 %></p>
@@ -42,7 +40,5 @@
<p><label for="auth_source_attr_mail"><%=l(:field_mail)%></label>
<%= text_field 'auth_source', 'attr_mail', :size => 20 %></p>
</fieldset>
</p>
</div>
<!--[eoform:auth_source]-->

View File

@@ -38,3 +38,5 @@
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, {:controller => 'projects', :action => 'activity', :id => @project, :format => 'atom', :show_messages => 1, :key => User.current.rss_key}) %>
<% end %>
<% html_title l(:label_board_plural) %>

View File

@@ -57,3 +57,5 @@
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>
<% html_title h(@board.name) %>

View File

@@ -0,0 +1,64 @@
<% Redmine::UnifiedDiff.new(diff, diff_type).each do |table_file| -%>
<div class="autoscroll">
<% if diff_type == 'sbs' -%>
<table class="filecontent CodeRay">
<thead>
<tr><th colspan="4" class="filename"><%= table_file.file_name %></th></tr>
</thead>
<tbody>
<% prev_line_left, prev_line_right = nil, nil -%>
<% table_file.keys.sort.each do |key| -%>
<% if prev_line_left && prev_line_right && (table_file[key].nb_line_left != prev_line_left+1) && (table_file[key].nb_line_right != prev_line_right+1) -%>
<tr class="spacing">
<th class="line-num">...</th><td></td><th class="line-num">...</th><td></td>
<% end -%>
<tr>
<th class="line-num"><%= table_file[key].nb_line_left %></th>
<td class="line-code <%= table_file[key].type_diff_left %>">
<pre><%=to_utf8 table_file[key].line_left %></pre>
</td>
<th class="line-num"><%= table_file[key].nb_line_right %></th>
<td class="line-code <%= table_file[key].type_diff_right %>">
<pre><%=to_utf8 table_file[key].line_right %></pre>
</td>
</tr>
<% prev_line_left, prev_line_right = table_file[key].nb_line_left.to_i, table_file[key].nb_line_right.to_i -%>
<% end -%>
</tbody>
</table>
<% else -%>
<table class="filecontent CodeRay">
<thead>
<tr><th colspan="3" class="filename"><%= table_file.file_name %></th></tr>
</thead>
<tbody>
<% prev_line_left, prev_line_right = nil, nil -%>
<% table_file.keys.sort.each do |key, line| %>
<% if prev_line_left && prev_line_right && (table_file[key].nb_line_left != prev_line_left+1) && (table_file[key].nb_line_right != prev_line_right+1) -%>
<tr class="spacing">
<th class="line-num">...</th><th class="line-num">...</th><td></td>
</tr>
<% end -%>
<tr>
<th class="line-num"><%= table_file[key].nb_line_left %></th>
<th class="line-num"><%= table_file[key].nb_line_right %></th>
<% if table_file[key].line_left.empty? -%>
<td class="line-code <%= table_file[key].type_diff_right %>">
<pre><%=to_utf8 table_file[key].line_right %></pre>
</td>
<% else -%>
<td class="line-code <%= table_file[key].type_diff_left %>">
<pre><%=to_utf8 table_file[key].line_left %></pre>
</td>
<% end -%>
</tr>
<% prev_line_left = table_file[key].nb_line_left.to_i if table_file[key].nb_line_left.to_i > 0 -%>
<% prev_line_right = table_file[key].nb_line_right.to_i if table_file[key].nb_line_right.to_i > 0 -%>
<% end -%>
</tbody>
</table>
<% end -%>
</div>
<% end -%>

View File

@@ -0,0 +1,11 @@
<div class="autoscroll">
<table class="filecontent CodeRay">
<tbody>
<% line_num = 1 %>
<% syntax_highlight(filename, to_utf8(content)).each_line do |line| %>
<tr><th class="line-num" id="L<%= line_num %>"><%= line_num %></th><td class="line-code"><pre><%= line %></pre></td></tr>
<% line_num += 1 %>
<% end %>
</tbody>
</table>
</div>

View File

@@ -1,3 +1,3 @@
<fieldset class="preview"><legend><%= l(:label_preview) %></legend>
<%= textilizable @text, :attachments => @attachements %>
<%= textilizable @text, :attachments => @attachements, :object => @previewed %>
</fieldset>

View File

@@ -1,6 +1,6 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
xml.title @title
xml.title truncate_single_line(@title, 100)
xml.link "rel" => "self", "href" => url_for(params.merge({:format => nil, :only_path => false}))
xml.link "rel" => "alternate", "href" => url_for(:controller => 'welcome', :only_path => false)
xml.id url_for(:controller => 'welcome', :only_path => false)
@@ -10,11 +10,15 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
@items.each do |item|
xml.entry do
url = url_for(item.event_url(:only_path => false))
xml.title truncate(item.event_title, 100)
if @project
xml.title truncate_single_line(item.event_title, 100)
else
xml.title truncate_single_line("#{item.project} - #{item.event_title}", 100)
end
xml.link "rel" => "alternate", "href" => url
xml.id url
xml.updated item.event_datetime.xmlschema
author = item.event_author if item.respond_to?(:author)
author = item.event_author if item.respond_to?(:event_author)
xml.author do
xml.name(author)
xml.email(author.mail) if author.respond_to?(:mail) && !author.mail.blank?

View File

@@ -105,6 +105,9 @@ when "IssueCustomField" %>
<% when "ProjectCustomField" %>
<p><%= f.check_box :is_required %></p>
<% when "TimeEntryCustomField" %>
<p><%= f.check_box :is_required %></p>
<% end %>
</div>
<%= javascript_tag "toggle_custom_field_format();" %>

View File

@@ -0,0 +1,12 @@
<h2><%= l(@enumeration.option_name) %>: <%=h @enumeration %></h2>
<% form_tag({}) do %>
<div class="box">
<p><strong><%= l(:text_enumeration_destroy_question, @enumeration.objects_count) %></strong></p>
<p><%= l(:text_enumeration_category_reassign_to) %>
<%= select_tag 'reassign_to_id', ("<option>--- #{l(:actionview_instancetag_blank_option)} ---</option>" + options_from_collection_for_select(@enumerations, 'id', 'name')) %></p>
</div>
<%= submit_tag l(:button_apply) %>
<%= link_to l(:button_cancel), :controller => 'enumerations', :action => 'index' %>
<% end %>

View File

@@ -1,14 +1,14 @@
<h2><%=l(:label_enumerations)%></h2>
<% Enumeration::OPTIONS.each do |option, name| %>
<h3><%= l(name) %></h3>
<% Enumeration::OPTIONS.each do |option, params| %>
<h3><%= l(params[:label]) %></h3>
<% enumerations = Enumeration.get_values(option) %>
<% if enumerations.any? %>
<table class="list">
<% enumerations.each do |enumeration| %>
<tr class="<%= cycle('odd', 'even') %>">
<td><%= link_to enumeration.name, :action => 'edit', :id => enumeration %></td>
<td><%= link_to h(enumeration), :action => 'edit', :id => enumeration %></td>
<td style="width:15%;"><%= image_tag('true.png') if enumeration.is_default? %></td>
<td style="width:15%;">
<%= link_to image_tag('2uparrow.png', :alt => l(:label_sort_highest)), {:action => 'move', :id => enumeration, :position => 'highest'}, :method => :post, :title => l(:label_sort_highest) %>
@@ -16,6 +16,9 @@
<%= link_to image_tag('1downarrow.png', :alt => l(:label_sort_lower)), {:action => 'move', :id => enumeration, :position => 'lower'}, :method => :post, :title => l(:label_sort_lower) %>
<%= link_to image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), {:action => 'move', :id => enumeration, :position => 'lowest'}, :method => :post, :title => l(:label_sort_lowest) %>
</td>
<td align="center" style="width:10%;">
<%= link_to l(:button_delete), { :action => 'destroy', :id => enumeration }, :method => :post, :confirm => l(:text_are_you_sure), :class => "icon icon-del" %>
</td>
</tr>
<% end %>
</table>

View File

@@ -4,6 +4,7 @@
:class => nil,
:multipart => true} do |f| %>
<%= error_messages_for 'issue' %>
<%= error_messages_for 'time_entry' %>
<div class="box">
<% if @edit_allowed || !@allowed_statuses.empty? %>
<fieldset class="tabular"><legend><%= l(:label_change_properties) %>
@@ -21,9 +22,12 @@
<p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
</div>
<div class="splitcontentright">
<p><%= time_entry.text_field :comments, :size => 40 %></p>
<p><%= time_entry.select :activity_id, (@activities.collect {|p| [p.name, p.id]}) %></p>
<p><%= time_entry.select :activity_id, activity_collection_for_select_options %></p>
</div>
<p><%= time_entry.text_field :comments, :size => 60 %></p>
<% @time_entry.custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :time_entry, value %></p>
<% end %>
<% end %>
</fieldset>
<% end %>

View File

@@ -48,4 +48,6 @@
<p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
<% end %>
<%= Redmine::Plugin::Hook::Manager.call_hook(:issue_edit, {:project => @project, :issue => @issue, :form => f }) %>
<%= wikitoolbar_for 'issue_description' %>

View File

@@ -1,11 +1,12 @@
<div class="splitcontentleft">
<% i = 1 %>
<% for @custom_value in values %>
<p><%= custom_field_tag_with_label @custom_value %></p>
<% if i == values.size / 2 %>
<% split_on = @issue.custom_field_values.size / 2 %>
<% @issue.custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :issue, value %></p>
<% if i == split_on -%>
</div><div class="splitcontentright">
<% end %>
<% i += 1 %>
<% end %>
<% end -%>
<% i += 1 -%>
<% end -%>
</div>
<div style="clear:both;"> </div>

View File

@@ -1,3 +1,4 @@
<% reply_links = authorize_for('issues', 'edit') -%>
<% for journal in journals %>
<div id="change-<%= journal.id %>" class="journal">
<h4><div style="float:right;"><%= link_to "##{journal.indice}", :anchor => "note-#{journal.indice}" %></div>
@@ -8,6 +9,6 @@
<li><%= show_detail(detail) %></li>
<% end %>
</ul>
<%= render_notes(journal) unless journal.notes.blank? %>
<%= render_notes(journal, :reply_links => reply_links) unless journal.notes.blank? %>
</div>
<% end %>

View File

@@ -1,7 +1,7 @@
<% form_tag({}) do -%>
<table class="list issues">
<thead><tr>
<th><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(this.up("form")); return false;',
<th><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;',
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
</th>
<%= sort_header_tag("#{Issue.table_name}.id", :caption => '#', :default_order => 'desc') %>

View File

@@ -1,118 +0,0 @@
<% pdf.SetFontStyle('B',11)
pdf.Cell(190,10, "#{issue.project.name} - #{issue.tracker.name} # #{issue.id}: #{issue.subject}")
pdf.Ln
y0 = pdf.GetY
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_status) + ":","LT")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, issue.status.name,"RT")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_priority) + ":","LT")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, issue.priority.name,"RT")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_author) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, issue.author.name,"R")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_category) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, (issue.category ? issue.category.name : "-"),"R")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_created_on) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(issue.created_on),"R")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_assigned_to) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, (issue.assigned_to ? issue.assigned_to.name : "-"),"R")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_updated_on) + ":","LB")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(issue.updated_on),"RB")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_due_date) + ":","LB")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(issue.due_date),"RB")
pdf.Ln
for custom_value in issue.custom_values
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, custom_value.custom_field.name + ":","L")
pdf.SetFontStyle('',9)
pdf.MultiCell(155,5, (show_value custom_value),"R")
end
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_subject) + ":","LTB")
pdf.SetFontStyle('',9)
pdf.Cell(155,5, issue.subject,"RTB")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_description) + ":")
pdf.SetFontStyle('',9)
pdf.MultiCell(155,5, issue.description,"BR")
pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
pdf.Ln
if @issue.changesets.any? && User.current.allowed_to?(:view_changesets, issue.project)
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_associated_revisions), "B")
pdf.Ln
for changeset in @issue.changesets
pdf.SetFontStyle('B',8)
pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.committer)
pdf.Ln
unless changeset.comments.blank?
pdf.SetFontStyle('',8)
pdf.MultiCell(190,5, changeset.comments)
end
pdf.Ln
end
end
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_history), "B")
pdf.Ln
for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
pdf.SetFontStyle('B',8)
pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
pdf.Ln
pdf.SetFontStyle('I',8)
for detail in journal.details
pdf.Cell(190,5, "- " + show_detail(detail, true))
pdf.Ln
end
if journal.notes?
pdf.SetFontStyle('',8)
pdf.MultiCell(190,5, journal.notes)
end
pdf.Ln
end
if issue.attachments.any?
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_attachment_plural), "B")
pdf.Ln
for attachment in issue.attachments
pdf.SetFontStyle('',8)
pdf.Cell(80,5, attachment.filename)
pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R")
pdf.Cell(65,5, attachment.author.name,0,0,"R")
pdf.Ln
end
end
%>

View File

@@ -38,6 +38,7 @@
<label><%= l(:field_done_ratio) %>:
<%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label>
</p>
<%= Redmine::Plugin::Hook::Manager.call_hook(:issue_bulk_edit, {:project => @project, :issue => @issues }) %>
</fieldset>
<fieldset><legend><%= l(:field_notes) %></legend>

View File

@@ -6,7 +6,7 @@
<a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
<ul>
<% @statuses.each do |s| -%>
<li><%= context_menu_link s.name, {:controller => 'issues', :action => 'edit', :id => @issue, :issue => {:status_id => s}},
<li><%= context_menu_link s.name, {:controller => 'issues', :action => 'edit', :id => @issue, :issue => {:status_id => s}, :back_to => @back}, :method => :post,
:selected => (s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %></li>
<% end -%>
</ul>
@@ -20,6 +20,19 @@
<% end -%>
</ul>
</li>
<% unless @project.versions.empty? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
<ul>
<% @project.versions.sort.each do |v| -%>
<li><%= context_menu_link v.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[fixed_version_id]' => v, :back_to => @back}, :method => :post,
:selected => (v == @issue.fixed_version), :disabled => !@can[:update] %></li>
<% end -%>
<li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[fixed_version_id]' => '', :back_to => @back}, :method => :post,
:selected => @issue.fixed_version.nil?, :disabled => !@can[:update] %></li>
</ul>
</li>
<% end %>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
<ul>
@@ -31,6 +44,19 @@
:selected => @issue.assigned_to.nil?, :disabled => !@can[:update] %></li>
</ul>
</li>
<% unless @project.issue_categories.empty? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_category) %></a>
<ul>
<% @project.issue_categories.each do |u| -%>
<li><%= context_menu_link u.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[category_id]' => u, :back_to => @back}, :method => :post,
:selected => (u == @issue.category), :disabled => !@can[:update] %></li>
<% end -%>
<li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[category_id]' => '', :back_to => @back}, :method => :post,
:selected => @issue.category.nil?, :disabled => !@can[:update] %></li>
</ul>
</li>
<% end -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
<ul>
@@ -40,6 +66,10 @@
<% end -%>
</ul>
</li>
<% if @can[:log_time] -%>
<li><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue},
:class => 'icon-time' %></li>
<% end %>
<li><%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue},
:class => 'icon-copy', :disabled => !@can[:copy] %></li>
<% else -%>

View File

@@ -45,7 +45,7 @@
<p class="other-formats">
<%= l(:label_export_to) %>
<span><%= link_to 'Atom', {:format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
<span><%= link_to 'Atom', {:query_id => @query, :format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
<span><%= link_to 'CSV', {:format => 'csv'}, :class => 'csv' %></span>
<span><%= link_to 'PDF', {:format => 'pdf'}, :class => 'pdf' %></span>
</p>

View File

@@ -4,7 +4,123 @@
pdf.footer_date = format_date(Date.today)
pdf.AddPage
render :partial => 'issues/pdf', :locals => { :pdf => pdf, :issue => @issue }
pdf.SetFontStyle('B',11)
pdf.Cell(190,10, "#{@issue.project} - #{@issue.tracker} # #{@issue.id}: #{@issue.subject}")
pdf.Ln
y0 = pdf.GetY
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_status) + ":","LT")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, @issue.status.name,"RT")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_priority) + ":","LT")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, @issue.priority.name,"RT")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_author) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, @issue.author.name,"R")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_category) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, (@issue.category ? @issue.category.name : "-"),"R")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_created_on) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(@issue.created_on),"R")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_assigned_to) + ":","L")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, (@issue.assigned_to ? @issue.assigned_to.name : "-"),"R")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_updated_on) + ":","LB")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(@issue.updated_on),"RB")
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_due_date) + ":","LB")
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(@issue.due_date),"RB")
pdf.Ln
for custom_value in @issue.custom_values
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, custom_value.custom_field.name + ":","L")
pdf.SetFontStyle('',9)
pdf.MultiCell(155,5, (show_value custom_value),"R")
end
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_subject) + ":","LTB")
pdf.SetFontStyle('',9)
pdf.Cell(155,5, @issue.subject,"RTB")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_description) + ":")
pdf.SetFontStyle('',9)
pdf.MultiCell(155,5, @issue.description,"BR")
pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
pdf.Ln
if @issue.changesets.any? && User.current.allowed_to?(:view_changesets, @issue.project)
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_associated_revisions), "B")
pdf.Ln
for changeset in @issue.changesets
pdf.SetFontStyle('B',8)
pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.committer)
pdf.Ln
unless changeset.comments.blank?
pdf.SetFontStyle('',8)
pdf.MultiCell(190,5, changeset.comments)
end
pdf.Ln
end
end
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_history), "B")
pdf.Ln
for journal in @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
pdf.SetFontStyle('B',8)
pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
pdf.Ln
pdf.SetFontStyle('I',8)
for detail in journal.details
pdf.Cell(190,5, "- " + show_detail(detail, true))
pdf.Ln
end
if journal.notes?
pdf.SetFontStyle('',8)
pdf.MultiCell(190,5, journal.notes)
end
pdf.Ln
end
if @issue.attachments.any?
pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_attachment_plural), "B")
pdf.Ln
for attachment in @issue.attachments
pdf.SetFontStyle('',8)
pdf.Cell(80,5, attachment.filename)
pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R")
pdf.Cell(65,5, attachment.author.name,0,0,"R")
pdf.Ln
end
end
%>
<%= pdf.Output %>

View File

@@ -1,5 +1,5 @@
<div class="contextual">
<%= show_and_goto_link(l(:button_update), 'update', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) if authorize_for('issues', 'edit') %>
<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
<%= watcher_tag(@issue, User.current) %>
<%= link_to_if_authorized l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %>
@@ -18,34 +18,34 @@
<table width="100%">
<tr>
<td style="width:15%"><b><%=l(:field_status)%> :</b></td><td style="width:35%"><%= @issue.status.name %></td>
<td style="width:15%"><b><%=l(:field_start_date)%> :</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
<td style="width:15%"><b><%=l(:field_status)%>:</b></td><td style="width:35%"><%= @issue.status.name %></td>
<td style="width:15%"><b><%=l(:field_start_date)%>:</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
</tr>
<tr>
<td><b><%=l(:field_priority)%> :</b></td><td><%= @issue.priority.name %></td>
<td><b><%=l(:field_due_date)%> :</b></td><td><%= format_date(@issue.due_date) %></td>
<td><b><%=l(:field_priority)%>:</b></td><td><%= @issue.priority.name %></td>
<td><b><%=l(:field_due_date)%>:</b></td><td><%= format_date(@issue.due_date) %></td>
</tr>
<tr>
<td><b><%=l(:field_assigned_to)%> :</b></td><td><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
<td><b><%=l(:field_done_ratio)%> :</b></td><td><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
<td><b><%=l(:field_assigned_to)%>:</b></td><td><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
<td><b><%=l(:field_done_ratio)%>:</b></td><td><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
</tr>
<tr>
<td><b><%=l(:field_category)%> :</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
<td><b><%=l(:field_category)%>:</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
<% if User.current.allowed_to?(:view_time_entries, @project) %>
<td><b><%=l(:label_spent_time)%> :</b></td>
<td><b><%=l(:label_spent_time)%>:</b></td>
<td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
<% end %>
</tr>
<tr>
<td><b><%=l(:field_fixed_version)%> :</b></td><td><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
<td><b><%=l(:field_fixed_version)%>:</b></td><td><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
<% if @issue.estimated_hours %>
<td><b><%=l(:field_estimated_hours)%> :</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
<td><b><%=l(:field_estimated_hours)%>:</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
<% end %>
</tr>
<tr>
<% n = 0
for custom_value in @custom_values %>
<td valign="top"><b><%= custom_value.custom_field.name %> :</b></td><td valign="top"><%= simple_format(h(show_value(custom_value))) %></td>
<% n = 0 -%>
<% @issue.custom_values.each do |value| -%>
<td valign="top"><b><%=h value.custom_field.name %>:</b></td><td valign="top"><%= simple_format(h(show_value(value))) %></td>
<% n = n + 1
if (n > 1)
n = 0 %>
@@ -53,9 +53,17 @@ for custom_value in @custom_values %>
<%end
end %>
</tr>
<%= Redmine::Plugin::Hook::Manager.call_hook(:issue_show, {:project => @project, :issue => @issue}) %>
</table>
<hr />
<div class="contextual">
<%= link_to_remote(image_tag('comment.png'),
{ :url => {:controller => 'issues', :action => 'reply', :id => @issue} },
:title => l(:button_reply)) if authorize_for('issues', 'edit') %>
</div>
<p><strong><%=l(:field_description)%></strong></p>
<div class="wiki">
<%= textilizable @issue, :description, :attachments => @issue.attachments %>
@@ -110,4 +118,5 @@ end %>
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
<%= stylesheet_link_tag 'scm' %>
<% end %>

View File

@@ -36,7 +36,7 @@
<%= render :partial => 'layouts/project_selector' if User.current.memberships.any? %>
</div>
<h1><%= h(@project ? @project.name : Setting.app_title) %></h1>
<h1><%= h(@project && !@project.new_record? ? @project.name : Setting.app_title) %></h1>
<div id="main-menu">
<%= render_main_menu(@project) %>

View File

@@ -1,12 +1,32 @@
<html>
<head>
<style>
body { font-family: Verdana, sans-serif; font-size: 0.8em; color:#484848; }
body h1 { font-family: "Trebuchet MS", Verdana, sans-serif; font-size: 1.2em; margin: 0;}
a, a:link, a:visited{ color: #2A5685; }
a:hover, a:active{ color: #c61a1a; }
hr { width: 100%; height: 1px; background: #ccc; border: 0; }
.footer { font-size: 0.8em; font-style: italic; }
body {
font-family: Verdana, sans-serif;
font-size: 0.8em;
color:#484848;
}
h1 {
font-family: "Trebuchet MS", Verdana, sans-serif;
font-size: 1.2em;
margin: 0px;
}
a, a:link, a:visited {
color: #2A5685;
}
a:hover, a:active {
color: #c61a1a;
}
hr {
width: 100%;
height: 1px;
background: #ccc;
border: 0;
}
.footer {
font-size: 0.8em;
font-style: italic;
}
</style>
</head>
<body>

View File

@@ -1,2 +1,4 @@
<p><%= l(:mail_body_lost_password) %><br />
<%= auto_link(@url) %></p>
<p><%= l(:field_login) %>: <b><%= @token.user.login %></b></p>

View File

@@ -1,2 +1,4 @@
<%= l(:mail_body_lost_password) %>
<%= @url %>
<%= l(:field_login) %>: <%= @token.user.login %>

View File

@@ -0,0 +1,9 @@
<p><%= l(:mail_body_reminder, @issues.size, @days) %></p>
<ul>
<% @issues.each do |issue| -%>
<li><%=h "#{issue.project} - #{issue.tracker} ##{issue.id}: #{issue.subject}" %></li>
<% end -%>
</ul>
<p><%= link_to l(:label_issue_view_all), @issues_url %></p>

View File

@@ -0,0 +1,7 @@
<%= l(:mail_body_reminder, @issues.size, @days) %>:
<% @issues.each do |issue| -%>
* <%= "#{issue.project} - #{issue.tracker} ##{issue.id}: #{issue.subject}" %>
<% end -%>
<%= @issues_url %>

View File

@@ -17,6 +17,7 @@
</div>
<br />
<% unless @replies.empty? %>
<h3 class="icon22 icon22-comment"><%= l(:label_reply_plural) %></h3>
<% @replies.each do |message| %>
<a name="<%= "message-#{message.id}" %>"></a>
@@ -30,6 +31,7 @@
<%= link_to_attachments message.attachments, :no_author => true %>
</div>
<% end %>
<% end %>
<% if !@topic.locked? && authorize_for('messages', 'reply') %>
<p><%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %></p>
@@ -48,3 +50,9 @@
<div id="preview" class="wiki"></div>
</div>
<% end %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag 'scm' %>
<% end %>
<% html_title h(@topic.subject) %>

View File

@@ -5,7 +5,7 @@
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
<%= link_to_remote l(:label_preview),
{ :url => { :controller => 'news', :action => 'preview' },
{ :url => { :controller => 'news', :action => 'preview', :project_id => @project },
:method => 'post',
:update => 'preview',
:with => "Form.serialize('news-form')"

View File

@@ -12,7 +12,7 @@
<%= render :partial => 'news/form', :locals => { :f => f } %>
<%= submit_tag l(:button_create) %>
<%= link_to_remote l(:label_preview),
{ :url => { :controller => 'news', :action => 'preview' },
{ :url => { :controller => 'news', :action => 'preview', :project_id => @project },
:method => 'post',
:update => 'preview',
:with => "Form.serialize('news-form')"

View File

@@ -5,7 +5,7 @@
<%= render :partial => 'news/form', :locals => { :f => f } %>
<%= submit_tag l(:button_create) %>
<%= link_to_remote l(:label_preview),
{ :url => { :controller => 'news', :action => 'preview' },
{ :url => { :controller => 'news', :action => 'preview', :project_id => @project },
:method => 'post',
:update => 'preview',
:with => "Form.serialize('news-form')"

View File

@@ -15,7 +15,7 @@
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
<%= link_to_remote l(:label_preview),
{ :url => { :controller => 'news', :action => 'preview' },
{ :url => { :controller => 'news', :action => 'preview', :project_id => @project },
:method => 'post',
:update => 'preview',
:with => "Form.serialize('news-form')"
@@ -55,3 +55,7 @@
<% end %>
<% html_title @news.title -%>
<% content_for :header_tags do %>
<%= stylesheet_link_tag 'scm' %>
<% end %>

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