Compare commits

..

99 Commits
0.3.0 ... 0.4.1

Author SHA1 Message Date
Jean-Philippe Lang
38aeb2746c tagged version 0.4.1
git-svn-id: http://redmine.rubyforge.org/svn/tags/0.4.1@149 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-03 18:44:55 +00:00
Jean-Philippe Lang
28ea323791 updated RDM_APP_VERSION for 0.4.1
git-svn-id: http://redmine.rubyforge.org/svn/trunk@148 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-03 18:38:08 +00:00
Jean-Philippe Lang
18787caab9 0.4.1 release
git-svn-id: http://redmine.rubyforge.org/svn/trunk@147 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-03 18:32:03 +00:00
Jean-Philippe Lang
390d6023c6 * added calendar.png removed by accident
* removed resizer background style for textareas

git-svn-id: http://redmine.rubyforge.org/svn/trunk@146 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-03 18:29:16 +00:00
Jean-Philippe Lang
e1ef9a5c72 fixed: no recipient if one of the members of the project has disabled mail notifications
git-svn-id: http://redmine.rubyforge.org/svn/trunk@145 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-03 18:26:45 +00:00
Jean-Philippe Lang
f3babef54a 0.4.0 release
git-svn-id: http://redmine.rubyforge.org/svn/trunk@143 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-02 16:34:15 +00:00
Jean-Philippe Lang
2dc3850798 added an icon on news comments
git-svn-id: http://redmine.rubyforge.org/svn/trunk@142 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-02 15:27:10 +00:00
Jean-Philippe Lang
18001e5a5b various documentation updates
git-svn-id: http://redmine.rubyforge.org/svn/trunk@141 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-02 14:56:10 +00:00
Jean-Philippe Lang
32f6cb7c13 unused images cleaning
git-svn-id: http://redmine.rubyforge.org/svn/trunk@140 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-02 11:20:10 +00:00
Jean-Philippe Lang
9f76b4d2ef enumerations/list redesign
git-svn-id: http://redmine.rubyforge.org/svn/trunk@139 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-02 11:15:54 +00:00
Jean-Philippe Lang
a9efe82117 updated redirection to the help main index (recognition fails when redirecting to a directory)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@138 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-02 10:49:59 +00:00
Jean-Philippe Lang
4ee55dc456 updated validation format of repository url to allow file:///
git-svn-id: http://redmine.rubyforge.org/svn/trunk@137 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-02 10:12:15 +00:00
Jean-Philippe Lang
470ef4d11e * replaced "add_issue" links on projects/show by a drop down list
* added this shortcut on list_issues

git-svn-id: http://redmine.rubyforge.org/svn/trunk@136 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-02 09:58:07 +00:00
Jean-Philippe Lang
57d4e698fe updated notice_file_not_found message
git-svn-id: http://redmine.rubyforge.org/svn/trunk@135 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-02 08:49:23 +00:00
Jean-Philippe Lang
f50544bb15 addded ruby-net-ldap (0.0.4) dependency in vendor/pluggin
git-svn-id: http://redmine.rubyforge.org/svn/trunk@134 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-02 08:48:40 +00:00
Jean-Philippe Lang
e3becc7c3c ActiveRecord::RecordNotFound exceptions handled more gracefully
git-svn-id: http://redmine.rubyforge.org/svn/trunk@133 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-02 08:47:07 +00:00
Jean-Philippe Lang
e7caec6e7d language for default configuration data can now be chosen when running 'load_default_data' task
git-svn-id: http://redmine.rubyforge.org/svn/trunk@132 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-01 21:34:10 +00:00
Jean-Philippe Lang
6487c1803f option to set maximum size of uploaded files
git-svn-id: http://redmine.rubyforge.org/svn/trunk@131 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-01 12:09:09 +00:00
Jean-Philippe Lang
ff65a5b22a added multiple file upload for documents and files modules
git-svn-id: http://redmine.rubyforge.org/svn/trunk@130 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-01 11:26:56 +00:00
Jean-Philippe Lang
7a03cf92ba fixed: non public projects were shown on welcome screen even if current user is not a member
git-svn-id: http://redmine.rubyforge.org/svn/trunk@129 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-01 10:13:01 +00:00
Jean-Philippe Lang
e6fa690d65 fixed: public actions not authorized for members of non public projects
git-svn-id: http://redmine.rubyforge.org/svn/trunk@128 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-31 17:00:16 +00:00
Jean-Philippe Lang
95cc65f14e replaced deprecated controller instance variables: @params, @session, @request
git-svn-id: http://redmine.rubyforge.org/svn/trunk@127 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 21:33:44 +00:00
Jean-Philippe Lang
b3a3d3e2fa replaced deprecated find_all calls
git-svn-id: http://redmine.rubyforge.org/svn/trunk@126 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 21:18:44 +00:00
Jean-Philippe Lang
0820e745d6 textile for revisions comments
git-svn-id: http://redmine.rubyforge.org/svn/trunk@125 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 21:15:28 +00:00
Jean-Philippe Lang
2197fa3120 added issues integration tests
git-svn-id: http://redmine.rubyforge.org/svn/trunk@124 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 20:14:03 +00:00
Jean-Philippe Lang
b4d4b80dcd replaced deprecated ":dependent => true" statements
git-svn-id: http://redmine.rubyforge.org/svn/trunk@123 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 17:11:44 +00:00
Jean-Philippe Lang
3bfaa20c05 fixed: unable to attach a file when creating an issue ("attachment: invalid" error)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@122 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 17:10:05 +00:00
Jean-Philippe Lang
58f8a23d58 * news rss feed added
* "header_tags" block added to allow inclusion of page specific tags in the html header (stylesheets, javascripts, feeds...)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@121 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 16:19:12 +00:00
Jean-Philippe Lang
1f3380bfad misc GUI changes on roles views
git-svn-id: http://redmine.rubyforge.org/svn/trunk@120 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 15:02:33 +00:00
Jean-Philippe Lang
451ec23060 replaced some submit buttons by links on projects/settings
git-svn-id: http://redmine.rubyforge.org/svn/trunk@119 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 11:42:37 +00:00
Jean-Philippe Lang
4c84165aa0 added the ability to manage users memberships directly on users/edit (administration module)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@118 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 11:28:22 +00:00
Jean-Philippe Lang
a6c8feea21 misc GUI modifications
git-svn-id: http://redmine.rubyforge.org/svn/trunk@117 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 10:11:55 +00:00
Jean-Philippe Lang
718de6c7fd added a condition on project -> members association so that only active users are considered as members
git-svn-id: http://redmine.rubyforge.org/svn/trunk@116 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 09:13:56 +00:00
Jean-Philippe Lang
d81a135704 href title attribute with full issue subject added
git-svn-id: http://redmine.rubyforge.org/svn/trunk@115 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 09:13:03 +00:00
Jean-Philippe Lang
1a90fbee8b fixed a bug in gantt display when last day is a monday
git-svn-id: http://redmine.rubyforge.org/svn/trunk@114 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-26 09:12:13 +00:00
Jean-Philippe Lang
2ac30707d2 updated the appearance of table headers
git-svn-id: http://redmine.rubyforge.org/svn/trunk@113 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-25 10:53:15 +00:00
Jean-Philippe Lang
58a36e3604 attachments list on issues/show:
* "delete" form replaced by link_to
* added css class for attachment icon 

git-svn-id: http://redmine.rubyforge.org/svn/trunk@112 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-25 10:14:24 +00:00
Jean-Philippe Lang
676fe403ab removed menu.css and rails.css (content merged into application.css)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@111 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-25 09:56:21 +00:00
Jean-Philippe Lang
13611bf0ab route for repositories controller removed (problem when running under apache)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@110 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-24 17:19:40 +00:00
Jean-Philippe Lang
ad02b3fb7b fixed in svn browser: revisions were ignored when no author was found
git-svn-id: http://redmine.rubyforge.org/svn/trunk@109 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-24 17:18:29 +00:00
Jean-Philippe Lang
d3600e729f css cleaning
git-svn-id: http://redmine.rubyforge.org/svn/trunk@108 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-24 16:35:25 +00:00
Jean-Philippe Lang
6c8e1514aa xss in issue subject on issues/edit
git-svn-id: http://redmine.rubyforge.org/svn/trunk@107 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-24 15:41:23 +00:00
Jean-Philippe Lang
bc44158501 svn browser merged in trunk
git-svn-id: http://redmine.rubyforge.org/svn/trunk@106 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-24 13:38:45 +00:00
Jean-Philippe Lang
918123cd06 * code and views cleaning
* javascript added on custom field form to show/hide fields according to the format of custom field

git-svn-id: http://redmine.rubyforge.org/svn/trunk@100 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-17 14:22:09 +00:00
Jean-Philippe Lang
2b86ef8e28 various modifications to prevent xss
- validation of names and labels against /^[\w\s\'\-]*$/i
- html entities encoding

git-svn-id: http://redmine.rubyforge.org/svn/trunk@99 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-17 08:10:18 +00:00
Jean-Philippe Lang
3e28dc669b various eager loadings added
git-svn-id: http://redmine.rubyforge.org/svn/trunk@98 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-16 17:33:31 +00:00
Jean-Philippe Lang
bee3f353fc fixed bug when no version is selected in projects/add_file
git-svn-id: http://redmine.rubyforge.org/svn/trunk@97 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-16 16:31:11 +00:00
Jean-Philippe Lang
005f4baaa4 issues table is no more displayed if there is no issue to display
git-svn-id: http://redmine.rubyforge.org/svn/trunk@96 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-16 13:45:56 +00:00
Jean-Philippe Lang
2b0142580f "queries" branch merged
git-svn-id: http://redmine.rubyforge.org/svn/trunk@95 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-16 13:37:32 +00:00
Jean-Philippe Lang
236c735d08 contextual links redesign
git-svn-id: http://redmine.rubyforge.org/svn/trunk@93 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-16 12:15:31 +00:00
Jean-Philippe Lang
e5562afcd3 useless javascript for drop down menu removed
git-svn-id: http://redmine.rubyforge.org/svn/trunk@85 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-13 19:57:19 +00:00
Jean-Philippe Lang
256eb6250e bug fixed in projects/activity due to sql join
documents added in projects/activity

git-svn-id: http://redmine.rubyforge.org/svn/trunk@84 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-11 21:25:50 +00:00
Jean-Philippe Lang
e50ba150c3 added print.css to hide header, menus and footer
git-svn-id: http://redmine.rubyforge.org/svn/trunk@83 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-10 20:34:51 +00:00
Jean-Philippe Lang
bab9c40dcc "export to" label added on issues/show
git-svn-id: http://redmine.rubyforge.org/svn/trunk@82 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-10 20:18:13 +00:00
Jean-Philippe Lang
55ed70529a added model Comment.
comments can now be added on news.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@81 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-10 18:35:48 +00:00
Jean-Philippe Lang
28c6aa4e6d css adjustment for drag n drop handles
git-svn-id: http://redmine.rubyforge.org/svn/trunk@74 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-07 18:12:27 +00:00
Jean-Philippe Lang
ab88b27622 gantt link added in left menu and removed from calendar view
git-svn-id: http://redmine.rubyforge.org/svn/trunk@73 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-06 21:13:46 +00:00
Jean-Philippe Lang
2c335dbf32 calendar and activity views edited (previous/next links)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@72 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-06 20:54:43 +00:00
Jean-Philippe Lang
d6d1502200 blockquote style added in css
git-svn-id: http://redmine.rubyforge.org/svn/trunk@69 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-05 21:06:07 +00:00
Jean-Philippe Lang
4bb61ade27 /trunk/redmine removed
git-svn-id: http://redmine.rubyforge.org/svn/trunk@68 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-05 20:46:55 +00:00
Jean-Philippe Lang
96f83cc8f0 trunk moved from /trunk/redmine to /trunk
git-svn-id: http://redmine.rubyforge.org/svn/trunk@67 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-05 20:45:04 +00:00
Jean-Philippe Lang
eabc04d836 form_for added in my/account view
git-svn-id: http://redmine.rubyforge.org/svn/trunk@66 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-04 22:02:30 +00:00
Jean-Philippe Lang
b3be8745b0 tr color removed on issues list
git-svn-id: http://redmine.rubyforge.org/svn/trunk@65 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-04 21:31:14 +00:00
Jean-Philippe Lang
aa28c3d7ba git-svn-id: http://redmine.rubyforge.org/svn/trunk@64 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-12-04 21:05:17 +00:00
Jean-Philippe Lang
948e2190f8 "hide my email address" preference added, so that it's not displayed on account/show
git-svn-id: http://redmine.rubyforge.org/svn/trunk@63 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-03 20:51:17 +00:00
Jean-Philippe Lang
362633d486 - new controller "myController"
- account/my_page moved to my/page
- account/my_account mmoved to my/account
- "my page" is now customizable (table user_preferences added)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@62 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-12-03 19:55:45 +00:00
Jean-Philippe Lang
120e39ba80 news and documents views modified
git-svn-id: http://redmine.rubyforge.org/svn/trunk@61 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-30 22:17:44 +00:00
Jean-Philippe Lang
98d4bdb7df documents views modified
git-svn-id: http://redmine.rubyforge.org/svn/trunk@60 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-30 22:00:35 +00:00
Jean-Philippe Lang
2c6e3cb02d export links modified
git-svn-id: http://redmine.rubyforge.org/svn/trunk@59 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-29 21:50:10 +00:00
Jean-Philippe Lang
9c70084ea5 legend added on calendar
git-svn-id: http://redmine.rubyforge.org/svn/trunk@58 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-29 20:29:12 +00:00
Jean-Philippe Lang
9b2bd72e1f issue subjects modified on gantt pdf export
git-svn-id: http://redmine.rubyforge.org/svn/trunk@57 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-29 20:06:44 +00:00
Jean-Philippe Lang
707360169e issue links modified on activity, calendar and gantt views
git-svn-id: http://redmine.rubyforge.org/svn/trunk@56 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-29 20:02:55 +00:00
Jean-Philippe Lang
418b8d861a mailer bug fix
git-svn-id: http://redmine.rubyforge.org/svn/trunk@55 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-27 22:45:10 +00:00
Jean-Philippe Lang
42181112ff improved issues change history
git-svn-id: http://redmine.rubyforge.org/svn/trunk@54 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-27 22:31:14 +00:00
Jean-Philippe Lang
67e81b0ae9 gantt correction
git-svn-id: http://redmine.rubyforge.org/svn/trunk@53 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-27 19:53:59 +00:00
Jean-Philippe Lang
51317b350c git-svn-id: http://redmine.rubyforge.org/svn/trunk@52 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-11-21 22:39:46 +00:00
Jean-Philippe Lang
c83e794871 git-svn-id: http://redmine.rubyforge.org/svn/trunk@51 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-11-21 22:27:47 +00:00
Jean-Philippe Lang
26ec775854 git-svn-id: http://redmine.rubyforge.org/svn/trunk@50 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-11-21 19:09:40 +00:00
Jean-Philippe Lang
5e9e01bf7f missing rfpdf in the repository...
git-svn-id: http://redmine.rubyforge.org/svn/trunk@49 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-21 18:34:04 +00:00
Jean-Philippe Lang
f7df1b7d55 missing rfpdf in the repository...
git-svn-id: http://redmine.rubyforge.org/svn/trunk@48 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-21 18:33:16 +00:00
Jean-Philippe Lang
abb5d019cd git-svn-id: http://redmine.rubyforge.org/svn/trunk@47 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-11-20 22:41:27 +00:00
Jean-Philippe Lang
474b5a52d6 git-svn-id: http://redmine.rubyforge.org/svn/trunk@46 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-11-20 20:40:52 +00:00
Jean-Philippe Lang
63d3b12bb5 * css modified
* 404 and 500 error pages modified

git-svn-id: http://redmine.rubyforge.org/svn/trunk@45 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-17 20:24:34 +00:00
Jean-Philippe Lang
7136b85f21 * new report: project activity
* "start date" and "% done" fields added on issues
* project calendar added
* gantt chart added (exportable to pdf)
* multiple file upload for issues attachments
* user custom field displayed on account/show
* default configuration improved (default roles, trackers, status, permissions and workflows)
* fixed: project settings now displayed according to user's permissions

git-svn-id: http://redmine.rubyforge.org/svn/trunk@44 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-12 18:50:30 +00:00
Jean-Philippe Lang
6707034768 english help translation added
thx to Karim Trott

git-svn-id: http://redmine.rubyforge.org/svn/trunk@43 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-07 17:44:50 +00:00
Jean-Philippe Lang
ef99d3c4ce english help translation added
thx to Karim Trott

git-svn-id: http://redmine.rubyforge.org/svn/trunk@42 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-07 17:44:47 +00:00
Jean-Philippe Lang
4c9f27e550 git-svn-id: http://redmine.rubyforge.org/svn/trunk@41 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-11-05 20:31:29 +00:00
Jean-Philippe Lang
55dded8e5f multiple file upload
git-svn-id: http://redmine.rubyforge.org/svn/trunk@40 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-11-05 18:38:20 +00:00
Jean-Philippe Lang
0778ac8c1b git-svn-id: http://redmine.rubyforge.org/svn/trunk@39 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-11-05 16:52:22 +00:00
Jean-Philippe Lang
3628515b31 git-svn-id: http://redmine.rubyforge.org/svn/trunk@38 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-11-05 16:49:27 +00:00
Jean-Philippe Lang
51e0989c49 * single/multiple issues pdf export added
* new filter in issues list: "Author"
* option to set number of results per page on issues list
* localized csv separator (comma/semicolon)
* csv output encoded to ISO-8859-1
* fixed: custom fields not in csv exports

git-svn-id: http://redmine.rubyforge.org/svn/trunk@37 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-10-22 14:47:25 +00:00
Jean-Philippe Lang
79cbe8d199 issues/add_note added
git-svn-id: http://redmine.rubyforge.org/svn/trunk@36 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-10-21 16:07:34 +00:00
Jean-Philippe Lang
a2d4ca095a issues report by author
git-svn-id: http://redmine.rubyforge.org/svn/trunk@35 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-10-21 15:41:42 +00:00
Jean-Philippe Lang
7473be4072 issues reports improvements
git-svn-id: http://redmine.rubyforge.org/svn/trunk@34 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-10-21 12:57:19 +00:00
Jean-Philippe Lang
6a0022d7a1 ajaxified paginators
git-svn-id: http://redmine.rubyforge.org/svn/trunk@33 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-10-21 10:38:53 +00:00
Jean-Philippe Lang
e4d4a12c6a git-svn-id: http://redmine.rubyforge.org/svn/trunk@32 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-10-15 14:33:04 +00:00
Jean-Philippe Lang
4a1cfb3df9 git-svn-id: http://redmine.rubyforge.org/svn/trunk@31 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-10-15 09:00:05 +00:00
Jean-Philippe Lang
b00769f606 git-svn-id: http://redmine.rubyforge.org/svn/trunk@30 e93f8b46-1217-0410-a6f0-8f06a7374b81 2006-10-14 07:47:25 +00:00
Jean-Philippe Lang
6e367354e9 date picker
git-svn-id: http://redmine.rubyforge.org/svn/trunk@29 e93f8b46-1217-0410-a6f0-8f06a7374b81
2006-10-14 07:44:00 +00:00
638 changed files with 19486 additions and 2277 deletions

View File

@@ -27,6 +27,9 @@ class AccountController < ApplicationController
# Show user's account
def show
@user = User.find(params[:id])
@custom_values = @user.custom_values.find(:all, :include => :custom_field)
rescue ActiveRecord::RecordNotFound
render_404
end
# Login request and validation
@@ -39,7 +42,7 @@ class AccountController < ApplicationController
user = User.try_to_login(params[:login], params[:password])
if user
self.logged_in_user = user
redirect_back_or_default :controller => 'account', :action => 'my_page'
redirect_back_or_default :controller => 'my', :action => 'page'
else
flash.now[:notice] = l(:notice_account_invalid_creditentials)
end
@@ -51,41 +54,6 @@ class AccountController < ApplicationController
self.logged_in_user = nil
redirect_to :controller => ''
end
# Show logged in user's page
def my_page
@user = self.logged_in_user
@reported_issues = Issue.find(:all, :conditions => ["author_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
@assigned_issues = Issue.find(:all, :conditions => ["assigned_to_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
end
# Edit logged in user's account
def my_account
@user = self.logged_in_user
if request.post? and @user.update_attributes(@params[:user])
set_localization
flash.now[:notice] = l(:notice_account_updated)
self.logged_in_user.reload
end
end
# Change logged in user's password
def change_password
@user = self.logged_in_user
flash[:notice] = l(:notice_can_t_change_password) and redirect_to :action => 'my_account' and return if @user.auth_source_id
if @user.check_password?(@params[:password])
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
if @user.save
flash[:notice] = l(:notice_account_password_updated)
else
render :action => 'my_account'
return
end
else
flash[:notice] = l(:notice_account_wrong_password)
end
redirect_to :action => 'my_account'
end
# Enable user to choose a new password
def lost_password

View File

@@ -31,10 +31,12 @@ class AdminController < ApplicationController
@project_count = Project.count
@project_pages = Paginator.new self, @project_count,
15,
@params['page']
params['page']
@projects = Project.find :all, :order => sort_clause,
:limit => @project_pages.items_per_page,
:offset => @project_pages.current.offset
:offset => @project_pages.current.offset
render :action => "projects", :layout => false if request.xhr?
end
def mail_options

View File

@@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base
def logged_in_user
if session[:user_id]
@logged_in_user ||= User.find(session[:user_id], :include => :memberships)
@logged_in_user ||= User.find(session[:user_id])
else
nil
end
@@ -41,7 +41,7 @@ class ApplicationController < ActionController::Base
if self.logged_in_user and self.logged_in_user.language and !self.logged_in_user.language.empty? and GLoc.valid_languages.include? self.logged_in_user.language.to_sym
self.logged_in_user.language
elsif request.env['HTTP_ACCEPT_LANGUAGE']
accept_lang = HTTPUtils.parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
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
end
@@ -71,9 +71,9 @@ class ApplicationController < ActionController::Base
end
# authorizes the user for the requested action.
def authorize
def authorize(ctrl = params[:controller], action = params[:action])
# check if action is allowed on public projects
if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ @params[:controller], @params[:action] ]
if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ ctrl, action ]
return true
end
# if action is not public, force login
@@ -82,17 +82,17 @@ class ApplicationController < ActionController::Base
return true if self.logged_in_user.admin?
# if not admin, check membership permission
@user_membership ||= Member.find(:first, :conditions => ["user_id=? and project_id=?", self.logged_in_user.id, @project.id])
if @user_membership and Permission.allowed_to_role( "%s/%s" % [ @params[:controller], @params[:action] ], @user_membership.role_id )
if @user_membership and Permission.allowed_to_role( "%s/%s" % [ ctrl, action ], @user_membership.role_id )
return true
end
render :nothing => true, :status => 403
false
end
# store current uri in session.
# return to this location by calling redirect_back_or_default
def store_location
session[:return_to] = @request.request_uri
session[:return_to] = request.request_uri
end
# move to the last store_location call or to the passed default one
@@ -103,5 +103,30 @@ class ApplicationController < ActionController::Base
redirect_to_url session[:return_to]
session[:return_to] = nil
end
end
def render_404
@html_title = "404"
render :template => "common/404", :layout => true, :status => 404
return false
end
# qvalues http header parser
# code taken from webrick
def parse_qvalues(value)
tmp = []
if value
parts = value.split(/,\s*/)
parts.each {|part|
if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
val = m[1]
q = (m[2] or 1).to_f
tmp.push([val, q])
end
}
tmp = tmp.sort_by{|val, q| -q}
tmp.collect!{|val, q| val}
end
return tmp
end
end

View File

@@ -21,7 +21,7 @@ class AuthSourcesController < ApplicationController
def index
list
render :action => 'list'
render :action => 'list' unless request.xhr?
end
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
@@ -30,6 +30,7 @@ class AuthSourcesController < ApplicationController
def list
@auth_source_pages, @auth_sources = paginate :auth_sources, :per_page => 10
render :action => "list", :layout => false if request.xhr?
end
def new

View File

@@ -21,11 +21,12 @@ class CustomFieldsController < ApplicationController
def index
list
render :action => 'list'
render :action => 'list' unless request.xhr?
end
def list
@custom_field_pages, @custom_fields = paginate :custom_fields, :per_page => 15
@custom_field_pages, @custom_fields = paginate :custom_fields, :per_page => 15
render :action => "list", :layout => false if request.xhr?
end
def new

View File

@@ -40,15 +40,15 @@ class DocumentsController < ApplicationController
@attachment = @document.attachments.find(params[:attachment_id])
@attachment.increment_download
send_file @attachment.diskfile, :filename => @attachment.filename
rescue
render_404
end
def add_attachment
# Save the attachment
if params[:attachment][:file].size > 0
@attachment = @document.attachments.build(params[:attachment])
@attachment.author_id = self.logged_in_user.id if self.logged_in_user
@attachment.save
end
# Save the attachments
params[:attachments].each { |a|
Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
} if params[:attachments] and params[:attachments].is_a? Array
redirect_to :action => 'show', :id => @document
end
@@ -61,5 +61,7 @@ private
def find_project
@document = Document.find(params[:id])
@project = @document.project
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -0,0 +1,25 @@
# redMine - project management software
# Copyright (C) 2006 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 FeedsController < ApplicationController
session :off
def news
@news = News.find :all, :order => 'news.created_on DESC', :limit => 10, :include => [ :author, :project ]
@headers["Content-Type"] = "application/rss+xml"
end
end

View File

@@ -23,20 +23,20 @@ class HelpController < ApplicationController
# displays help page for the requested controller/action
def index
# select help page to display
if @params[:ctrl] and @help_config['pages'][@params[:ctrl]]
if @params[:page] and @help_config['pages'][@params[:ctrl]][@params[:page]]
template = @help_config['pages'][@params[:ctrl]][@params[:page]]
if params[:ctrl] and @help_config['pages'][params[:ctrl]]
if params[:page] and @help_config['pages'][params[:ctrl]][params[:page]]
template = @help_config['pages'][params[:ctrl]][params[:page]]
else
template = @help_config['pages'][@params[:ctrl]]['index']
template = @help_config['pages'][params[:ctrl]]['index']
end
end
# choose language according to available help translations
lang = (@help_config['langs'].include? current_language) ? current_language : @help_config['langs'].first
lang = (@help_config['langs'].include? current_language.to_s) ? current_language.to_s : @help_config['langs'].first
if template
redirect_to "/manual/#{lang}/#{template}"
else
redirect_to "/manual/#{lang}/"
redirect_to "/manual/#{lang}/index.html"
end
end

View File

@@ -35,8 +35,10 @@ class IssueCategoriesController < ApplicationController
end
private
def find_project
def find_project
@category = IssueCategory.find(params[:id])
@project = @category.project
end
@project = @category.project
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -21,11 +21,12 @@ class IssueStatusesController < ApplicationController
def index
list
render :action => 'list'
render :action => 'list' unless request.xhr?
end
def list
@issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 10
@issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 10
render :action => "list", :layout => false if request.xhr?
end
def new

View File

@@ -16,15 +16,30 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssuesController < ApplicationController
layout 'base'
layout 'base', :except => :export_pdf
before_filter :find_project, :authorize
helper :custom_fields
include CustomFieldsHelper
helper :ifpdf
include IfpdfHelper
def show
@status_options = @issue.status.workflows.find(:all, :include => :new_status, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
@custom_values = @issue.custom_values.find(:all, :include => :custom_field)
@journals_count = @issue.journals.count
@journals = @issue.journals.find(:all, :include => [:user, :details], :limit => 15, :order => "journals.created_on desc")
end
def history
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "journals.created_on desc")
@journals_count = @journals.length
end
def export_pdf
@custom_values = @issue.custom_values.find(:all, :include => :custom_field)
@options_for_rfpdf ||= {}
@options_for_rfpdf[:file_name] = "#{@project.name}_#{@issue.long_id}.pdf"
end
def edit
@@ -33,6 +48,7 @@ class IssuesController < ApplicationController
@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
begin
@issue.init_journal(self.logged_in_user)
# Retrieve custom fields and values
@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
@@ -47,20 +63,41 @@ class IssuesController < ApplicationController
end
end
end
def add_note
unless params[:notes].empty?
journal = @issue.init_journal(self.logged_in_user, params[:notes])
#@history = @issue.histories.build(params[:history])
#@history.author_id = self.logged_in_user.id if self.logged_in_user
#@history.status = @issue.status
if @issue.save
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @issue
return
end
end
show
render :action => 'show'
end
def change_status
@history = @issue.histories.build(params[:history])
#@history = @issue.histories.build(params[:history])
@status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
@new_status = IssueStatus.find(params[:new_status_id])
if params[:confirm]
begin
@history.author_id = self.logged_in_user.id if self.logged_in_user
@issue.status = @history.status
@issue.fixed_version_id = (params[:issue][:fixed_version_id])
@issue.assigned_to_id = (params[:issue][:assigned_to_id])
@issue.lock_version = (params[:issue][:lock_version])
if @issue.save
#@history.author_id = self.logged_in_user.id if self.logged_in_user
#@issue.status = @history.status
#@issue.fixed_version_id = (params[:issue][:fixed_version_id])
#@issue.assigned_to_id = (params[:issue][:assigned_to_id])
#@issue.done_ratio = (params[:issue][:done_ratio])
#@issue.lock_version = (params[:issue][:lock_version])
journal = @issue.init_journal(self.logged_in_user, params[:notes])
@issue.status = @new_status
if @issue.update_attributes(params[:issue])
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_change_status(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @issue
end
rescue ActiveRecord::StaleObjectError
@@ -77,12 +114,11 @@ class IssuesController < ApplicationController
end
def add_attachment
# Save the attachment
if params[:attachment][:file].size > 0
@attachment = @issue.attachments.build(params[:attachment])
@attachment.author_id = self.logged_in_user.id if self.logged_in_user
# Save the attachments
params[:attachments].each { |a|
@attachment = @issue.attachments.build(:file => a, :author => self.logged_in_user) unless a.size == 0
@attachment.save
end
} if params[:attachments] and params[:attachments].is_a? Array
redirect_to :action => 'show', :id => @issue
end
@@ -95,11 +131,16 @@ class IssuesController < ApplicationController
def download
@attachment = @issue.attachments.find(params[:attachment_id])
send_file @attachment.diskfile, :filename => @attachment.filename
rescue
render_404
end
private
def find_project
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
@project = @issue.project
@html_title = "#{@project.name} - #{@issue.tracker.name} ##{@issue.id}"
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -33,9 +33,11 @@ class MembersController < ApplicationController
end
private
def find_project
def find_project
@member = Member.find(params[:id])
@project = @member.project
end
@project = @member.project
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -0,0 +1,131 @@
# redMine - project management software
# Copyright (C) 2006 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 MyController < ApplicationController
layout 'base'
before_filter :require_login
BLOCKS = { 'issues_assigned_to_me' => :label_assigned_to_me_issues,
'issues_reported_by_me' => :label_reported_issues,
'latest_news' => :label_news_latest,
'calendar' => :label_calendar,
'documents' => :label_document_plural
}.freeze
verify :xhr => true,
:session => :page_layout,
:only => [:add_block, :remove_block, :order_blocks]
def index
page
render :action => 'page'
end
# Show user's page
def page
@user = self.logged_in_user
@blocks = @user.pref[:my_page_layout] || { 'left' => ['issues_assigned_to_me'], 'right' => ['issues_reported_by_me'] }
end
# Edit user's account
def account
@user = self.logged_in_user
@pref = @user.pref
@user.attributes = params[:user]
@user.pref.attributes = params[:pref]
if request.post? and @user.save
set_localization
flash.now[:notice] = l(:notice_account_updated)
self.logged_in_user.reload
end
end
# Change user's password
def change_password
@user = self.logged_in_user
flash[:notice] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id
if @user.check_password?(params[:password])
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
if @user.save
flash[:notice] = l(:notice_account_password_updated)
else
render :action => 'account'
return
end
else
flash[:notice] = l(:notice_account_wrong_password)
end
redirect_to :action => 'account'
end
# User's page layout configuration
def page_layout
@user = self.logged_in_user
@blocks = @user.pref[:my_page_layout] || { 'left' => ['issues_assigned_to_me'], 'right' => ['issues_reported_by_me'] }
session[:page_layout] = @blocks
%w(top left right).each {|f| session[:page_layout][f] ||= [] }
@block_options = []
BLOCKS.each {|k, v| @block_options << [l(v), k]}
end
# Add a block to user's page
# The block is added on top of the page
# params[:block] : id of the block to add
def add_block
@user = self.logged_in_user
block = params[:block]
# remove if already present in a group
%w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
# add it on top
session[:page_layout]['top'].unshift block
render :partial => "block", :locals => {:user => @user, :block_name => block}
end
# Remove a block to user's page
# params[:block] : id of the block to remove
def remove_block
block = params[:block]
# remove block in all groups
%w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
render :nothing => true
end
# Change blocks order on user's page
# params[:group] : group to order (top, left or right)
# params[:list-(top|left|right)] : array of block ids of the group
def order_blocks
group = params[:group]
group_items = params["list-#{group}"]
if group_items and group_items.is_a? Array
# remove group blocks if they are presents in other groups
%w(top left right).each {|f|
session[:page_layout][f] = (session[:page_layout][f] || []) - group_items
}
session[:page_layout][group] = group_items
end
render :nothing => true
end
# Save user's page layout
def page_layout_save
@user = self.logged_in_user
@user.pref[:my_page_layout] = session[:page_layout] if session[:page_layout]
@user.pref.save
session[:page_layout] = nil
redirect_to :action => 'page'
end
end

View File

@@ -27,7 +27,23 @@ class NewsController < ApplicationController
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @news
end
end
def add_comment
@comment = Comment.new(params[:comment])
@comment.author = logged_in_user
if @news.comments << @comment
flash[:notice] = l(:label_comment_added)
redirect_to :action => 'show', :id => @news
else
render :action => 'show'
end
end
def destroy_comment
@news.comments.find(params[:comment_id]).destroy
redirect_to :action => 'show', :id => @news
end
def destroy
@news.destroy
@@ -35,8 +51,10 @@ class NewsController < ApplicationController
end
private
def find_project
def find_project
@news = News.find(params[:id])
@project = @news.project
end
@project = @news.project
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -0,0 +1,532 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 ProjectsController < ApplicationController
layout 'base'
before_filter :find_project, :authorize, :except => [ :index, :list, :add ]
before_filter :require_admin, :only => [ :add, :destroy ]
helper :sort
include SortHelper
helper :custom_fields
include CustomFieldsHelper
helper :ifpdf
include IfpdfHelper
helper IssuesHelper
helper :queries
include QueriesHelper
def index
list
render :action => 'list' unless request.xhr?
end
# Lists public projects
def list
sort_init 'name', 'asc'
sort_update
@project_count = Project.count(["is_public=?", true])
@project_pages = Paginator.new self, @project_count,
15,
params['page']
@projects = Project.find :all, :order => sort_clause,
:conditions => ["is_public=?", true],
:limit => @project_pages.items_per_page,
:offset => @project_pages.current.offset
render :action => "list", :layout => false if request.xhr?
end
# Add a new project
def add
@custom_fields = IssueCustomField.find(:all)
@root_projects = Project.find(:all, :conditions => "parent_id is null")
@project = Project.new(params[:project])
if request.get?
@custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
else
@project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
@custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
@project.custom_values = @custom_values
if params[:repository_enabled] && params[:repository_enabled] == "1"
@project.repository = Repository.new
@project.repository.attributes = params[:repository]
end
if @project.save
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'admin', :action => 'projects'
end
end
end
# Show @project
def show
@custom_values = @project.custom_values.find(:all, :include => :custom_field)
@members = @project.members.find(:all, :include => [:user, :role])
@subprojects = @project.children if @project.children_count > 0
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "news.created_on DESC")
@trackers = Tracker.find(:all)
end
def settings
@root_projects = Project::find(:all, :conditions => ["parent_id is null and id <> ?", @project.id])
@custom_fields = IssueCustomField.find(:all)
@issue_category ||= IssueCategory.new
@member ||= @project.members.new
@roles = Role.find(:all)
@users = User.find(:all) - @project.members.find(:all, :include => :user).collect{|m| m.user }
@custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
end
# Edit @project
def edit
if request.post?
@project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
if params[:custom_fields]
@custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
@project.custom_values = @custom_values
end
if params[:repository_enabled]
case params[:repository_enabled]
when "0"
@project.repository = nil
when "1"
@project.repository ||= Repository.new
@project.repository.attributes = params[:repository]
end
end
@project.attributes = params[:project]
if @project.save
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project
else
settings
render :action => 'settings'
end
end
end
# Delete @project
def destroy
if request.post? and params[:confirm]
@project.destroy
redirect_to :controller => 'admin', :action => 'projects'
end
end
# Add a new issue category to @project
def add_issue_category
if request.post?
@issue_category = @project.issue_categories.build(params[:issue_category])
if @issue_category.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'settings', :id => @project
else
settings
render :action => 'settings'
end
end
end
# Add a new version to @project
def add_version
@version = @project.versions.build(params[:version])
if request.post? and @version.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'settings', :id => @project
end
end
# Add a new member to @project
def add_member
@member = @project.members.build(params[:member])
if request.post?
if @member.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'settings', :id => @project
else
settings
render :action => 'settings'
end
end
end
# Show members list of @project
def list_members
@members = @project.members
end
# Add a new document to @project
def add_document
@categories = Enumeration::get_values('DCAT')
@document = @project.documents.build(params[:document])
if request.post? and @document.save
# Save the attachments
params[:attachments].each { |a|
Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
} if params[:attachments] and params[:attachments].is_a? Array
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list_documents', :id => @project
end
end
# Show documents list of @project
def list_documents
@documents = @project.documents.find :all, :include => :category
end
# Add a new issue to @project
def add_issue
@tracker = Tracker.find(params[:tracker_id])
@priorities = Enumeration::get_values('IPRI')
@issue = Issue.new(:project => @project, :tracker => @tracker)
if request.get?
@issue.start_date = Date.today
@custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) }
else
@issue.attributes = params[:issue]
@issue.author_id = self.logged_in_user.id if self.logged_in_user
# Multiple file upload
@attachments = []
params[:attachments].each { |a|
@attachments << Attachment.new(:container => @issue, :file => a, :author => logged_in_user) unless a.size == 0
} if params[:attachments] and params[:attachments].is_a? Array
@custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
@issue.custom_values = @custom_values
if @issue.save
@attachments.each(&:save)
flash[:notice] = l(:notice_successful_create)
Mailer.deliver_issue_add(@issue) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'list_issues', :id => @project
end
end
end
# Show filtered/sorted issues list of @project
def list_issues
sort_init 'issues.id', 'desc'
sort_update
retrieve_query
@results_per_page_options = [ 15, 25, 50, 100 ]
if params[:per_page] and @results_per_page_options.include? params[:per_page].to_i
@results_per_page = params[:per_page].to_i
session[:results_per_page] = @results_per_page
else
@results_per_page = session[:results_per_page] || 25
end
if @query.valid?
@issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
@issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
@issues = Issue.find :all, :order => sort_clause,
:include => [ :author, :status, :tracker, :project ],
:conditions => @query.statement,
:limit => @issue_pages.items_per_page,
:offset => @issue_pages.current.offset
end
@trackers = Tracker.find :all
render :layout => false if request.xhr?
end
# Export filtered/sorted issues list to CSV
def export_issues_csv
sort_init 'issues.id', 'desc'
sort_update
retrieve_query
render :action => 'list_issues' and return unless @query.valid?
@issues = Issue.find :all, :order => sort_clause,
:include => [ :author, :status, :tracker, :project, :custom_values ],
:conditions => @query.statement
ic = Iconv.new('ISO-8859-1', 'UTF-8')
export = StringIO.new
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
# csv header fields
headers = [ "#", l(:field_status), l(:field_tracker), l(:field_subject), l(:field_author), l(:field_created_on), l(:field_updated_on) ]
for custom_field in @project.all_custom_fields
headers << custom_field.name
end
csv << headers.collect {|c| ic.iconv(c) }
# csv lines
@issues.each do |issue|
fields = [issue.id, issue.status.name, issue.tracker.name, issue.subject, issue.author.display_name, l_datetime(issue.created_on), l_datetime(issue.updated_on)]
for custom_field in @project.all_custom_fields
fields << (show_value issue.custom_value_for(custom_field))
end
csv << fields.collect {|c| ic.iconv(c.to_s) }
end
end
export.rewind
send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
end
# Export filtered/sorted issues to PDF
def export_issues_pdf
sort_init 'issues.id', 'desc'
sort_update
retrieve_query
render :action => 'list_issues' and return unless @query.valid?
@issues = Issue.find :all, :order => sort_clause,
:include => [ :author, :status, :tracker, :project, :custom_values ],
:conditions => @query.statement
@options_for_rfpdf ||= {}
@options_for_rfpdf[:file_name] = "export.pdf"
render :layout => false
end
def move_issues
@issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
redirect_to :action => 'list_issues', :id => @project and return unless @issues
@projects = []
# find projects to which the user is allowed to move the issue
@logged_in_user.memberships.each {|m| @projects << m.project if Permission.allowed_to_role("projects/move_issues", m.role_id)}
# issue can be moved to any tracker
@trackers = Tracker.find(:all)
if request.post? and params[:new_project_id] and params[:new_tracker_id]
new_project = Project.find(params[:new_project_id])
new_tracker = Tracker.find(params[:new_tracker_id])
@issues.each { |i|
# project dependent properties
unless i.project_id == new_project.id
i.category = nil
i.fixed_version = nil
end
# move the issue
i.project = new_project
i.tracker = new_tracker
i.save
}
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list_issues', :id => @project
end
end
def add_query
@query = Query.new(params[:query])
@query.project = @project
@query.user = logged_in_user
params[:fields].each do |field|
@query.add_filter(field, params[:operators][field], params[:values][field])
end if params[:fields]
if request.post? and @query.save
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'reports', :action => 'issue_report', :id => @project
end
render :layout => false if request.xhr?
end
# Add a news to @project
def add_news
@news = News.new(:project => @project)
if request.post?
@news.attributes = params[:news]
@news.author_id = self.logged_in_user.id if self.logged_in_user
if @news.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list_news', :id => @project
end
end
end
# Show news list of @project
def list_news
@news_pages, @news = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "news.created_on DESC"
render :action => "list_news", :layout => false if request.xhr?
end
def add_file
if request.post?
@version = @project.versions.find_by_id(params[:version_id])
# Save the attachments
params[:attachments].each { |a|
Attachment.create(:container => @version, :file => a, :author => logged_in_user) unless a.size == 0
} if params[:attachments] and params[:attachments].is_a? Array
redirect_to :controller => 'projects', :action => 'list_files', :id => @project
end
@versions = @project.versions
end
def list_files
@versions = @project.versions
end
# Show changelog for @project
def changelog
@trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true])
if request.get?
@selected_tracker_ids = @trackers.collect {|t| t.id.to_s }
else
@selected_tracker_ids = params[:tracker_ids].collect { |id| id.to_i.to_s } if params[:tracker_ids] and params[:tracker_ids].is_a? Array
end
@selected_tracker_ids ||= []
@fixed_issues = @project.issues.find(:all,
:include => [ :fixed_version, :status, :tracker ],
:conditions => [ "issue_statuses.is_closed=? and issues.tracker_id in (#{@selected_tracker_ids.join(',')}) and issues.fixed_version_id is not null", true],
:order => "versions.effective_date DESC, issues.id DESC"
) unless @selected_tracker_ids.empty?
@fixed_issues ||= []
end
def activity
if params[:year] and params[:year].to_i > 1900
@year = params[:year].to_i
if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
@month = params[:month].to_i
end
end
@year ||= Date.today.year
@month ||= Date.today.month
@date_from = Date.civil(@year, @month, 1)
@date_to = (@date_from >> 1)-1
@events_by_day = {}
unless params[:show_issues] == "0"
@project.issues.find(:all, :include => [:author, :status], :conditions => ["issues.created_on>=? and issues.created_on<=?", @date_from, @date_to] ).each { |i|
@events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i
}
@show_issues = 1
end
unless params[:show_news] == "0"
@project.news.find(:all, :conditions => ["news.created_on>=? and news.created_on<=?", @date_from, @date_to], :include => :author ).each { |i|
@events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i
}
@show_news = 1
end
unless params[:show_files] == "0"
Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN versions ON versions.id = attachments.container_id", :conditions => ["attachments.container_type='Version' and versions.project_id=? and attachments.created_on>=? and attachments.created_on<=?", @project.id, @date_from, @date_to], :include => :author ).each { |i|
@events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i
}
@show_files = 1
end
unless params[:show_documents] == "0"
@project.documents.find(:all, :conditions => ["documents.created_on>=? and documents.created_on<=?", @date_from, @date_to] ).each { |i|
@events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i
}
Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN documents ON documents.id = attachments.container_id", :conditions => ["attachments.container_type='Document' and documents.project_id=? and attachments.created_on>=? and attachments.created_on<=?", @project.id, @date_from, @date_to], :include => :author ).each { |i|
@events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i
}
@show_documents = 1
end
render :layout => false if request.xhr?
end
def calendar
if params[:year] and params[:year].to_i > 1900
@year = params[:year].to_i
if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
@month = params[:month].to_i
end
end
@year ||= Date.today.year
@month ||= Date.today.month
@date_from = Date.civil(@year, @month, 1)
@date_to = (@date_from >> 1)-1
# start on monday
@date_from = @date_from - (@date_from.cwday-1)
# finish on sunday
@date_to = @date_to + (7-@date_to.cwday)
@issues = @project.issues.find(:all, :include => :tracker, :conditions => ["((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?))", @date_from, @date_to, @date_from, @date_to])
render :layout => false if request.xhr?
end
def gantt
if params[:year] and params[:year].to_i >0
@year_from = params[:year].to_i
if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
@month_from = params[:month].to_i
else
@month_from = 1
end
else
@month_from ||= (Date.today << 1).month
@year_from ||= (Date.today << 1).year
end
@zoom = (params[:zoom].to_i > 0 and params[:zoom].to_i < 5) ? params[:zoom].to_i : 2
@months = (params[:months].to_i > 0 and params[:months].to_i < 25) ? params[:months].to_i : 6
@date_from = Date.civil(@year_from, @month_from, 1)
@date_to = (@date_from >> @months) - 1
@issues = @project.issues.find(:all, :order => "start_date, due_date", :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)", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to])
if params[:output]=='pdf'
@options_for_rfpdf ||= {}
@options_for_rfpdf[:file_name] = "gantt.pdf"
render :template => "projects/gantt.rfpdf", :layout => false
else
render :template => "projects/gantt.rhtml"
end
end
private
# Find project of id params[:id]
# if not found, redirect to project list
# Used as a before_filter
def find_project
@project = Project.find(params[:id])
@html_title = @project.name
rescue ActiveRecord::RecordNotFound
render_404
end
# Retrieve query from session or build a new query
def retrieve_query
if params[:query_id]
@query = @project.queries.find(params[:query_id])
else
if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id
# Give it a name, required to be valid
@query = Query.new(:name => "_")
@query.project = @project
if params[:fields] and params[:fields].is_a? Array
params[:fields].each do |field|
@query.add_filter(field, params[:operators][field], params[:values][field])
end
else
@query.available_filters.keys.each do |field|
@query.add_short_filter(field, params[field]) if params[field]
end
end
session[:query] = @query
else
@query = session[:query]
end
end
end
end

View File

@@ -0,0 +1,51 @@
# redMine - project management software
# Copyright (C) 2006 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 QueriesController < ApplicationController
layout 'base'
before_filter :require_login, :find_query
def edit
if request.post?
@query.filters = {}
params[:fields].each do |field|
@query.add_filter(field, params[:operators][field], params[:values][field])
end if params[:fields]
@query.attributes = params[:query]
if @query.save
flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => @query
end
end
end
def destroy
@query.destroy if request.post?
redirect_to :controller => 'reports', :action => 'issue_report', :id => @project
end
private
def find_query
@query = Query.find(params[:id])
@project = @query.project
# check if user is allowed to manage queries (same permission as add_query)
authorize('projects', 'add_query')
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -0,0 +1,167 @@
# redMine - project management software
# Copyright (C) 2006 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 ReportsController < ApplicationController
layout 'base'
before_filter :find_project, :authorize
def issue_report
@statuses = IssueStatus.find :all
case params[:detail]
when "tracker"
@field = "tracker_id"
@rows = Tracker.find :all
@data = issues_by_tracker
@report_title = l(:field_tracker)
render :template => "reports/issue_report_details"
when "priority"
@field = "priority_id"
@rows = Enumeration::get_values('IPRI')
@data = issues_by_priority
@report_title = l(:field_priority)
render :template => "reports/issue_report_details"
when "category"
@field = "category_id"
@rows = @project.issue_categories
@data = issues_by_category
@report_title = l(:field_category)
render :template => "reports/issue_report_details"
when "author"
@field = "author_id"
@rows = @project.members.collect { |m| m.user }
@data = issues_by_author
@report_title = l(:field_author)
render :template => "reports/issue_report_details"
else
@queries = @project.queries.find :all, :conditions => ["is_public=? or user_id=?", true, (logged_in_user ? logged_in_user.id : 0)]
@trackers = Tracker.find(:all)
@priorities = Enumeration::get_values('IPRI')
@categories = @project.issue_categories
@authors = @project.members.collect { |m| m.user }
issues_by_tracker
issues_by_priority
issues_by_category
issues_by_author
render :template => "reports/issue_report"
end
end
def delays
@trackers = Tracker.find(:all)
if request.get?
@selected_tracker_ids = @trackers.collect {|t| t.id.to_s }
else
@selected_tracker_ids = params[:tracker_ids].collect { |id| id.to_i.to_s } if params[:tracker_ids] and params[:tracker_ids].is_a? Array
end
@selected_tracker_ids ||= []
@raw =
ActiveRecord::Base.connection.select_all("SELECT datediff( a.created_on, b.created_on ) as delay, count(a.id) as total
FROM issue_histories a, issue_histories b, issues i
WHERE a.status_id =5
AND a.issue_id = b.issue_id
AND a.issue_id = i.id
AND i.tracker_id in (#{@selected_tracker_ids.join(',')})
AND b.id = (
SELECT min( c.id )
FROM issue_histories c
WHERE b.issue_id = c.issue_id )
GROUP BY delay") unless @selected_tracker_ids.empty?
@raw ||=[]
@x_from = 0
@x_to = 0
@y_from = 0
@y_to = 0
@sum_total = 0
@sum_delay = 0
@raw.each do |r|
@x_to = [r['delay'].to_i, @x_to].max
@y_to = [r['total'].to_i, @y_to].max
@sum_total = @sum_total + r['total'].to_i
@sum_delay = @sum_delay + r['total'].to_i * r['delay'].to_i
end
end
private
# Find project of id params[:id]
def find_project
@project = Project.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
def issues_by_tracker
@issues_by_tracker ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
t.id as tracker_id,
count(i.id) as total
from
issues i, issue_statuses s, trackers t
where
i.status_id=s.id
and i.tracker_id=t.id
and i.project_id=#{@project.id}
group by s.id, s.is_closed, t.id")
end
def issues_by_priority
@issues_by_priority ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
p.id as priority_id,
count(i.id) as total
from
issues i, issue_statuses s, enumerations p
where
i.status_id=s.id
and i.priority_id=p.id
and i.project_id=#{@project.id}
group by s.id, s.is_closed, p.id")
end
def issues_by_category
@issues_by_category ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
c.id as category_id,
count(i.id) as total
from
issues i, issue_statuses s, issue_categories c
where
i.status_id=s.id
and i.category_id=c.id
and i.project_id=#{@project.id}
group by s.id, s.is_closed, c.id")
end
def issues_by_author
@issues_by_author ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
a.id as author_id,
count(i.id) as total
from
issues i, issue_statuses s, users a
where
i.status_id=s.id
and i.author_id=a.id
and i.project_id=#{@project.id}
group by s.id, s.is_closed, a.id")
end
end

View File

@@ -0,0 +1,74 @@
# redMine - project management software
# Copyright (C) 2006 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 RepositoriesController < ApplicationController
layout 'base'
before_filter :find_project, :authorize
def show
@entries = @repository.scm.entries('')
show_error and return unless @entries
@latest_revision = @entries.revisions.latest
end
def browse
@entries = @repository.scm.entries(@path, @rev)
show_error and return unless @entries
end
def revisions
@entry = @repository.scm.entry(@path, @rev)
@revisions = @repository.scm.revisions(@path, @rev)
show_error and return unless @entry && @revisions
end
def entry
if 'raw' == params[:format]
content = @repository.scm.cat(@path, @rev)
show_error and return unless content
send_data content, :filename => @path.split('/').last
end
end
def revision
@revisions = @repository.scm.revisions '', @rev, @rev, :with_paths => true
show_error and return unless @revisions
@revision = @revisions.first
end
def diff
@rev_to = params[:rev_to] || (@rev-1)
@diff = @repository.scm.diff(params[:path], @rev, @rev_to)
show_error and return unless @diff
end
private
def find_project
@project = Project.find(params[:id])
@repository = @project.repository
@path = params[:path].squeeze('/').gsub(/^\//, '') if params[:path]
@path ||= ''
@rev = params[:rev].to_i if params[:rev] and params[:rev].to_i > 0
rescue ActiveRecord::RecordNotFound
render_404
end
def show_error
flash.now[:notice] = l(:notice_scm_error)
render :nothing => true, :layout => true
end
end

View File

@@ -21,17 +21,18 @@ class RolesController < ApplicationController
def index
list
render :action => 'list'
render :action => 'list' unless request.xhr?
end
def list
@role_pages, @roles = paginate :roles, :per_page => 10
@role_pages, @roles = paginate :roles, :per_page => 10
render :action => "list", :layout => false if request.xhr?
end
def new
@role = Role.new(params[:role])
if request.post?
@role.permissions = Permission.find(@params[:permission_ids]) if @params[:permission_ids]
@role.permissions = Permission.find(params[:permission_ids]) if params[:permission_ids]
if @role.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list'
@@ -43,7 +44,7 @@ class RolesController < ApplicationController
def edit
@role = Role.find(params[:id])
if request.post? and @role.update_attributes(params[:role])
@role.permissions = Permission.find(@params[:permission_ids] || [])
@role.permissions = Permission.find(params[:permission_ids] || [])
Permission.allowed_to_role_expired
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list'
@@ -76,8 +77,8 @@ class RolesController < ApplicationController
flash[:notice] = l(:notice_successful_update)
end
end
@roles = Role.find_all
@trackers = Tracker.find_all
@roles = Role.find :all
@trackers = Tracker.find :all
@statuses = IssueStatus.find(:all, :include => :workflows)
end
end

View File

@@ -21,14 +21,15 @@ class TrackersController < ApplicationController
def index
list
render :action => 'list'
render :action => 'list' unless request.xhr?
end
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :list }
def list
@tracker_pages, @trackers = paginate :trackers, :per_page => 10
@tracker_pages, @trackers = paginate :trackers, :per_page => 10
render :action => "list", :layout => false if request.xhr?
end
def new

View File

@@ -26,7 +26,7 @@ class UsersController < ApplicationController
def index
list
render :action => 'list'
render :action => 'list' unless request.xhr?
end
def list
@@ -35,10 +35,12 @@ class UsersController < ApplicationController
@user_count = User.count
@user_pages = Paginator.new self, @user_count,
15,
@params['page']
params['page']
@users = User.find :all,:order => sort_clause,
:limit => @user_pages.items_per_page,
:offset => @user_pages.current.offset
:offset => @user_pages.current.offset
render :action => "list", :layout => false if request.xhr?
end
def add
@@ -49,14 +51,15 @@ class UsersController < ApplicationController
@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]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
@custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
@user.custom_values = @custom_values
if @user.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list'
end
end
end
@auth_sources = AuthSource.find(:all)
end
def edit
@@ -66,7 +69,7 @@ class UsersController < ApplicationController
else
@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?
@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).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
@user.custom_values = @custom_values
@@ -76,6 +79,28 @@ class UsersController < ApplicationController
redirect_to :action => 'list'
end
end
@auth_sources = AuthSource.find(:all)
@roles = Role.find :all
@projects = Project.find(:all) - @user.projects
@membership ||= Member.new
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
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
end
def destroy

View File

@@ -39,8 +39,7 @@ class VersionsController < ApplicationController
@attachment.increment_download
send_file @attachment.diskfile, :filename => @attachment.filename
rescue
flash[:notice] = l(:notice_file_not_found)
redirect_to :controller => 'projects', :action => 'list_files', :id => @project
render_404
end
def destroy_file
@@ -53,5 +52,7 @@ private
def find_project
@version = Version.find(params[:id])
@project = @version.project
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -19,7 +19,7 @@ class WelcomeController < ApplicationController
layout 'base'
def index
@news = News.latest
@projects = Project.latest
@news = News.latest logged_in_user
@projects = Project.latest logged_in_user
end
end

View File

@@ -49,7 +49,7 @@ module ApplicationHelper
def link_to_user(user)
link_to user.display_name, :controller => 'account', :action => 'show', :id => user
end
def format_date(date)
l_date(date) if date
end
@@ -58,14 +58,36 @@ module ApplicationHelper
l_datetime(time) if time
end
def day_name(day)
l(:general_day_names).split(',')[day-1]
end
def month_name(month)
l(:actionview_datehelper_select_month_names).split(',')[month-1]
end
def pagination_links_full(paginator, options={}, html_options={})
html =''
html << link_to(('&#171; ' + l(:label_previous) ), { :page => paginator.current.previous }) + ' ' if paginator.current.previous
html << (pagination_links(paginator, options, html_options) || '')
html << ' ' + link_to((l(:label_next) + ' &#187;'), { :page => paginator.current.next }) if paginator.current.next
html = ''
html << link_to_remote(('&#171; ' + l(:label_previous)),
{:update => "content", :url => { :page => paginator.current.previous }},
{:href => url_for(:action => 'list', :params => params.merge({:page => paginator.current.previous}))}) + ' ' if paginator.current.previous
html << (pagination_links_each(paginator, options) do |n|
link_to_remote(n.to_s,
{:url => {:action => 'list', :params => params.merge({:page => n})}, :update => 'content'},
{:href => url_for(:action => 'list', :params => params.merge({:page => n}))})
end || '')
html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
{:update => "content", :url => { :page => paginator.current.next }},
{:href => url_for(:action => 'list', :params => params.merge({:page => paginator.current.next}))}) if paginator.current.next
html
end
def textilizable(text)
$RDM_TEXTILE_DISABLED ? simple_format(auto_link(h(text))) : RedCloth.new(h(text)).to_html
end
def error_messages_for(object_name, options = {})
options = options.symbolize_keys
object = instance_variable_get("@#{object_name}")
@@ -121,6 +143,11 @@ module ApplicationHelper
" | " +
link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
end
def calendar_for(field_id)
image_tag("calendar", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
end
end
class TabularFormBuilder < ActionView::Helpers::FormBuilder
@@ -131,12 +158,13 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
end
(field_helpers - %w(radio_button) + %w(date_select)).each do |selector|
(field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
src = <<-END_SRC
def #{selector}(field, options = {})
return super if options.delete :no_label
label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
label = @template.content_tag("label", label_text,
:class => (@object.errors[field] ? "error" : nil),
:class => (@object && @object.errors[field] ? "error" : nil),
:for => (@object_name.to_s + "_" + field.to_s))
label + super
end
@@ -144,10 +172,10 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
class_eval src, __FILE__, __LINE__
end
def select(field, choices, options = {})
def select(field, choices, options = {}, html_options = {})
label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
label = @template.content_tag("label", label_text,
:class => (@object.errors[field] ? "error" : nil),
:class => (@object && @object.errors[field] ? "error" : nil),
:for => (@object_name.to_s + "_" + field.to_s))
label + super
end

View File

@@ -24,8 +24,11 @@ module CustomFieldsHelper
field_id = "custom_fields_#{custom_field.id}"
case custom_field.field_format
when "string", "int", "date"
when "string", "int"
text_field 'custom_value', 'value', :name => field_name, :id => field_id
when "date"
text_field('custom_value', 'value', :name => field_name, :id => field_id, :size => 10) +
calendar_for(field_id)
when "text"
text_area 'custom_value', 'value', :name => field_name, :id => field_id, :cols => 60, :rows => 3
when "bool"
@@ -50,16 +53,25 @@ module CustomFieldsHelper
# Return a string used to display a custom value
def show_value(custom_value)
case custom_value.custom_field.field_format
return "" unless custom_value
format_value(custom_value.value, custom_value.custom_field.field_format)
end
# Return a string used to display a custom value
def format_value(value, field_format)
return "" unless value
case field_format
when "date"
value.empty? ? "" : l_date(value.to_date)
when "bool"
l_YesNo(custom_value.value == "1")
l_YesNo(value == "1")
else
custom_value.value
end
value
end
end
# Return an array of custom field formats which can be used in select_tag
def custom_field_formats_for_select
CustomField::FIELD_FORMATS.keys.collect { |k| [ l(CustomField::FIELD_FORMATS[k]), k ] }
CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] }
end
end

View File

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

View File

@@ -0,0 +1,48 @@
# redMine - project management software
# Copyright (C) 2006 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.
require 'iconv'
module IfpdfHelper
class IFPDF < FPDF
attr_accessor :footer_date
def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
@ic ||= Iconv.new('ISO-8859-1', 'UTF-8')
txt = begin
@ic.iconv(txt)
rescue
txt
end
super w,h,txt,border,ln,align,fill,link
end
def Footer
SetFont('Helvetica', 'I', 8)
SetY(-15)
SetX(15)
Cell(0, 5, @footer_date, 0, 0, 'L')
SetY(-15)
SetX(-30)
Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
end
end
end

View File

@@ -0,0 +1,74 @@
# redMine - project management software
# Copyright (C) 2006 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 IssuesHelper
def show_detail(detail, no_html=false)
case detail.property
when 'attr'
label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
case detail.prop_key
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 '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 '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
when 'priority_id'
e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
when 'category_id'
c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
when 'fixed_version_id'
v = Version.find_by_id(detail.value) and value = v.name if detail.value
v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
end
when 'cf'
custom_field = CustomField.find_by_id(detail.prop_key)
if custom_field
label = custom_field.name
value = format_value(detail.value, custom_field.field_format) if detail.value
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
end
end
label ||= detail.prop_key
value ||= detail.value
old_value ||= detail.old_value
unless no_html
label = content_tag('strong', label)
old_value = content_tag("i", h(old_value)) if old_value
old_value = content_tag("strike", h(old_value)) if old_value and !value
value = content_tag("i", h(value)) if value
end
if value
if old_value
label + " " + l(:text_journal_changed, old_value, value)
else
label + " " + l(:text_journal_set_to, value)
end
else
label + " " + l(:text_journal_deleted) + " (#{old_value})"
end
end
end

19
app/helpers/my_helper.rb Normal file
View File

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

View File

@@ -0,0 +1,6 @@
module QueriesHelper
def operators_for_select(filter_type)
Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
end
end

View File

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

View File

@@ -61,7 +61,7 @@ module SortHelper
# defaults to '<controller_name>_sort'.
#
def sort_init(default_key, default_order='asc', name=nil)
@sort_name = name || @params[:controller] + @params[:action] + '_sort'
@sort_name = name || params[:controller] + params[:action] + '_sort'
@sort_default = {:key => default_key, :order => default_order}
end
@@ -69,21 +69,21 @@ module SortHelper
# sort_clause.
#
def sort_update()
if @params[:sort_key]
sort = {:key => @params[:sort_key], :order => @params[:sort_order]}
elsif @session[@sort_name]
sort = @session[@sort_name] # Previous sort.
if params[:sort_key]
sort = {:key => params[:sort_key], :order => params[:sort_order]}
elsif session[@sort_name]
sort = session[@sort_name] # Previous sort.
else
sort = @sort_default
end
@session[@sort_name] = sort
session[@sort_name] = sort
end
# Returns an SQL sort clause corresponding to the current sort state.
# Use this to sort the controller's table items collection.
#
def sort_clause()
@session[@sort_name][:key] + ' ' + @session[@sort_name][:order]
session[@sort_name][:key] + ' ' + session[@sort_name][:order]
end
# Returns a link which sorts by the named column.
@@ -93,7 +93,7 @@ module SortHelper
# - A sort icon image is positioned to the right of the sort link.
#
def sort_link(column, caption=nil)
key, order = @session[@sort_name][:key], @session[@sort_name][:order]
key, order = session[@sort_name][:key], session[@sort_name][:order]
if key == column
if order.downcase == 'asc'
icon = 'sort_asc'
@@ -108,7 +108,10 @@ module SortHelper
end
caption = titleize(Inflector::humanize(column)) unless caption
params = {:params => {:sort_key => column, :sort_order => order}}
link_to(caption, params) + (icon ? nbsp(2) + image_tag(icon) : '')
link_to_remote(caption,
{:update => "content", :url => { :sort_key => column, :sort_order => order}},
{:href => url_for(:params => { :sort_key => column, :sort_order => order})}) +
(icon ? nbsp(2) + image_tag(icon) : '')
end
# Returns a table header <th> tag with a sort link for the named column

View File

@@ -21,8 +21,12 @@ class Attachment < ActiveRecord::Base
belongs_to :container, :polymorphic => true
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
validates_presence_of :filename
@@max_size = $RDM_ATTACHMENT_MAX_SIZE || 5*1024*1024
cattr_reader :max_size
validates_presence_of :container, :filename
validates_inclusion_of :filesize, :in => 1..@@max_size
def file=(incomming_file)
unless incomming_file.nil?
@temp_file = incomming_file
@@ -35,6 +39,10 @@ class Attachment < ActiveRecord::Base
end
end
def file
nil
end
# Copy temp file to its final location
def before_save
if @temp_file && (@temp_file.size > 0)

View File

@@ -1,28 +1,23 @@
# redMine - project management software
# Copyright (C) 2006 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 News < ActiveRecord::Base
belongs_to :project
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
validates_presence_of :title, :description
# returns last created news
def self.latest
find(:all, :limit => 5, :include => [ :author, :project ], :order => "news.created_on DESC")
end
# redMine - project management software
# Copyright (C) 2006 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 Comment < ActiveRecord::Base
belongs_to :commented, :polymorphic => true, :counter_cache => true
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
validates_presence_of :commented, :author, :comment
end

View File

@@ -16,18 +16,19 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CustomField < ActiveRecord::Base
has_many :custom_values, :dependent => true
has_many :custom_values, :dependent => :delete_all
FIELD_FORMATS = { "list" => :label_list,
"date" => :label_date,
"bool" => :label_boolean,
"int" => :label_integer,
"string" => :label_string,
"text" => :label_text
FIELD_FORMATS = { "string" => { :name => :label_string, :order => 1 },
"text" => { :name => :label_text, :order => 2 },
"int" => { :name => :label_integer, :order => 3 },
"list" => { :name => :label_list, :order => 4 },
"date" => { :name => :label_date, :order => 5 },
"bool" => { :name => :label_boolean, :order => 6 }
}.freeze
validates_presence_of :name, :field_format
validates_uniqueness_of :name
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys
validates_presence_of :possible_values, :if => Proc.new { |field| field.field_format == "list" }

View File

@@ -29,7 +29,7 @@ protected
when "int"
errors.add(:value, :activerecord_error_not_a_number) unless value =~ /^[0-9]*$/
when "date"
errors.add(:value, :activerecord_error_invalid) unless value =~ /^(\d+)\/(\d+)\/(\d+)$/ or value.empty?
errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ or value.empty?
when "list"
errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.split('|').include? value or value.empty?
end

View File

@@ -18,7 +18,7 @@
class Document < ActiveRecord::Base
belongs_to :project
belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
has_many :attachments, :as => :container, :dependent => true
has_many :attachments, :as => :container, :dependent => :destroy
validates_presence_of :project, :title, :category
end

View File

@@ -18,8 +18,9 @@
class Enumeration < ActiveRecord::Base
before_destroy :check_integrity
validates_presence_of :opt, :name
validates_uniqueness_of :name, :scope => [:opt]
validates_presence_of :opt, :name
validates_uniqueness_of :name, :scope => [:opt]
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
OPTIONS = {
"IPRI" => :enumeration_issue_priorities,
@@ -29,6 +30,10 @@ class Enumeration < ActiveRecord::Base
def self.get_values(option)
find(:all, :conditions => ['opt=?', option])
end
def option_name
OPTIONS[self.opt]
end
private
def check_integrity

102
app/models/issue.rb Normal file
View File

@@ -0,0 +1,102 @@
# redMine - project management software
# Copyright (C) 2006 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 Issue < ActiveRecord::Base
belongs_to :project
belongs_to :tracker
belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
has_many :journals, :as => :journalized, :dependent => :destroy
has_many :attachments, :as => :container, :dependent => :destroy
has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :custom_fields, :through => :custom_values
validates_presence_of :subject, :description, :priority, :tracker, :author, :status
validates_inclusion_of :done_ratio, :in => 0..100
validates_associated :custom_values, :on => :update
# set default status for new issues
def before_validation
self.status = IssueStatus.default if new_record?
end
def validate
if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
errors.add :due_date, :activerecord_error_not_a_date
end
if self.due_date and self.start_date and self.due_date < self.start_date
errors.add :due_date, :activerecord_error_greater_than_start_date
end
end
#def before_create
# build_history
#end
def before_save
if @current_journal
# attributes changes
(Issue.column_names - %w(id description)).each {|c|
@current_journal.details << JournalDetail.new(:property => 'attr',
:prop_key => c,
:old_value => @issue_before_change.send(c),
:value => send(c)) unless send(c)==@issue_before_change.send(c)
}
# custom fields changes
custom_values.each {|c|
@current_journal.details << JournalDetail.new(:property => 'cf',
:prop_key => c.custom_field_id,
:old_value => @custom_values_before_change[c.custom_field_id],
:value => c.value) unless @custom_values_before_change[c.custom_field_id]==c.value
}
@current_journal.save unless @current_journal.details.empty? and @current_journal.notes.empty?
end
end
def long_id
"%05d" % self.id
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
@custom_values_before_change = {}
self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
@current_journal
end
private
# Creates an history for the issue
#def build_history
# @history = self.histories.build
# @history.status = self.status
# @history.author = self.author
#end
end

View File

@@ -18,6 +18,7 @@
class IssueHistory < ActiveRecord::Base
belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
belongs_to :issue
validates_presence_of :status
end

View File

@@ -21,9 +21,14 @@ class IssueStatus < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :html_color, :is=>6
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_length_of :html_color, :is => 6
validates_format_of :html_color, :with => /^[a-f0-9]*$/i
def before_save
IssueStatus.update_all "is_default=false" if self.is_default?
end
# Returns the default status for new issues
def self.default
find(:first, :conditions =>["is_default=?", true])

22
app/models/journal.rb Normal file
View File

@@ -0,0 +1,22 @@
# redMine - project management software
# Copyright (C) 2006 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 Journal < ActiveRecord::Base
belongs_to :journalized, :polymorphic => true
belongs_to :user
has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
end

View File

@@ -0,0 +1,20 @@
# redMine - project management software
# Copyright (C) 2006 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 JournalDetail < ActiveRecord::Base
belongs_to :journal
end

View File

@@ -15,35 +15,39 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Mailer < ActionMailer::Base
def issue_change_status(issue)
# Sends to all project members
@recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
@from = 'redmine@somenet.foo'
@subject = "Issue ##{issue.id} has been updated"
@body['issue'] = issue
end
class Mailer < ActionMailer::Base
helper IssuesHelper
def issue_add(issue)
# Sends to all project members
@recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
@from = 'redmine@somenet.foo'
@subject = "Issue ##{issue.id} has been reported"
@recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }.compact
@from = $RDM_MAIL_FROM
@subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
@body['issue'] = issue
end
def issue_edit(journal)
# Sends to all project members
issue = journal.journalized
@recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }.compact
@from = $RDM_MAIL_FROM
@subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
@body['issue'] = issue
@body['journal']= journal
end
def lost_password(token)
@recipients = token.user.mail
@from = 'redmine@somenet.foo'
@subject = "redMine password"
@from = $RDM_MAIL_FROM
@subject = l(:mail_subject_lost_password)
@body['token'] = token
end
def register(token)
@recipients = token.user.mail
@from = 'redmine@somenet.foo'
@subject = "redMine account activation"
@from = $RDM_MAIL_FROM
@subject = l(:mail_subject_register)
@body['token'] = token
end
end

29
app/models/news.rb Normal file
View File

@@ -0,0 +1,29 @@
# redMine - project management software
# Copyright (C) 2006 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 News < ActiveRecord::Base
belongs_to :project
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on"
validates_presence_of :title, :description
# 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.created_on DESC")
end
end

View File

@@ -25,10 +25,12 @@ class Permission < ActiveRecord::Base
200 => :label_member_plural,
300 => :label_version_plural,
400 => :label_issue_category_plural,
600 => :label_query_plural,
1000 => :label_issue_plural,
1100 => :label_news_plural,
1200 => :label_document_plural,
1300 => :label_attachment_plural,
1400 => :label_repository
}.freeze
@@cached_perms_for_public = nil
@@ -54,7 +56,7 @@ class Permission < ActiveRecord::Base
find(:all, :include => :roles).each {|p| perms.store "#{p.controller}/#{p.action}", p.roles.collect {|r| r.id } }
perms
end
@@cached_perms_for_roles[action] and @@cached_perms_for_roles[action].include? role
allowed_to_public(action) or (@@cached_perms_for_roles[action] and @@cached_perms_for_roles[action].include? role)
end
def self.allowed_to_role_expired

View File

@@ -16,26 +16,39 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Project < ActiveRecord::Base
has_many :versions, :dependent => true, :order => "versions.effective_date DESC, versions.name DESC"
has_many :members, :dependent => true
has_many :versions, :dependent => :destroy, :order => "versions.effective_date DESC, versions.name DESC"
has_many :members, :dependent => :delete_all, :include => :user, :conditions => "users.status=#{User::STATUS_ACTIVE}"
has_many :users, :through => :members
has_many :custom_values, :dependent => true, :as => :customized
has_many :issues, :dependent => true, :order => "issues.created_on DESC", :include => :status
has_many :documents, :dependent => true
has_many :news, :dependent => true, :include => :author
has_many :issue_categories, :dependent => true, :order => "issue_categories.name"
has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :issues, :dependent => :destroy, :order => "issues.created_on DESC", :include => [:status, :tracker]
has_many :queries, :dependent => :delete_all
has_many :documents, :dependent => :destroy
has_many :news, :dependent => :delete_all, :include => :author
has_many :issue_categories, :dependent => :delete_all, :order => "issue_categories.name"
has_one :repository, :dependent => :destroy
has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => 'custom_fields_projects', :association_foreign_key => 'custom_field_id'
acts_as_tree :order => "name", :counter_cache => true
validates_presence_of :name, :description
validates_uniqueness_of :name
validates_associated :custom_values, :on => :update
validates_associated :repository
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
# returns 5 last created projects
def self.latest
find(:all, :limit => 5, :order => "created_on DESC")
# returns latest created projects
# non public projects will be returned only if user is a member of those
def self.latest(user=nil, count=5)
find(:all, :limit => count, :conditions => visible_by(user), :order => "projects.created_on DESC")
end
def self.visible_by(user=nil)
if user && !user.memberships.empty?
return ["projects.is_public = ? or projects.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')})", true]
else
return ["projects.is_public = ?", true]
end
end
# 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)
@@ -43,6 +56,11 @@ class Project < ActiveRecord::Base
:conditions => ["is_for_all=? or project_id=?", true, self.id])
#(CustomField.for_all + custom_fields).uniq
end
def all_custom_fields
@all_custom_fields ||= IssueCustomField.find(:all, :include => :projects,
:conditions => ["is_for_all=? or project_id=?", true, self.id])
end
protected
def validate

166
app/models/query.rb Normal file
View File

@@ -0,0 +1,166 @@
# redMine - project management software
# Copyright (C) 2006 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 Query < ActiveRecord::Base
belongs_to :project
belongs_to :user
serialize :filters
attr_protected :project, :user
validates_presence_of :name, :on => :save
@@operators = { "=" => :label_equals,
"!" => :label_not_equals,
"o" => :label_open_issues,
"c" => :label_closed_issues,
"!*" => :label_none,
"*" => :label_all,
"<t+" => :label_in_less_than,
">t+" => :label_in_more_than,
"t+" => :label_in,
"t" => :label_today,
">t-" => :label_less_than_ago,
"<t-" => :label_more_than_ago,
"t-" => :label_ago,
"~" => :label_contains,
"!~" => :label_not_contains }
cattr_reader :operators
@@operators_by_filter_type = { :list => [ "=", "!" ],
:list_status => [ "o", "=", "!", "c", "*" ],
:list_optional => [ "=", "!", "!*", "*" ],
:date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ],
:date_past => [ ">t-", "<t-", "t-", "t" ],
:text => [ "~", "!~" ] }
cattr_reader :operators_by_filter_type
def initialize(attributes = nil)
super attributes
self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
self.is_public = true
end
def validate
filters.each_key do |field|
errors.add field.gsub(/\_id$/, ""), :activerecord_error_blank unless
# filter requires one or more values
(values_for(field) and !values_for(field).first.empty?) or
# filter doesn't require any value
["o", "c", "!*", "*", "t"].include? operator_for(field)
end if filters
end
def available_filters
return @available_filters if @available_filters
@available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all).collect{|s| [s.name, s.id.to_s] } },
"tracker_id" => { :type => :list, :order => 2, :values => Tracker.find(:all).collect{|s| [s.name, s.id.to_s] } },
"priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } },
"subject" => { :type => :text, :order => 7 },
"created_on" => { :type => :date_past, :order => 8 },
"updated_on" => { :type => :date_past, :order => 9 },
"start_date" => { :type => :date, :order => 10 },
"due_date" => { :type => :date, :order => 11 } }
unless project.nil?
# project specific filters
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => @project.users.collect{|s| [s.name, s.id.to_s] } }
@available_filters["author_id"] = { :type => :list, :order => 5, :values => @project.users.collect{|s| [s.name, s.id.to_s] } }
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
# remove category filter if no category defined
@available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
end
@available_filters
end
def add_filter(field, operator, values)
# values must be an array
return unless values and values.is_a? Array # and !values.first.empty?
# check if field is defined as an available filter
if available_filters.has_key? field
filter_options = available_filters[field]
# check if operator is allowed for that filter
#if @@operators_by_filter_type[filter_options[:type]].include? operator
# allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
# filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
#end
filters[field] = {:operator => operator, :values => values }
end
end
def add_short_filter(field, expression)
return unless expression
parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
add_filter field, (parms[0] || "="), [parms[1] || ""]
end
def has_filter?(field)
filters and filters[field]
end
def operator_for(field)
has_filter?(field) ? filters[field][:operator] : nil
end
def values_for(field)
has_filter?(field) ? filters[field][:values] : nil
end
def statement
sql = "1=1"
sql << " AND issues.project_id=%d" % project.id if project
filters.each_key do |field|
v = values_for field
next unless v and !v.empty?
sql = sql + " AND " unless sql.empty?
case operator_for field
when "="
sql = sql + "issues.#{field} IN (" + v.each(&:to_i).join(",") + ")"
when "!"
sql = sql + "issues.#{field} NOT IN (" + v.each(&:to_i).join(",") + ")"
when "!*"
sql = sql + "issues.#{field} IS NULL"
when "*"
sql = sql + "issues.#{field} IS NOT NULL"
when "o"
sql = sql + "issue_statuses.is_closed=#{connection.quoted_false}" if field == "status_id"
when "c"
sql = sql + "issue_statuses.is_closed=#{connection.quoted_true}" if field == "status_id"
when ">t-"
sql = sql + "issues.#{field} >= '%s'" % connection.quoted_date(Date.today - v.first.to_i)
when "<t-"
sql = sql + "issues.#{field} <= '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'"
when "t-"
sql = sql + "issues.#{field} = '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'"
when ">t+"
sql = sql + "issues.#{field} >= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
when "<t+"
sql = sql + "issues.#{field} <= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
when "t+"
sql = sql + "issues.#{field} = '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
when "t"
sql = sql + "issues.#{field} = '%s'" % connection.quoted_date(Date.today)
when "~"
sql = sql + "issues.#{field} LIKE '%#{connection.quote_string(v.first)}%'"
when "!~"
sql = sql + "issues.#{field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
end
end if filters and valid?
sql
end
end

28
app/models/repository.rb Normal file
View File

@@ -0,0 +1,28 @@
# redMine - project management software
# Copyright (C) 2006 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 Repository < ActiveRecord::Base
belongs_to :project
validates_presence_of :url
validates_format_of :url, :with => /^(http|https|svn|file):\/\/.+/i
@scm = nil
def scm
@scm ||= SvnRepos::Base.new url
end
end

View File

@@ -18,11 +18,12 @@
class Role < ActiveRecord::Base
before_destroy :check_integrity
has_and_belongs_to_many :permissions
has_many :workflows, :dependent => true
has_many :workflows, :dependent => :delete_all
has_many :members
validates_presence_of :name
validates_uniqueness_of :name
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
private
def check_integrity

214
app/models/svn_repos.rb Normal file
View File

@@ -0,0 +1,214 @@
# redMine - project management software
# Copyright (C) 2006 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.
require 'rexml/document'
module SvnRepos
class CommandFailed < StandardError #:nodoc:
end
class Base
@url = nil
@login = nil
@password = nil
def initialize(url, login=nil, password=nil)
@url = url
@login = login if login && !login.empty?
@password = (password || "") if @login
end
# Returns the entry identified by path and revision identifier
# or nil if entry doesn't exist in the repository
def entry(path=nil, identifier=nil)
e = entries(path, identifier)
e ? e.first : nil
end
# Returns an Entries collection
# or nil if the given path doesn't exist in the repository
def entries(path=nil, identifier=nil)
path ||= ''
identifier = 'HEAD' unless identifier and identifier > 0
entries = Entries.new
cmd = "svn list --xml #{target(path)}@#{identifier}"
shellout(cmd) do |io|
begin
doc = REXML::Document.new(io)
doc.elements.each("lists/list/entry") do |entry|
entries << Entry.new({:name => entry.elements['name'].text,
:path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
:kind => entry.attributes['kind'],
:size => (entry.elements['size'] and entry.elements['size'].text).to_i,
:lastrev => Revision.new({
:identifier => entry.elements['commit'].attributes['revision'],
:time => Time.parse(entry.elements['commit'].elements['date'].text),
:author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "anonymous")
})
})
end
rescue
end
end
return nil if $? && $?.exitstatus != 0
entries.sort_by_name
rescue Errno::ENOENT => e
raise CommandFailed
end
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path ||= ''
identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0
identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
revisions = Revisions.new
cmd = "svn log --xml -r #{identifier_from}:#{identifier_to} "
cmd << "--verbose " if options[:with_paths]
cmd << target(path)
shellout(cmd) do |io|
begin
doc = REXML::Document.new(io)
doc.elements.each("log/logentry") do |logentry|
paths = []
logentry.elements.each("paths/path") do |path|
paths << {:action => path.attributes['action'],
:path => path.text
}
end
paths.sort! { |x,y| x[:path] <=> y[:path] }
revisions << Revision.new({:identifier => logentry.attributes['revision'],
:author => (logentry.elements['author'] ? logentry.elements['author'].text : "anonymous"),
:time => Time.parse(logentry.elements['date'].text),
:message => logentry.elements['msg'].text,
:paths => paths
})
end
rescue
end
end
return nil if $? && $?.exitstatus != 0
revisions
rescue Errno::ENOENT => e
raise CommandFailed
end
def diff(path, identifier_from, identifier_to=nil)
path ||= ''
if identifier_to and identifier_to.to_i > 0
identifier_to = identifier_to.to_i
else
identifier_to = identifier_from.to_i - 1
end
cmd = "svn diff -r "
cmd << "#{identifier_to}:"
cmd << "#{identifier_from}"
cmd << "#{target(path)}@#{identifier_from}"
diff = []
shellout(cmd) do |io|
io.each_line do |line|
diff << line
end
end
return nil if $? && $?.exitstatus != 0
diff
rescue Errno::ENOENT => e
raise CommandFailed
end
def cat(path, identifier=nil)
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
cmd = "svn cat #{target(path)}@#{identifier}"
cat = nil
shellout(cmd) do |io|
cat = io.read
end
return nil if $? && $?.exitstatus != 0
cat
rescue Errno::ENOENT => e
raise CommandFailed
end
private
def target(path)
" \"" << "#{@url}/#{path}".gsub(/["'?<>\*]/, '') << "\""
end
def logger
RAILS_DEFAULT_LOGGER
end
def shellout(cmd, &block)
logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
IO.popen(cmd) do |io|
block.call(io) if block_given?
end
end
end
class Entries < Array
def sort_by_name
sort {|x,y|
if x.kind == y.kind
x.name <=> y.name
else
x.kind <=> y.kind
end
}
end
def revisions
revisions ||= Revisions.new(collect{|entry| entry.lastrev})
end
end
class Entry
attr_accessor :name, :path, :kind, :size, :lastrev
def initialize(attributes={})
self.name = attributes[:name] if attributes[:name]
self.path = attributes[:path] if attributes[:path]
self.kind = attributes[:kind] if attributes[:kind]
self.size = attributes[:size].to_i if attributes[:size]
self.lastrev = attributes[:lastrev]
end
def is_file?
'file' == self.kind
end
def is_dir?
'dir' == self.kind
end
end
class Revisions < Array
def latest
sort {|x,y| x.time <=> y.time}.last
end
end
class Revision
attr_accessor :identifier, :author, :time, :message, :paths
def initialize(attributes={})
self.identifier = attributes[:identifier]
self.author = attributes[:author]
self.time = attributes[:time]
self.message = attributes[:message] || ""
self.paths = attributes[:paths]
end
end
end

View File

@@ -18,12 +18,13 @@
class Tracker < ActiveRecord::Base
before_destroy :check_integrity
has_many :issues
has_many :workflows, :dependent => true
has_many :workflows, :dependent => :delete_all
has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => 'custom_fields_trackers', :association_foreign_key => 'custom_field_id'
validates_presence_of :name
validates_uniqueness_of :name
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
private
def check_integrity
raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])

View File

@@ -18,8 +18,10 @@
require "digest/sha1"
class User < ActiveRecord::Base
has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => true
has_many :custom_values, :dependent => true, :as => :customized
has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => :delete_all
has_many :projects, :through => :memberships
has_many :custom_values, :dependent => :delete_all, :as => :customized
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
belongs_to :auth_source
attr_accessor :password, :password_confirmation
@@ -30,7 +32,8 @@ class User < ActiveRecord::Base
validates_presence_of :login, :firstname, :lastname, :mail
validates_uniqueness_of :login, :mail
# Login must contain lettres, numbers, underscores only
validates_format_of :login, :with => /^[a-z0-9_]+$/i
validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-]*$/i
validates_format_of :login, :with => /^[a-z0-9_\-@\.]+$/i
validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
# Password length between 4 and 12
validates_length_of :password, :in => 4..12, :allow_nil => true
@@ -85,10 +88,18 @@ class User < ActiveRecord::Base
firstname + " " + lastname
end
def name
display_name
end
def active?
self.status == STATUS_ACTIVE
end
def registered?
self.status == STATUS_REGISTERED
end
def locked?
self.status == STATUS_LOCKED
end
@@ -106,6 +117,10 @@ class User < ActiveRecord::Base
end
@role_for_projects[project_id]
end
def pref
self.preference ||= UserPreference.new(:user => self)
end
private
# Return password digest

View File

@@ -0,0 +1,44 @@
# redMine - project management software
# Copyright (C) 2006 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 UserPreference < ActiveRecord::Base
belongs_to :user
serialize :others, Hash
attr_protected :others
def initialize(attributes = nil)
super
self.others ||= {}
end
def [](attr_name)
if attribute_present? attr_name
super
else
others[attr_name]
end
end
def []=(attr_name, value)
if attribute_present? attr_name
super
else
others.store attr_name, value
end
end
end

View File

@@ -19,10 +19,11 @@ class Version < ActiveRecord::Base
before_destroy :check_integrity
belongs_to :project
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
has_many :attachments, :as => :container, :dependent => true
has_many :attachments, :as => :container, :dependent => :destroy
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id]
validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :activerecord_error_not_a_date
private
def check_integrity

View File

@@ -1,8 +1,15 @@
<h2><%= @user.display_name %></h2>
<p>
<%= mail_to @user.mail %><br />
<%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %>
<%= mail_to @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 %>
<% if !custom_value.value.empty? %>
<li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
<% end %>
<% end %>
</ul>
</p>
<h3><%=l(:label_project_plural)%></h3>

View File

@@ -1,19 +1,23 @@
<div class="contextual">
<%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'pic picAdd' %>
</div>
<h2><%=l(:label_project_plural)%></h2>
<table class="listTableContent">
<tr class="ListHead">
<table class="list">
<thead><tr>
<%= sort_header_tag('name', :caption => l(:label_project)) %>
<th><%=l(:field_description)%></th>
<th><%=l(:field_is_public)%></th>
<th><%=l(:label_subproject_plural)%></th>
<%= sort_header_tag('created_on', :caption => l(:field_created_on)) %>
<th></th>
</tr>
<th></th>
</tr></thead>
<tbody>
<% for project in @projects %>
<tr class="<%= cycle("odd", "even") %>">
<td><%= link_to project.name, :controller => 'projects', :action => 'settings', :id => project %>
<td><%= project.description %>
<td><%=h project.description %>
<td align="center"><%= image_tag 'true' if project.is_public? %>
<td align="center"><%= project.projects_count %>
<td align="center"><%= format_date(project.created_on) %>
@@ -22,9 +26,8 @@
</td>
</tr>
<% end %>
</tbody>
</table>
<p><%= pagination_links_full @project_pages %>
[ <%= @project_pages.current.first_item %> - <%= @project_pages.current.last_item %> / <%= @project_count %> ]</p>
<p><%= link_to ('&#187; ' + l(:label_project_new)), :controller => 'projects', :action => 'add' %></p>
[ <%= @project_pages.current.first_item %> - <%= @project_pages.current.last_item %> / <%= @project_count %> ]</p>

View File

@@ -0,0 +1,28 @@
<div class="contextual">
<%= link_to l(:label_auth_source_new), {:action => 'new'}, :class => 'pic picAdd' %>
</div>
<h2><%=l(:label_auth_source_plural)%></h2>
<table class="list">
<thead><tr>
<th><%=l(:field_name)%></th>
<th><%=l(:field_type)%></th>
<th><%=l(:field_host)%></th>
<th></th>
<th></th>
</tr></thead>
<tbody>
<% for source in @auth_sources %>
<tr class="<%= cycle("odd", "even") %>">
<td><%= link_to source.name, :action => 'edit', :id => source%></td>
<td align="center"><%= source.auth_method_name %></td>
<td align="center"><%= source.host %></td>
<td align="center"><%= link_to l(:button_test), :action => 'test_connection', :id => source %></td>
<td align="center"><%= button_to l(:button_delete), { :action => 'destroy', :id => source }, :confirm => l(:text_are_you_sure), :class => "button-small" %></td>
</tr>
<% end %>
</tbody>
</table>
<%= pagination_links_full @auth_source_pages %>

View File

@@ -0,0 +1,4 @@
<h2>404</h2>
<p><%= l(:notice_file_not_found) %></p>
<p><a href="javascript:history.back()">Back</a></p>

View File

@@ -0,0 +1,70 @@
<%= error_messages_for 'custom_field' %>
<script>
function toggle_custom_field_format() {
format = $("custom_field_field_format");
p_length = $("custom_field_min_length");
p_regexp = $("custom_field_regexp");
p_values = $("custom_field_possible_values");
switch (format.value) {
case "list":
Element.hide(p_length.parentNode);
Element.hide(p_regexp.parentNode);
Element.show(p_values.parentNode);
break;
case "int":
case "string":
case "text":
Element.show(p_length.parentNode);
Element.show(p_regexp.parentNode);
Element.hide(p_values.parentNode);
break;
case "date":
case "bool":
Element.hide(p_length.parentNode);
Element.hide(p_regexp.parentNode);
Element.hide(p_values.parentNode);
break;
default:
Element.show(p_length.parentNode);
Element.show(p_regexp.parentNode);
Element.show(p_values.parentNode);
break;
}
}
</script>
<!--[form:custom_field]-->
<div class="box">
<p><%= f.text_field :name, :required => true %></p>
<p><%= f.select :field_format, custom_field_formats_for_select, {}, :onchange => "toggle_custom_field_format();" %></p>
<p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
<%= f.text_field :min_length, :size => 5, :no_label => true %> -
<%= f.text_field :max_length, :size => 5, :no_label => true %><br>(<%=l(:text_min_max_length_info)%>)</p>
<p><%= f.text_field :regexp, :size => 50 %><br>(<%=l(:text_regexp_info)%>)</p>
<p><%= f.text_area :possible_values, :rows => 5, :cols => 60 %><br>(<%=l(:text_possible_values_info)%>)</p>
</div>
<%= javascript_tag "toggle_custom_field_format();" %>
<!--[eoform:custom_field]-->
<div class="box">
<% case @custom_field.type.to_s
when "IssueCustomField" %>
<fieldset><legend><%=l(:label_tracker_plural)%></legend>
<% for tracker in @trackers %>
<%= check_box_tag "tracker_ids[]", tracker.id, (@custom_field.trackers.include? tracker) %> <%= tracker.name %>
<% end %>
</fieldset>
&nbsp;
<p><%= f.check_box :is_required %></p>
<p><%= f.check_box :is_for_all %></p>
<% when "UserCustomField" %>
<p><%= f.check_box :is_required %></p>
<% when "ProjectCustomField" %>
<p><%= f.check_box :is_required %></p>
<% end %>
</div>

View File

@@ -0,0 +1,6 @@
<h2><%=l(:label_custom_field)%> (<%=l(@custom_field.type_name)%>)</h2>
<% labelled_tabular_form_for :custom_field, @custom_field, :url => { :action => "edit", :id => @custom_field } do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
<% end %>

View File

@@ -1,7 +1,7 @@
<h2><%=l(:label_custom_field_plural)%></h2>
<table class="listTableContent">
<tr class="ListHead">
<table class="list">
<thead><tr>
<th><%=l(:field_name)%></th>
<th><%=l(:field_type)%></th>
<th><%=l(:field_field_format)%></th>
@@ -9,12 +9,13 @@
<th><%=l(:field_is_for_all)%></th>
<th><%=l(:label_used_by)%></th>
<th></th>
</tr>
</tr></thead>
<tbody>
<% for custom_field in @custom_fields %>
<tr class="<%= cycle("odd", "even") %>">
<td><%= link_to custom_field.name, :action => 'edit', :id => custom_field %></td>
<td align="center"><%= l(custom_field.type_name) %></td>
<td align="center"><%= l(CustomField::FIELD_FORMATS[custom_field.field_format]) %></td>
<td align="center"><%= l(CustomField::FIELD_FORMATS[custom_field.field_format][:name]) %></td>
<td align="center"><%= image_tag 'true' if custom_field.is_required? %></td>
<td align="center"><%= image_tag 'true' if custom_field.is_for_all? %></td>
<td align="center"><%= custom_field.projects.count.to_s + ' ' + lwr(:label_project, custom_field.projects.count) if custom_field.is_a? IssueCustomField and !custom_field.is_for_all? %></td>
@@ -23,6 +24,7 @@
</td>
</tr>
<% end %>
</tbody>
</table>
<%= pagination_links_full @custom_field_pages %>

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