Compare commits

..

903 Commits
0.8.3 ... 0.9.0

Author SHA1 Message Date
Jean-Philippe Lang
c549c8b721 tagged version 0.9.0 (release candidate)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/0.9.0@3297 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-01-09 12:03:20 +00:00
Jean-Philippe Lang
eaaa471d6a Merged r3295 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3296 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-01-09 11:50:31 +00:00
Jean-Philippe Lang
2b6d8125d1 Merged r3293 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3294 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-01-09 11:47:38 +00:00
Jean-Philippe Lang
12b75ded08 Locales updated to trunk @ r3291.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3292 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-01-09 10:41:27 +00:00
Jean-Philippe Lang
46bf2b9276 Merged r3289 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3290 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-01-09 10:27:50 +00:00
Jean-Philippe Lang
9d82bff1a8 Merged r3282 to r3284 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3287 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-01-09 10:04:21 +00:00
Jean-Philippe Lang
4c75864948 Merged r3258 and r3281 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3286 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-01-09 10:02:16 +00:00
Jean-Philippe Lang
9d2474c234 Merged r3277 to r3279 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3280 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-01-03 11:47:25 +00:00
Jean-Philippe Lang
7193817dea Translation updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3276 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-01-03 10:32:37 +00:00
Eric Davis
610d7d4ba4 Merged r3267 from trunk
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3268 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-30 00:51:08 +00:00
Eric Davis
4b41788848 Merged r3265 from trunk
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3266 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-30 00:26:48 +00:00
Eric Davis
e1423c7c23 Merged r3263 from trunk
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3264 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-29 23:33:46 +00:00
Eric Davis
9c9f6722f6 Merged r3261 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3262 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-29 17:58:21 +00:00
Jean-Philippe Lang
a8be47295a Merged r3259 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3260 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-29 14:56:17 +00:00
Eric Davis
ba98197637 Merged r3256 from trunk
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3257 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-29 00:44:28 +00:00
Jean-Philippe Lang
a23399f220 Merged r3249 to r3252 and r3254 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3255 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-27 12:41:27 +00:00
Jean-Philippe Lang
718cd596e0 Merged r3246 and r3247 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3248 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-26 13:54:57 +00:00
Jean-Philippe Lang
c5ccfede6d Set VERSION::BRANCH to stable.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3245 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-26 10:27:46 +00:00
Jean-Philippe Lang
7702bdcdab 0.9-stable branch created
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3244 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-26 10:22:16 +00:00
Jean-Philippe Lang
1052658df2 Merged 0.8.x changelog.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3243 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-26 10:21:05 +00:00
Jean-Philippe Lang
9c1efcfa48 Do not display the copy form when project copy is created.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3242 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-26 10:18:28 +00:00
Jean-Philippe Lang
45f3199aa9 View cleanup.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3241 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-25 17:39:06 +00:00
Jean-Philippe Lang
8db9ecef08 Removes column opt in enumerations table.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3240 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-25 17:13:58 +00:00
Eric Davis
9fb40b1a2f Project#activities should check all overridden activities, not just active ones.
Fixes #4084

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3239 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-24 18:25:49 +00:00
Jean-Philippe Lang
62c83bdd2e Adds a 'Add subprojects' permission.
* 'Add project' permission will let user create a root project
* 'Add subprojects' permission will let project members create subprojects

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3238 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-24 16:18:15 +00:00
Jean-Philippe Lang
24fde6f109 Hide "Groups" tab on user form if no group exist.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3237 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-24 16:14:15 +00:00
Jean-Philippe Lang
6ea202f808 Translation updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3236 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-24 13:58:15 +00:00
Jean-Philippe Lang
2a19efaf05 Rails 2.3.5 deprecation.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3235 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 20:55:42 +00:00
Jean-Philippe Lang
962827c793 Upgrade to Rails 2.3.5
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3234 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 20:54:32 +00:00
Jean-Philippe Lang
e4d06246cd Set version to 0.9.0.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3233 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 20:47:10 +00:00
Jean-Philippe Lang
d63784569f Force TimeEntry#hours default to nil (#3075, #4449).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3232 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 20:05:46 +00:00
Jean-Philippe Lang
5e1dc78d75 Fixed: wiki pages in search results are referenced by project number, not by project identifier (#4456).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3231 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 20:01:28 +00:00
Jean-Philippe Lang
32bb0226e7 Remove invalid escaping in version field (#4460).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3230 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 19:58:41 +00:00
Jean-Philippe Lang
0704ac9444 Fixes delimiters regexp.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3229 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 19:52:48 +00:00
Jean-Philippe Lang
569d13b7f5 Translation updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3228 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 19:35:24 +00:00
Jean-Philippe Lang
c4b01e5c13 Adds Indonesian translation by Raden Prabowo (#4399).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3227 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 18:56:03 +00:00
Jean-Philippe Lang
a54fa93b2e Adds a setting to remove incoming emails body after a delimiter (#4409).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3226 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 18:35:19 +00:00
Jean-Philippe Lang
e26eeef837 Display API key in the sidebar, just below the API key heading.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3225 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 18:13:24 +00:00
Jean-Philippe Lang
d404d2f586 Do not display API key if API is disabled.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3224 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 18:08:39 +00:00
Jean-Philippe Lang
682829a904 Fills translations.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3223 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 18:03:40 +00:00
Jean-Philippe Lang
ff36245f3e Move API setting to Authentication tab and add translations strings.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3222 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 17:56:39 +00:00
Jean-Philippe Lang
edab0f0cbb Do not mass create API keys for existing users.
They will be created on the fly if needed, just like for new users.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3221 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 17:49:32 +00:00
Eric Davis
bfcd5039f2 Added an Admin setting to enable/disable the REST web service. (#3920)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3220 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 06:27:44 +00:00
Eric Davis
e07e9d8bfe Added support for HTTP Basic access to the API. (#3920)
A user can authenticate using either their:

* username/password
* api-key/random

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3219 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 06:27:38 +00:00
Eric Davis
baa1ad4256 Allow authenticating with an API token via XML or JSON. (#3920)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3218 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 06:27:33 +00:00
Eric Davis
aa9951b38b Added an API token for each User to use when making API requests. (#3920)
The API key will be displayed on My Account page with a link to reset or
generate a new one.  All existing users will have a token generated by the
migration.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3217 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-23 06:27:28 +00:00
Eric Davis
9f59cd64ab Added the revision title to any revision links.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3216 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-22 23:23:54 +00:00
Jean-Philippe Lang
ffe8222257 Redmine.pm: deny access if user doesn't have browse_repository permission (#4338).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3215 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-22 18:08:19 +00:00
Jean-Philippe Lang
18c7c0d3ee Fixes a test failure.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3214 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-21 21:19:01 +00:00
Jean-Philippe Lang
fce25d4488 Fixes test failure.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3213 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-21 21:03:29 +00:00
Jean-Philippe Lang
3b7e7be72a Fixes odd test failures.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3212 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-21 20:02:51 +00:00
Eric Davis
87e83c7285 Fixed the view_hook_helper test, it was reading all of the log files into
memory and would throw a NoMemoryError (2GB+ used).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3211 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-21 02:24:56 +00:00
Eric Davis
e1013c44a3 Make sure the RSS token is getting destroyed and created.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3210 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-21 02:24:49 +00:00
Eric Davis
c478fa7f90 Extract method
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3209 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-21 02:24:44 +00:00
Eric Davis
0844a22b02 Refactor: Use the existing method for failing onthefly creations.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3208 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-20 20:13:50 +00:00
Eric Davis
6be0e335fb Added an optimization note for later.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3207 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-20 19:13:27 +00:00
Eric Davis
1ebb78e412 Added some database indexes to commonly queried fields.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3206 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-20 19:13:22 +00:00
Eric Davis
7955e1eb9f Change the cursor for context menus. (#2924)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3205 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-20 18:05:53 +00:00
Jean-Philippe Lang
c5976333c2 Adds a 'Create and continue' button on new user form.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3204 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-20 10:51:33 +00:00
Jean-Philippe Lang
1099d704b5 Removes unused css classes (closes #4452).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3203 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-20 09:47:16 +00:00
Jean-Philippe Lang
e92802508e Change MailHandler 403 error message.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3202 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-20 09:45:04 +00:00
Jean-Philippe Lang
cf9bb2699f Adds on optional API key to repositories management controller and reposman.rb.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3201 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-20 09:44:28 +00:00
Jean-Philippe Lang
4398386c48 Admin info cleanup.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3200 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-19 20:33:24 +00:00
Jean-Philippe Lang
503d613403 Changes errors style.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3199 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-19 20:17:40 +00:00
Jean-Philippe Lang
6ccbcfb589 Adds helpers for setting field tags.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3198 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-19 20:07:09 +00:00
Jean-Philippe Lang
b5b6a5e971 Speeds up very slow tests.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3197 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-19 20:06:32 +00:00
Jean-Philippe Lang
3285230708 Translation update (#4352).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3196 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-19 14:17:20 +00:00
Jean-Philippe Lang
06ca18b042 Adds a 'no_permission_check' option to the MailHandler.
Used with the 'project' option, it allows anyone to submit emails to a private inbox project (#4407).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3195 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-19 14:08:48 +00:00
Jean-Philippe Lang
9d120c872c Fixes block reordering on my page (#2971).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3194 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-19 13:32:21 +00:00
Jean-Philippe Lang
008ad85d10 Fixes an error with postgres.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3193 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-19 12:40:43 +00:00
Jean-Philippe Lang
03b57415d6 Avoid a ruby warning.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3192 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-19 12:16:45 +00:00
Jean-Philippe Lang
77aeca5d55 Fixes 'follows' relation validation.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3191 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-18 20:59:30 +00:00
Jean-Philippe Lang
beb20e7c6e Adds 'follows' relation (#1432).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3190 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-18 20:35:16 +00:00
Jean-Philippe Lang
111950108a Issue sidebar cleanup.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3189 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-18 20:15:49 +00:00
Jean-Philippe Lang
6bf0723d06 By default, only show statuses that are used by the tracker on the workflow edit view.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3188 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-18 15:41:32 +00:00
Jean-Philippe Lang
6a369f28dd Display shared versions on the issue summary view (#4425).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3187 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-18 14:59:19 +00:00
Jean-Philippe Lang
1ba5779f94 Fixed: rubytree gem breaks db:migrate:plugins (#4394).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3186 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-18 14:41:37 +00:00
Jean-Philippe Lang
488c192286 Removes "xxx and return" calls (#4446).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3185 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-18 14:22:18 +00:00
Jean-Philippe Lang
0e4525d76c Fixes test broken by r3176 (#4439).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3184 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-18 14:15:26 +00:00
Jean-Philippe Lang
8e3def7129 Icons updates and cleaning.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3183 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-18 14:10:26 +00:00
Jean-Philippe Lang
6b94614c36 Added missing translation for permission_view_issues (#4415).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3182 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-17 20:18:43 +00:00
Jean-Philippe Lang
a011e9a9bf Group icon changed on project members list.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3181 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-17 19:58:31 +00:00
Jean-Philippe Lang
46d27f16d5 IE8 fix.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3180 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-17 19:00:31 +00:00
Jean-Philippe Lang
91a493e2c7 Removes large icons.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3179 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-17 18:52:28 +00:00
Jean-Philippe Lang
b8cd280bec Do not hide scroll buttons if some tabs are still hidden.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3178 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-17 18:23:00 +00:00
Jean-Philippe Lang
7eb9cca660 Adds auto overflow to content div.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3177 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-17 18:21:50 +00:00
Jean-Philippe Lang
dfabadf4f7 Adds an admin layout that displays the admin menu in the sidebar.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3176 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-17 18:21:02 +00:00
Eric Davis
39b44b1cb9 Adds a Plugin API to allow one plugin to depend on another.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3175 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-16 02:07:46 +00:00
Jean-Philippe Lang
1c11d3403e Adds buttons to scroll the tabs when they overflow.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3174 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-14 20:13:29 +00:00
Azamat Hackimov
09e47a3b63 One missing string (#4380)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3173 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-14 17:44:18 +00:00
Azamat Hackimov
7e0ed924da Translation updates
* lt (#4384)
* pt-BR (#4380)
* ru
* zh-TW (#4352)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3172 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-14 17:38:05 +00:00
Jean-Philippe Lang
0b34755eb0 Moved charset meta tag before title (#4320).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3171 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-13 14:55:17 +00:00
Jean-Philippe Lang
9a452a5c35 Make sure user can not watch what he is not allowed to view.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3170 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-13 14:48:28 +00:00
Jean-Philippe Lang
bb477a3a0f Make sure users don't get notified for thing they can not view (#3589).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3169 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-13 14:26:54 +00:00
Jean-Philippe Lang
6610bb6b6c Moves watchers filtering logic to ActsAsWatchable.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3168 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-13 13:06:57 +00:00
Jean-Philippe Lang
f33231181f Makes user unwatch what he can no longer view after its permissions have changed (#3589).
A rake task (redmine:watchers:prune) is also added to prune existing watchers.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3167 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-13 12:39:22 +00:00
Eric Davis
c31a671973 Added a setting to configure the day that week start on (Monday or Sunday). (#4363)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3166 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-13 04:06:55 +00:00
Eric Davis
4fb554e95d Project custom fields are already copied via the Copy form. (#3367)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3165 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-13 03:37:26 +00:00
Eric Davis
17512e7efd Copy issue relations when copying a project. (#3367)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3164 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-13 03:37:21 +00:00
Jean-Philippe Lang
d58762a52d Roadmap: sort issues by project and prepend project name if different (#4373).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3163 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 15:33:31 +00:00
Jean-Philippe Lang
c66943c9b8 Removes changelog view.
All trackers can now be displayed on the roadmap. By default, only those marked as displayed on the roadmap are displayed.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3162 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 15:23:22 +00:00
Jean-Philippe Lang
21b52d2fd9 Display users then groups on project memberships view (#4389).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3161 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 11:20:26 +00:00
Jean-Philippe Lang
72ceefd36e Sort groups on user groups setting (#4389).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3160 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 11:05:06 +00:00
Jean-Philippe Lang
1f47d5856e Issue#done_ratio should not break if status is nil.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3159 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 11:02:53 +00:00
Jean-Philippe Lang
008f35277e Retrieve root_url if needed (#4377).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3158 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 10:35:16 +00:00
Jean-Philippe Lang
d905f2ce7e Allow blank value for IssueStatus#default_done_ratio.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3157 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 10:33:12 +00:00
Jean-Philippe Lang
d72778619f Fixes fr locale.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3156 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 10:18:26 +00:00
Jean-Philippe Lang
b63b5e5928 Locales update.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3155 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 10:15:13 +00:00
Jean-Philippe Lang
5c6ce51ec9 Adds workflow copy functionality (#1727).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3154 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 10:06:07 +00:00
Eric Davis
ddeaf9da96 Refactor conditions.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3153 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 06:18:45 +00:00
Eric Davis
7959101288 Search mail addresses in Principal#like
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3152 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-12 06:18:40 +00:00
Eric Davis
4fe14e71c2 Adds a Setting to control how an Issue's done_ratio is calculated:
* Issue field (default) - the done_ratio field for the Issue
* Issue status - uses the Issue Status's value

  #4274

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3151 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-11 18:48:34 +00:00
Jean-Philippe Lang
a83501364d Makes target version field on update form use the grouped combo.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3150 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-10 20:15:03 +00:00
Jean-Philippe Lang
272869c654 Fix target version field on issue update form.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3149 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-10 19:55:20 +00:00
Jean-Philippe Lang
7537e86fd2 Fixes that issue copy/move throws an error when status is not changed (#4369).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3148 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-09 16:04:45 +00:00
Jean-Philippe Lang
9f6612651c Cleaning workflow edit view.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3147 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-09 13:30:54 +00:00
Jean-Philippe Lang
1c3722b5cb Moves submit button to tracker form.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3146 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-09 11:03:55 +00:00
Jean-Philippe Lang
34d14be556 Adds custom field selection on tracker form.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3145 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-09 10:58:51 +00:00
Jean-Philippe Lang
8f9f56502d Slight change to the breadcrumb on custom field admin views.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3144 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-09 10:49:58 +00:00
Jean-Philippe Lang
84abeac304 Improve rdm-mailhandler exit status (#4368).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3143 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-09 10:44:15 +00:00
Jean-Philippe Lang
3918374d5c Add test for #4354.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3142 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-09 10:31:17 +00:00
Jean-Philippe Lang
96fe47ea19 Disabled broken textile references (#4354).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3141 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-09 10:30:23 +00:00
Jean-Philippe Lang
b090098952 Fixed: Bulk edit of issues throws 500 if no versions are defined on the project (#4366).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3140 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-09 09:22:16 +00:00
Jean-Philippe Lang
f2520385e4 Fixes tracker_id and custom_field_values assignment order for issues (#4353).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3139 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-09 09:12:29 +00:00
Jean-Philippe Lang
1de2a0b7c7 Ignore subversion root_url case when extracting relative url.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3138 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-09 08:39:51 +00:00
Jean-Philippe Lang
0fe389b841 Optimize updates of issue's shared versions.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3137 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-08 20:47:52 +00:00
Jean-Philippe Lang
8f85ba79bc No need to update issues from the version's project.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3136 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-08 16:25:27 +00:00
Jean-Philippe Lang
40e2af7ab9 Optimize issue updates when a version sharing changes.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3135 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-08 16:22:21 +00:00
Jean-Philippe Lang
517a87f8c5 Fixed: Subversion#latest_changesets ignores revision argument (#4360).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3134 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-08 16:06:22 +00:00
Jean-Philippe Lang
efeebd4278 Display shared versions in project settings (#4357).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3133 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-08 14:19:38 +00:00
Jean-Philippe Lang
e823efc41f Fixes a test broken by r3127.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3132 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-08 13:27:06 +00:00
Jean-Philippe Lang
174f014564 Fixes broken admin menu extension (#4351).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3131 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-07 20:16:30 +00:00
Jean-Philippe Lang
5266e328c0 Fixes Project#shared_versions for descendants sharing (#465).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3130 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-07 19:28:47 +00:00
Azamat Hackimov
84bf891bb5 Translation updates
* es (#4286)
* sv (#4310)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3129 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-06 19:37:39 +00:00
Jean-Philippe Lang
aab6e68865 Remove edit link on wiki page diff (#4346).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3128 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-06 17:04:33 +00:00
Jean-Philippe Lang
f2113e735d Removes the 'Copy' checkbox on the copy/move form.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3127 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-06 12:52:03 +00:00
Jean-Philippe Lang
a2b4bb0dfb Ability to add a new version from the issue form (#4315).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3126 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-06 11:09:12 +00:00
Jean-Philippe Lang
8f33c6589d Fixed: error when changing tracker on the new issue form (#4345).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3125 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-06 10:37:08 +00:00
Jean-Philippe Lang
719cd7cfce Adds translation strings for r3123.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3124 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-06 10:28:58 +00:00
Jean-Philippe Lang
5f8e9d7118 Version sharing (#465) + optional inclusion of subprojects in the roadmap view (#2666).
Each version of a project can be shared with:
* subprojects
* projects in the project hierarchy: ancestors + descendants (needs versions management permission on the root project)
* projects in the project tree: root project + all its descendants (same as above)
* all projects (can be set by admin users only)

Notes:
* when sharing a version of a private project with others projects, its name will be visible within the other projects
* a project with versions used by non descendant projects can not be archived

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3123 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-06 10:28:20 +00:00
Eric Davis
e178123569 Enhanced the Issue Bulk Copy feature:
* Add a Copy option to the Context menu for multiple issues.
* Added assigned to, status, start and due date options to the move/copy form.
* Allow Issue#move_to to change attributes on the moved/copied issue.

  #4117

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3122 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-04 22:46:12 +00:00
Jean-Philippe Lang
c870a7b9ef Do not notify users that are no longer allowed to view an issue (#3589, #4263).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3121 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-03 21:28:14 +00:00
Jean-Philippe Lang
8bc0f7888b Fixed: News from a project with 'news' module disabled, are still diplayed in the cross-project news list (#4333).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3120 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-03 21:16:08 +00:00
Eric Davis
e02da72947 Add a second action menu to IssuesController#show. (#4331)
Will have the update, log time, watch, copy, etc buttons.  Had to rework
how the watchers button works since it requires a unique DOM id for the
Ajax response.  Also modified the WatchersController to be able to replace
multiple sections of the page, e.g. two Watch links.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3119 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-03 18:41:00 +00:00
Jean-Philippe Lang
97c5362cfe Fixed: no error message when creating a category from the issue form fails (#1477).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3118 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-02 21:18:05 +00:00
Jean-Philippe Lang
f5f26a44c1 Removes useless class.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3117 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-02 21:13:03 +00:00
Jean-Philippe Lang
184aae5bf2 Replaces 'New category' link on the issue form with an icon.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3116 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-02 21:05:43 +00:00
Jean-Philippe Lang
c051947161 Add assertion about email notification on issue creation via email (#4228).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3115 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-02 19:52:56 +00:00
Jean-Philippe Lang
e5dc94fe82 Make use of #watched_by? in issue form.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3114 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-02 19:17:42 +00:00
Jean-Philippe Lang
7592a955fb Fixes tracker update on context menu (#2405).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3113 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-02 19:07:27 +00:00
Jean-Philippe Lang
346c569f98 Fixed: "None" category issue count is empty while grouping by category (#4308).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3112 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-12-02 18:57:17 +00:00
Jean-Philippe Lang
8b8c24e61f Fixes context menu broken by r3109 (#2405).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3111 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-30 19:18:09 +00:00
Jean-Philippe Lang
25e131f176 Remove duplicate validation.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3110 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-29 20:05:15 +00:00
Jean-Philippe Lang
f134e72fec Adds tracker update to context menu and bulk edit form (#2405).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3109 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-29 20:00:21 +00:00
Jean-Philippe Lang
88fcf484d4 Enable tracker update on issue edit form (#2405).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3108 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-29 19:46:40 +00:00
Jean-Philippe Lang
3d317a435b Adds an action to SysController to fetch changesets of one or all projects (#2925).
Exemples:
* /sys/fetch_changesets (=> fetches changesets for all active projects)
* /sys/fetch_changeseys?id=foo (=> fetches changesets for project foo only)

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3107 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-28 16:50:26 +00:00
Jean-Philippe Lang
4c2264ee42 Adds 2 buttons to easily reorder selected columns (#4272).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3106 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-28 11:59:45 +00:00
Jean-Philippe Lang
66540afc08 Adds dynamic columns selection on the issue list (#4272).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3105 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-28 11:34:12 +00:00
Jean-Philippe Lang
99b52c8796 Rescue invalid query statement error with an error message.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3104 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-28 10:29:48 +00:00
Jean-Philippe Lang
915bbcfaff Move issues, journals, versions queries from IssuesController to Query model.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3103 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-28 10:08:29 +00:00
Jean-Philippe Lang
724141a39a Cleaning edit/delete links style.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3102 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-27 20:57:35 +00:00
Jean-Philippe Lang
c2e2040fc0 Fixes fixtures loading.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3101 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-27 20:49:33 +00:00
Jean-Philippe Lang
f9732b02a2 Fixes a test failure.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3100 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-27 20:49:08 +00:00
Jean-Philippe Lang
b9151cb3e8 Fixes a test failure.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3099 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-27 20:32:28 +00:00
Jean-Philippe Lang
2416260af2 Add tabindex to login screen (#4299).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3098 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-27 20:30:15 +00:00
Jean-Philippe Lang
4af8765f15 Reverts r3072 (#4302: error raised when sorting on an association not included as column).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3097 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-27 19:59:10 +00:00
Jean-Philippe Lang
ae082205e2 Add failing test for #4302.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3096 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-27 19:56:09 +00:00
Jean-Philippe Lang
43fd27fd0c Show last update datetime (last attachment added) on document list (#4232).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3095 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-26 20:12:20 +00:00
Jean-Philippe Lang
f3bcb705f7 Display an error when authenticity token is invalid.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3094 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-25 20:45:16 +00:00
Jean-Philippe Lang
ebab5a0074 Remove broken cookies after upgrade from 0.8.x to prevent an error from Rails (#4292).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3093 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-25 20:28:56 +00:00
Eric Davis
b75e038255 Updated menus from JPL's feedback.
* Updated Mapper#push documentation
* Renamed :parent_menu to :parent
* Renamed the external API for :child_menus to :children.  Internally it needs
  to stay :child_menus because Tree::TreeNode already defines a children
  method for another purpose

  #4250

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3092 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-25 05:36:56 +00:00
Eric Davis
b0999e3764 Add support for unattached menus (generated dynamically)
A MenuItem can define a :child_menus option with a Proc.  When the menus
are rendered, the Proc will be run and the resulting MenuItems will be
added to the page as child menus

  #4250

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3091 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-25 05:36:50 +00:00
Eric Davis
1f06cf8899 Converted Menus to a Tree structure to allow submenus.
* Bundle the rubytree gem
* Patched RubyTree's TreeNode to add some additional methods.
* Converted the menu rendering to walk the Tree of MenuItems to render
  each item
* Added a menu option for :parent_menu to make this menu a child of the parent
* Added a bunch of tests
* Made MenuItem a subclass of Tree::TreeNode in order to use it's methods
  directly
* Changed the exceptions in MenuItem#new to be ArgumentErrors instead of the
  generic RuntimeError

  #4250

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3090 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-25 05:36:44 +00:00
Eric Davis
5a9528cf3d Added :view_my_account_contextual hook.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3089 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-25 01:14:19 +00:00
Jean-Philippe Lang
9b0acce9c9 Fixed: error raised when rendering text that contains an email address with textile disabled (#4268).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3088 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-24 22:02:14 +00:00
Jean-Philippe Lang
2398565193 Fixed: Editing issue notes removes quote link (#4279).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3087 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-24 18:00:57 +00:00
Jean-Philippe Lang
14e88ec420 Fixes revision form when browsing a subdirectory (#4281).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3086 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-24 17:55:45 +00:00
Jean-Philippe Lang
654ccddd76 Fixes css classes in table in admin/info (#4261).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3085 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-23 21:00:32 +00:00
Azamat Hackimov
58faed438d Translation updates
* pt-BR (#4229)
* ru
* zh-TW (#4236)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3084 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-22 16:14:10 +00:00
Jean-Philippe Lang
d8c5549168 Changes misleading scopes on Enumeration.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3083 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-21 12:00:49 +00:00
Jean-Philippe Lang
4bdfef4dc4 Fixed roadmap progress display error (#4255).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3082 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-21 10:50:36 +00:00
Jean-Philippe Lang
0485d3a524 Reset session on login/logout (#4248).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3080 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-21 10:02:39 +00:00
Jean-Philippe Lang
4e3202d2a2 Reverts r3014 (CodeRay back to 0.7.6).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3079 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-20 15:50:06 +00:00
Jean-Philippe Lang
d73fb1fab8 Load roles when loading project members.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3078 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-20 13:08:17 +00:00
Jean-Philippe Lang
c9bfdc009b Avoid useless database queries.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3077 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-20 12:57:52 +00:00
Eric Davis
ec4ba23248 Add a setting to pick alternative Gravatar images. #2734
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3076 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-17 03:10:49 +00:00
Jean-Philippe Lang
3fc655904f Copy issue status on project copy (#3877).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3075 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-16 20:06:37 +00:00
Jean-Philippe Lang
e24358bc43 Use /raw/ for url instead of ?format=raw for getting raw repository files (#1901, #4119).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3074 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-16 19:39:16 +00:00
Jean-Philippe Lang
dfd0b8bcdf Adds style for acronyms (#4224).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3073 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-16 18:33:09 +00:00
Jean-Philippe Lang
d84bd8edd8 Optimize associations loading on the issue list.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3072 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-16 18:28:44 +00:00
Jean-Philippe Lang
71bc44a89d Allow issue grouping by custom field (#2679).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3071 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-16 18:07:30 +00:00
Azamat Hackimov
2a3a6da45a Translation updates:
*pt-BR (#4223)
*ru


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3070 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-16 15:32:20 +00:00
Jean-Philippe Lang
0176430f5c Fixed: Alternate theme lacking right border if no sidebar present (#4069).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3069 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 21:15:31 +00:00
Jean-Philippe Lang
b2018dfa8a Adds a setting for new projects default modules (#1797).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3068 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 16:20:33 +00:00
Jean-Philippe Lang
1f1135e867 Adds links to versions on changelog.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3067 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 15:44:48 +00:00
Jean-Philippe Lang
c309210654 Eager load priorities on roadmap and changelog.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3066 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 15:42:44 +00:00
Jean-Philippe Lang
bccf6496f8 Fixes css classes on issue detail view (#4180).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3065 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 15:27:40 +00:00
Jean-Philippe Lang
ba7cf9c3ce Adds custom fields for versions (#4219).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3064 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 15:22:55 +00:00
Jean-Philippe Lang
7d57833740 Adds a 'Move and follow' button on Move/Copy view to be redirected to the created issue(s) rather than the source project issue list (#1847).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3063 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 14:31:19 +00:00
Jean-Philippe Lang
63c8675887 Make use of link_to_issue.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3062 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 14:07:37 +00:00
Jean-Philippe Lang
6838677e30 Remove dead code.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3061 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 13:43:44 +00:00
Jean-Philippe Lang
ded602c89f Run all tests for coverage.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3060 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 13:43:31 +00:00
Jean-Philippe Lang
534ce51154 Allow non admin users to add subprojects (#2963).
Subprojects can be added to the projects for which the user has add_project permission.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3059 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 13:22:08 +00:00
Jean-Philippe Lang
ea9a7c20e6 Set trunk version to 0.8.7.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3058 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-15 10:43:32 +00:00
Eric Davis
ea0bc56a65 Protect controllers from potential CSRF attacks. #4216
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3051 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-14 19:41:07 +00:00
Eric Davis
93bf1df5d4 Fix 500 errors with a POST request that requires a login. #4216
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3050 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-14 19:41:02 +00:00
Eric Davis
b2e4d8ad3f Fixed some tests that where looking for specific issue urls but weren't matching
the css classes for the link.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3049 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-14 19:40:56 +00:00
Jean-Philippe Lang
1a65eb8b08 Don't reveal issue subjects if user is only allowed to view spent time (#3187).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3043 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-14 14:10:09 +00:00
Azamat Hackimov
ed15b2aa14 Locales update
* pt-BR (#4214)
* ru
* zh (#4207)
* zh-TW (#4201)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3042 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-14 13:43:55 +00:00
Jean-Philippe Lang
bc37fcee74 Clean up ticket auto links.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3041 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-14 12:59:32 +00:00
Jean-Philippe Lang
cbeeaa4d4d Refactoring ApplicationHelper#link_to_issue.
Now displays issue subject by default.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3040 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-14 12:53:50 +00:00
Jean-Philippe Lang
dfd0204052 Add view_issues permission (#3187).
A migration adds this permission to all existing roles to preserve current behaviour.
This permission controls access to issues, roadmap and changelog.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3039 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-14 12:08:47 +00:00
Jean-Philippe Lang
326ed79b43 Fixed that "RE:" prefix is added to the subject each time the message is quoted (#4215).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3038 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-13 19:14:18 +00:00
Jean-Philippe Lang
8c769c546f Move checkbox for "Send account information to the user" to be clicked before the "Create" button (#4193).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3037 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-13 18:11:10 +00:00
Jean-Philippe Lang
28f0b5ce4a Fixed: Project#enabled_module_names= does not test if a module is already enabled (#4200).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3036 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-13 17:50:21 +00:00
Jean-Philippe Lang
0241003590 Fixed: Quoting in forums does not take the subject from the message being quoted (#4215).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3035 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-13 17:46:38 +00:00
Jean-Philippe Lang
d82738ad00 Add test for i18n number_to_human_size (#4208).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3034 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-12 22:00:29 +00:00
Eric Davis
cd110535a3 Adding the body of the feed to the failure message. #4204
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3033 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-12 04:59:54 +00:00
Eric Davis
bb8a397a13 Style the password field the same as the login field. #3845
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3032 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-12 04:44:28 +00:00
Jean-Philippe Lang
8267ded518 Allow [#id] as subject to reply by email (#3653).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3031 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-11 22:30:02 +00:00
Jean-Philippe Lang
1d8b4ee778 Fixed pre tags containing "<pre*" (#4125).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3030 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-11 16:23:11 +00:00
Jean-Philippe Lang
22d12032e7 Reject empty Mercurial entries (#4098).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3029 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-11 15:33:30 +00:00
Jean-Philippe Lang
4b83a0d848 Fixes diff parser for when lines starting with multiple dashes are removed (#4186).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3028 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-11 13:25:53 +00:00
Jean-Philippe Lang
58103680bd Redirect to the current page when using context menu on the issue list (#4184).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3027 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-11 11:35:27 +00:00
Jean-Philippe Lang
858f5cbf07 Hide paragraph terminator at the end of headings on html export (#4194).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3026 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-11 11:10:03 +00:00
Jean-Philippe Lang
b8b8cea288 Fixes jstoolbar lang files case (#4190).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3025 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-11 10:55:19 +00:00
Jean-Philippe Lang
c31411ec00 Fixes ApplicationHelper#link_to_user
* No link to a locked user page (closes #4182)
* Translate Anonymous string

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3024 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-11 10:48:54 +00:00
Jean-Philippe Lang
8f40750ad7 Adds a link to automatically close completed versions in project settings (#1245).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3023 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-09 18:53:12 +00:00
Jean-Philippe Lang
cc684803ba Ignore emails received from the application emission address to avoid hell cycles (#4139).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3022 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-08 13:51:53 +00:00
Jean-Philippe Lang
d4ccce3c72 Development database required for running rake test (closes #4153).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3021 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-08 13:15:59 +00:00
Jean-Philippe Lang
d201c54455 Adds version status to limit issue assignments (#1245).
Available version statuses are:
* open: no restriction
* locked: can not assign new issues to the version
* closed: can not assign new issues and can not reopen assigned issues

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3020 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-08 13:03:41 +00:00
Eric Davis
7c14c6d42e Added README.rdoc in the project root to display on Github
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3019 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-08 04:10:37 +00:00
Eric Davis
e02caeab0f Allow a config/additional_environment.rb file to customize the Rails::Initializer.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3018 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-08 02:16:42 +00:00
Jean-Philippe Lang
87d14dea10 Fixes SVG lib for ruby1.9.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3017 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-07 10:40:54 +00:00
Jean-Philippe Lang
86a9d90f07 Fixes distance of date in words calculation.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3016 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-07 09:50:16 +00:00
Jean-Philippe Lang
668ec7f694 Fixes a regexp for ruby1.9.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3015 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-07 09:43:02 +00:00
Jean-Philippe Lang
be41f7f473 Upgrade Coderay to 0.9.0.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3014 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-07 09:08:13 +00:00
Jean-Philippe Lang
3b9d8c2a72 Adds a few functional tests.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3013 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-07 08:44:56 +00:00
Jean-Philippe Lang
6245f49934 Fixed: Pre-filled time tracking date ignores timezone (#4160).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3012 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-06 19:41:03 +00:00
Jean-Philippe Lang
54e37b12fd Add test:coverage task.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3011 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-06 19:39:51 +00:00
Eric Davis
5f48256c20 Fixed a typo in Object daddy
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3010 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-05 21:32:26 +00:00
Jean-Philippe Lang
7b77301eab Fixed: first day of date range is not included in time report with SQLite (#3112).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3009 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-04 17:35:20 +00:00
Jean-Philippe Lang
c201581c05 Fixes test environments.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3008 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-04 17:15:20 +00:00
Jean-Philippe Lang
27e3fa2bed Use FasterCSV or ruby1.9 CSV instead of ruby1.8 builtin CSV.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3007 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-04 13:22:26 +00:00
Jean-Philippe Lang
4ea714fb91 Fixed: child_pages macro fails on wiki page history (#4152).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3006 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-04 12:36:45 +00:00
Jean-Philippe Lang
6994a81209 Fixed: Feed content limit ignored on issues list.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3005 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-04 12:31:37 +00:00
Jean-Philippe Lang
b2a55bda0c Sort the list of users to add to a group or project (#4150).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3004 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-04 11:17:55 +00:00
Jean-Philippe Lang
962535255c Set trunk version to 0.8.6
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3003 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-04 11:15:02 +00:00
Jean-Philippe Lang
9943f64ff0 Fixed: inline images in wiki headings (#4112).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2999 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-04 10:22:57 +00:00
Azamat Hackimov
03548f2d63 Locales update
* Swedish (#4161)
* Traditional Chinese (#4145)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2994 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-11-03 15:09:18 +00:00
Jean-Philippe Lang
5667b2b7d1 Translation updates (closes #4102, #4108, #4118).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2993 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-31 10:09:43 +00:00
Jean-Philippe Lang
279e81eb92 Fixed a test failure.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2992 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-29 18:51:10 +00:00
Jean-Philippe Lang
92ec35e657 Unified UsersController#list and #index.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2991 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-29 18:48:19 +00:00
Jean-Philippe Lang
e64fb6a728 Fixes users links.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2990 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-29 18:44:16 +00:00
Jean-Philippe Lang
e117dc8c9c Include missing fixtures.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2989 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-29 18:40:00 +00:00
Jean-Philippe Lang
a842769c3f AccountController#show (/account/show/:id) moved to UsersController#show (/users/:id).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2988 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-29 18:37:00 +00:00
Jean-Philippe Lang
ac56d1d5e5 Do not show user profile if no visible project or activity (#4129, #3720).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2986 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-29 18:09:40 +00:00
Jean-Philippe Lang
72d208cb35 Typo fixed (#4134).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2985 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-29 17:59:09 +00:00
Jean-Philippe Lang
86874785b7 Fixed error on repository when there are no comments in a changeset (#4126).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2983 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-29 17:55:59 +00:00
Jean-Philippe Lang
a658679d29 Add etag check on the activity view to avoid rendering when not modified.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2982 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 17:27:24 +00:00
Jean-Philippe Lang
06fff6295c Add indexes on various timestamps to speed up the activity view.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2981 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 16:56:46 +00:00
Jean-Philippe Lang
9aa2b6b9a4 IssuesController#destroy accepts POST only (#4107).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2980 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 13:38:48 +00:00
Jean-Philippe Lang
821f9eb390 HTML escaping (#4106).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2979 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 13:28:36 +00:00
Jean-Philippe Lang
a3fcdfe391 Add translations for new permission.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2978 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 12:13:51 +00:00
Jean-Philippe Lang
97b4e75478 Add a permission to remove issue watchers (#2450).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2977 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 12:11:53 +00:00
Jean-Philippe Lang
6fedbf60d5 Allow project forums copy.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2976 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 11:23:46 +00:00
Jean-Philippe Lang
6dfe0395a5 Display stats about objects that can be copied.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2975 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 11:10:35 +00:00
Jean-Philippe Lang
1a1bfbfb07 Do not ignore parent project setting on project copy (#3386).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2974 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 10:56:32 +00:00
Jean-Philippe Lang
a18676b669 Prevent mass-assignment warnings.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2973 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 10:51:21 +00:00
Jean-Philippe Lang
4e73685af7 Reset timestamps and wiki page hierarchy on project copy.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2972 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 10:44:03 +00:00
Jean-Philippe Lang
875eb47ad1 Prevent mass-assignment warnings.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2971 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 10:35:59 +00:00
Jean-Philippe Lang
2e675342cb Removes debug message.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2970 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 10:31:01 +00:00
Jean-Philippe Lang
a9fb11c0f5 Fixes project wiki copy.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2969 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 10:30:39 +00:00
Jean-Philippe Lang
eecec44ed2 Add a test that breaks before r2967 (broken project wiki copy).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2968 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-25 10:11:06 +00:00
Jean-Philippe Lang
9f12a14382 Wiki auto creation temporary disabled until project wiki copy is fixed (2 wikis created).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2967 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-24 16:12:13 +00:00
Jean-Philippe Lang
5b787785b4 Project copy: let the user choose what to copy from the source project (everything by default).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2966 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-24 13:30:23 +00:00
Jean-Philippe Lang
6842941adc Removes debug code.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2965 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-24 12:33:34 +00:00
Jean-Philippe Lang
383b2bd903 Small fix to HTML title.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2964 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-24 12:33:08 +00:00
Jean-Philippe Lang
83717a9b75 Add mail field to admin user search filter (#3882).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2963 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-24 12:21:09 +00:00
Jean-Philippe Lang
9233a07a23 Adds a test for attached image inside a link (#4033).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2962 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-24 12:19:22 +00:00
Jean-Philippe Lang
de8dcc5b26 Sets proper content type for plain text mails (#3970).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2961 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-24 11:57:01 +00:00
Jean-Philippe Lang
f206dfe9b5 Reverts r2770 (#3391).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2960 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-24 11:35:35 +00:00
Jean-Philippe Lang
a68d8a7b32 Sets the current project as the default value of project jump box (#4053).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2959 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-24 11:29:49 +00:00
Jean-Philippe Lang
2cec9f87ab Translation updates (closes #4054, #4060, #4075).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2958 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-24 11:22:06 +00:00
Jean-Philippe Lang
cabf052127 Prevent undefined method `<=>' for nil:NilClass in AbstractAdapter (#4098).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2957 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-24 11:08:09 +00:00
Jean-Philippe Lang
2445250960 Fixes form buttons on projects/settings/activities (closes #4083).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2956 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-22 17:51:23 +00:00
Jean-Philippe Lang
6ffe1926ab Remove hard coded string in a view (closes #4076).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2955 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-22 17:46:54 +00:00
Jean-Philippe Lang
04ae25f6b0 Do not render hidden news edit form if user is not allowed to edit (closes #4068).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2954 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-22 17:42:17 +00:00
Jean-Philippe Lang
e5c4cfc688 Add missing strings (rake locales:update).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2953 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-22 17:39:12 +00:00
Eric Davis
ff3d0fe4db Fixed some merge bugs. #4077
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2952 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 22:35:03 +00:00
Eric Davis
2e0cbd2840 Added all list and boolean custom data fields to the Time Report. #4077
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2951 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 22:34:57 +00:00
Eric Davis
37d401ac58 When a specific TimeEntryActivity are change, associated TimeEntries will be
re-assigned to the correct record.

* Renamed build to create since the methods now create objects.
* Removed call to Project#save that isn't needed since objects are saved directly now.
* Wrapped the activity creation and updates in a SQL transaction so TimeEntries
  will remain in a consistent state if there is an error.
* When a Project's TimeEntryActivities are destroyed, they are now
  reassigned to the parent TimeEntryActivity.

  #4077

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2950 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 22:34:52 +00:00
Eric Davis
5833ba9f81 Added a Activities tab to Project Settings
* Changed Project#activities to allow getting inactive Activities also:
  * Changed the Enumeration#all named_scope to exclude project specific Activities
  * Changed the Project has_many time_entry_activities to include all by default
    and provided an #active method to filter them to active ones only
  * Split Project#activities to two methods and gave it a parameter that is used
    to determine if inactive activities are included (default is no)
* Added a reset button to delete all project specific activities.
* Added ProjectsController#reset_activities to remove the project
  specific activities
* Added a HTTP DELETE route for reset_activities
* Changed the permissions for managing project activities to have access to the
  ProjectsController#reset_activities action
* Added a way to bulk save Project specific Activities in ProjectsController
  * #save_activities will save all the changed activities, including update the
    existing records
  * Added helper methods to the controller which will be refactored later
  * Allow the same TimeEntryActivity names on different projects

  #4077

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2949 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 22:34:45 +00:00
Eric Davis
e615266e9a Changed the Timelogs to use both the Systemwide and Project specific TimeEntryActivities
* Added Project#activities to return all the Systemwide and Project specific
  activities, excluding Systemwide ones that are overridden.
* Added some tests for TimelogHelper.

  #4077

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2948 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 22:34:39 +00:00
Eric Davis
29301c8a38 Added project specific Enumeration overrides.
These will be used to track if Enumeration's custom data or active state
is overridden on a project.  An overridden Enumeration is one that is associated
with a parent Enumeration.

  #4077

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2947 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 22:34:34 +00:00
Eric Davis
e76d4c5c4c Added an active field track if an Enumeration is active on the frontend view.
* Changed TimelogHelper#activity_collection_for_select_options to only use
  active TimeEntryActivities.
* Changed TimelogHelper#activity_collection_for_select_options to return a blank
  option if the time_entry's current activity is inactive.

  #4077

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2946 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 22:34:28 +00:00
Eric Davis
ac4937a767 Enumerations can now have custom fields defined on them. #4077
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2945 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 22:34:22 +00:00
Eric Davis
29ab7b4108 Updated the unit test so Enumerations are tested separately from the subclasses.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2944 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 22:34:17 +00:00
Jean-Philippe Lang
07aa3c55bd Contextual quick search (#3263).
Eg. when viewing issues, the quick search will search issues only.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2943 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 17:07:18 +00:00
Eric Davis
739e11702a Change the order of checkboxes for boolean custom fields.
Broken in r2887 for the Rails 2.3.4 upgrade.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2942 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 03:31:20 +00:00
Eric Davis
d40756d611 Renamed Project#public named_scope so it will not override Ruby's public method
#4056

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2941 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 03:21:31 +00:00
Eric Davis
50bab8b429 Mocha is needed for tests.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2940 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-21 03:13:24 +00:00
Eric Davis
a41ba2aed7 Renamed the default "Assigned" status to "In Progress". #3605
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2939 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-20 05:06:14 +00:00
Eric Davis
b887cef7af Moved object daddy helpers to their own module.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2938 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-20 00:36:55 +00:00
Eric Davis
6456f7c4a4 Updated some object_daddy exemplars for the Models.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2937 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-20 00:36:51 +00:00
Eric Davis
61c09b6442 Added shoulda macros to test Project's associations
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2936 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-19 00:27:13 +00:00
Eric Davis
257c92f8f9 Replaced a custom test with a shoulda macro.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2935 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-19 00:27:08 +00:00
Eric Davis
a150689be9 Improved Project#copy to copy more data from the source Project. #3367
* Versions
* Associate the copied issues with the new versions
* Wiki
* WikiPages
* WikiContents
* IssueCategories

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2934 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-19 00:07:37 +00:00
Eric Davis
6531c3f887 Preselect the issue custom fields from the source project when copying. #4045
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2933 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-19 00:07:32 +00:00
Eric Davis
945ea9b01c Refactored duplicated test code to a setup.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2932 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-19 00:07:27 +00:00
Eric Davis
548d5a21f6 Converted Project#copy test to use shoulda
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2931 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-19 00:07:23 +00:00
Eric Davis
35333367df Configured object_daddy to help generate test data instead of fixtures. #4004
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2930 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-18 22:25:00 +00:00
Azamat Hackimov
ee9c2d3d88 pt-BR update (#4040)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2929 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-18 21:27:26 +00:00
Eric Davis
70118fb136 Adding missing database indexes for foreign keys and STI fields.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2928 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-17 22:23:29 +00:00
Eric Davis
07ffad4a7e Rename the wiki toolbar's JavaScript variable, 'toolbar' conflicts with
the browser's variable.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2927 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-17 21:17:46 +00:00
Eric Davis
5e539c31b0 Fixed the generated test in the redmine_plugin_model. (#3927)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2926 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-17 21:08:33 +00:00
Eric Davis
4425acafff Setup shoulda for testing. (#4005)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2925 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-17 20:37:23 +00:00
Jean-Philippe Lang
afd2d4afc6 Fixed: HTML entities displayed when editing an issue note (#3996).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2924 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-11 10:36:11 +00:00
Jean-Philippe Lang
0b3e3471b0 Makes migration scripts load tickets in batches (#4011).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2923 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-11 10:12:07 +00:00
Jean-Philippe Lang
0ac07afc67 Fixes migration scripts broken by r2726.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2922 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-11 10:08:22 +00:00
Jean-Philippe Lang
6224f7caca da locale updated (closes #3919).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2921 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-11 09:24:45 +00:00
Jean-Philippe Lang
1088c7360b el locale updated (closes #3932).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2920 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-11 09:20:57 +00:00
Jean-Philippe Lang
c5d8bbeb8c translation updates (closes #4016, #4017).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2919 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-11 09:17:32 +00:00
Jean-Philippe Lang
4c42e1a08f ko locale updated (closes #3954).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2918 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-11 09:15:33 +00:00
Jean-Philippe Lang
ca250c1f2e ro locale updated (closes #2900).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2917 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-11 08:33:46 +00:00
Jean-Philippe Lang
f65133093a Fixes project shortcut combo.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2916 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-11 08:20:39 +00:00
Jean-Philippe Lang
d8ba5c2a06 Fixes MailHandler for ruby1.9.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2915 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-10 15:25:34 +00:00
Jean-Philippe Lang
4450e6f24b String#each removed in ruby1.9.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2914 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-10 15:19:27 +00:00
Jean-Philippe Lang
9cd662f8dc Fixes some test failures due to Array#to_s behaviour in ruby1.9.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2913 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-10 15:14:08 +00:00
Jean-Philippe Lang
22d3d5a3b0 Defines String#to_a (Object#to_a removed in ruby1.9).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2912 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-10 15:09:19 +00:00
Jean-Philippe Lang
531eb65557 Fixes a wrong number of arguments error with ruby1.9.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2911 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-10 15:04:08 +00:00
Jean-Philippe Lang
38dc4d1cf9 Sets file encoding to utf-8 for ruby 1.9.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2910 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-10 15:00:28 +00:00
Jean-Philippe Lang
ac8a67191f Renames uploaded_test_file helper.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2909 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-10 14:56:29 +00:00
Jean-Philippe Lang
94d34887cc Change deprecated dbfile parameter.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2908 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-10 14:37:47 +00:00
Jean-Philippe Lang
8ffc61f66c Completes r2856 (#3979).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2907 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-10 11:11:01 +00:00
Jean-Philippe Lang
480ccbf045 French locale update.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2906 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-10 10:30:21 +00:00
Jean-Philippe Lang
6656d41a41 Fixes User/CustomValue association broken by r2869 (#3978).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2905 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-10 10:16:00 +00:00
Eric Davis
bcbf08017a Upgrade the Engines plugin to get the bugfix for Rails::Plugin::GemLocator
This update will allow Rails Engines to be installed as gems.  For example:

    # config/environment.rb
    Rails::Initializer.run do |config|
      ...
      config.gem 'timesheet_plugin'
    end

Will load the timesheet_plugins's RubyGem and allow it to work as if it was
installed to vendor/plugins.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2904 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-07 16:43:50 +00:00
Jean-Philippe Lang
4e811bebd1 Fixes Repository#find_changeset_by_name with PostgreSQL (#3937).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2903 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-10-02 14:49:38 +00:00
Eric Davis
19df1b636c Fixed a bug where the form would POST causing a routing error. (#3918)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2902 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-25 00:39:02 +00:00
Azamat Hackimov
e6ac92487a Translation updates
* Japanese (#3865)
* Portuguese Brazilian (#3864)
* Russian
* Spanish (#3871)
* Traditional Chinese (#3863)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2901 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-24 15:03:23 +00:00
Jean-Philippe Lang
30ad78e57d Fixed: RepositoriesController#revision may show wrong revision (#3779).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2898 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-20 15:20:22 +00:00
Jean-Philippe Lang
e89d4825dd Set mime-type property (#3885).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2897 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-20 14:25:48 +00:00
Jean-Philippe Lang
dd633322f9 Unexpected closing tag removed.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2896 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-20 14:08:52 +00:00
Jean-Philippe Lang
21e18c1eb4 Adds missing native eol properties.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2895 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-20 14:06:57 +00:00
Jean-Philippe Lang
52a6b0a21e Fixed: Custom values with a nil value cause error on (project|account)/show (#3705).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2894 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-20 09:56:06 +00:00
Jean-Philippe Lang
41cbd239c4 Makes timelog report work at issue level (#2935).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2893 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-20 09:30:41 +00:00
Jean-Philippe Lang
b4c55ea4de Makes saved query filters visible (#2883).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2892 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-20 07:39:53 +00:00
Jean-Philippe Lang
3477ded32a Makes tickets and timelogs filters collapsible (UI).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2891 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-20 07:24:16 +00:00
Jean-Philippe Lang
cc3c8a717c Update version to 0.8.5.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2889 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-17 17:49:29 +00:00
Azamat Hackimov
d3691239fa Russian update (#3859)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2888 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-13 17:41:36 +00:00
Eric Davis
7b0cb6aba8 Upgraded to Rails 2.3.4 (#3597)
* Ran the Rails upgrade
* Upgraded to Rails Engines 2.3.2
* Added a plugin to let Engines override application views.
* Converted tests to use the new classes:
** ActionController::TestCase for functional
** ActiveSupport::TestCase for units
* Converted ActiveRecord::Error message to a string.
* ActiveRecord grouping returns an ordered hash which doesn't have #sort!
* Updated the I18n storage_units format.
* Added some default initializers from a fresh rails app
* Changed the order of check_box_tags and hidden_field_tags.  The hidden tag
  needs to appear first in Rails 2.3, otherwise it will override any value in
  the check_box_tag.
* Removed the custom handler for when the cookie store is tampered with.
  Rails 2.3 removed the TamperedWithCookie exception and instead Rails will not
  load the data from it when it's been tampered with (e.g. no user login).
* Fixed mail layouts, 2.3 has problems with implicit multipart emails that
  use layouts.  Also removed some custom Redmine mailer code.
* Fixed a bug that occurred in tests where the "required" span tag would be
  added to the :field_status translation.  This resulted in an email string of:

    <li>Status<span class="required"> *</span><span class="required"> *</span>

  Instead of:

    <li>Status: New</li>

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2887 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-13 17:14:35 +00:00
Azamat Hackimov
fb349dc4ab Translation updates
* Korean (#3855)
* Russian
* Traditional Chinese (#3853)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2880 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 17:26:06 +00:00
Jean-Philippe Lang
9ec5c32861 Fixes group update form url (#3854).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2879 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 16:12:42 +00:00
Jean-Philippe Lang
041277235b Slight change to the visibility SQL statement.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2878 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 11:22:42 +00:00
Jean-Philippe Lang
8faa66f68f Adds spent time to the activity view (#3809).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2877 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 10:37:49 +00:00
Jean-Philippe Lang
563c927e13 Fixes Textile reference link (#3812).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2876 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 10:06:17 +00:00
Jean-Philippe Lang
aca6ed13fc Fixed: issue attachments are saved even if the issue has been updated by another user (#3846).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2875 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 09:59:38 +00:00
Jean-Philippe Lang
8e3222195b Korean translation update (#3847).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2874 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 09:37:26 +00:00
Jean-Philippe Lang
275b555b09 Email handler: set a default issue subject if the email subject is blank (#3850).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2873 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 09:33:22 +00:00
Jean-Philippe Lang
7f4635022f Makes 'delete links' the same on admin views.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2872 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 09:25:39 +00:00
Jean-Philippe Lang
ede9960444 Refactoring of tabs rendering.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2871 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 09:13:13 +00:00
Jean-Philippe Lang
d4ed5ec30b Adds strings for user groups.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2870 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 08:38:02 +00:00
Jean-Philippe Lang
7707457145 User groups branch merged.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2869 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 08:36:46 +00:00
Eric Davis
847c7367b4 Added plugin hook, :view_projects_show_sidebar_bottom
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2866 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-12 00:08:38 +00:00
Azamat Hackimov
02d07d8a43 Translation update: pt-BR (#3839)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2860 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-10 14:22:44 +00:00
Azamat Hackimov
d41544402c Translation updates
* Traditional Chinese (#3828)
* Swedish (#3829)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2859 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-07 11:09:43 +00:00
Azamat Hackimov
edbfd09990 Changing l10n messages (fixes #3807)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2856 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-04 22:28:35 +00:00
Eric Davis
677d1769d6 Reset the default language for the test, autotest was caught it staying at :no
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2855 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-04 03:24:20 +00:00
Eric Davis
609faba6a3 Allow referencing issue numbers in brackets. This style is used by other
bug trackers.

Examples:

* "[#nnn] Worked on this issue"
* "[#nnn, #mmm] Worked on these"
* "[#nnn #mmm] Working some more"

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2854 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-03 01:21:11 +00:00
Eric Davis
cd1e094ce8 Missing 'test_' prefix on a helper test caused it to not run.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2853 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-03 01:03:10 +00:00
Azamat Hackimov
925104288a Romanian translation update, adding missed diacritics (#3797)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2852 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-02 18:01:03 +00:00
Azamat Hackimov
ec670f56ac Greek translation support, thanks to Vaggelis Typaldos for work (#3795)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2851 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-01 18:16:31 +00:00
Azamat Hackimov
f9fe92dcb3 Translation updates:
* Swedish (#3774)
* Traditional Chinese (#3772)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2850 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-01 18:13:49 +00:00
Jean-Philippe Lang
5b97570693 SCM:
* add latest changesets for the current directory when browsing the repository and a link to the full log
* ability to diff a directory (#3575)

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2849 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-01 12:13:17 +00:00
Eric Davis
71abeb5898 Add some margin to journal notes if Gravatars are enabled. (#3771)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2848 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-09-01 04:09:32 +00:00
Azamat Hackimov
f1f65794e4 New strings for translation (fixes #2872), thanks for Enderson Maia for pointing and translation
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2847 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-21 19:43:33 +00:00
Azamat Hackimov
f5517cd834 Translations updates:
* pt-BR.yml (#3752)
* ru.yml
* zh-TW.yml (#2750)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2846 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-21 19:27:20 +00:00
Jean-Philippe Lang
aae5c7d359 Small CSS fix.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2845 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-17 16:37:31 +00:00
Jean-Philippe Lang
e10577e9ed SCM:
* Fixes file log for non-Git repositories (Repository#latest_changesets ignores path argument)
* No longer used Repository#changesets_for_path method removed

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2844 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-17 16:32:24 +00:00
Jean-Philippe Lang
a4d7a03b14 SCM browser:
* don't show repository commits when showing a subfolder
* remove obsolete view browse.rhtml

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2843 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-17 14:26:13 +00:00
Jean-Philippe Lang
6fb80efdae Fixes argument in Repository#latest_changesets.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2842 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-17 14:12:51 +00:00
Jean-Philippe Lang
a49506ce5f Use selected columns in the issues PDF export (#1190).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2841 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-17 14:08:42 +00:00
Eric Davis
c28b044d68 Added branch and tag support to the git repository viewer. (#1406)
Many thanks to Adam Soltys and everyone else who tested this patch.

* Updated git test repository so it has a branch with some differences from the master branch
* Moved redmine diff class into a module so as not to clash with diff-lcs gem which is required by grit
* Find changesets from all branches, not just master
* Got revision browsing working
* Got file actions working properly
* Allow browsing by short form of commit identifier
* Added a method to retrieve repository branches
* Allow browsing by branch names as well as commit numbers
* Handle the case where a git repository has no master branch
* Expand revision box and handle finding revisions by first 8 characters
* Added branches dropdown to repository show page
* Combined repository browse and show into a single action.  Moved branch/revision navigation into a partial.
* Renamed partial navigation -> breadcrumbs
* Made it so latest revisions list uses branch and path context
* Preserve current path when changing branch or revision
* Perform slightly more graceful error handling in the case of invalid repository URLs
* Allow branch names to contain periods
* Allow dashes in branch names
* Sort branches by name
* Adding tags dropdown
* Need to disable both branches and tags dropdowns before submitting revision form
* Support underscores in revision (branch/tag) names
* Making file history sensitive to current branch/tag/revision, adding common navigation to changes page
* Updated translation files to include labels for 'branch', 'tag', and 'view all revisions'
* Reenable fields after submit so they don't look disabled and don't stay disabled on browser back button
* Instead of dashes just use empty string for default dropdown value
* Individual entry views now sport the upgraded revision navigation
* Don't display dropdowns with no entries
* Consider all revisions when doing initial load
* Fixed bug grabbing changesets.  Thanks to Bernhard Furtmueller for catching.
* Always check the entire log to find new revisions, rather than trying to go forward from the latest known one
* Added some cleverness to avoid selecting the whole changesets table any time someone views the repository root
* File copies and renames being detected properly
* Return gracefully if no revisions are found in the git log
* Applied patch from Babar Le Lapin to improve Windows compatibility

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2840 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-15 22:41:40 +00:00
Eric Davis
a39bc8f1f4 Adding .gitignore based on the svn-ignores.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2839 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-15 19:27:49 +00:00
Eric Davis
30526d55c3 Added missing fixture file from r2837 (#3731)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2838 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-13 16:57:36 +00:00
Eric Davis
9886827a66 Allow spaces between the keyword and colon in incoming mail. (#3731)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2837 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-12 23:26:32 +00:00
Azamat Hackimov
d7d72c43c8 Translation updates
* Korean (#3650)
* Spanish (#3664)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2836 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-11 16:06:16 +00:00
Eric Davis
ff0e5b019c Fixed missing translation for 'field_issue_to', the translation files ended in _id.
#3678

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2835 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-02 04:55:16 +00:00
Eric Davis
9ed048dc42 Include the issue status in search results and the Activity page. (#3700)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2834 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-02 04:36:06 +00:00
Eric Davis
06ff26f092 Enable SSL gravatars when Redmine is using https. (#2718)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2833 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-02 04:19:49 +00:00
Eric Davis
560e915c50 Upgraded the gravatar plugin from http://github.com/woods/gravatar-plugin
This will update the gravatar.com url (#2921) and provide support for SSL
gravatars (#2718).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2832 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-02 04:19:44 +00:00
Eric Davis
bb61c9a0ec Added two new plugin hooks to Administration > Users > Memberships
* view_users_memberships_table_header
* view_users_memberships_table_row

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2829 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-08-01 18:14:39 +00:00
Jean-Philippe Lang
6da352dc47 Display all custom fields on issue PDF.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2828 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-29 19:05:03 +00:00
Jean-Philippe Lang
cfd7d07b69 Fixed: custom fields are not displayed in the same order on issue form and view.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2827 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-29 19:04:27 +00:00
Jean-Philippe Lang
9e07fb5e04 Remove hard-coded styles from ticket attributes table.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2826 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-29 18:35:29 +00:00
Eric Davis
26bbad5f23 Switch from a custom rcov task to the metric_fu gem.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2825 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-27 00:55:43 +00:00
Jean-Philippe Lang
dcba9f18e6 Allow line breaks in wiki table cells (#2346).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2824 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-19 14:23:15 +00:00
Jean-Philippe Lang
5ec4d4cdab Adds a link to the file history on repository file views.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2823 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-18 09:59:55 +00:00
Jean-Philippe Lang
bf0ddc2886 Fixes artefacts in truncated search results (#3622).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2822 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-18 09:46:20 +00:00
Jean-Philippe Lang
36d8f35192 Prevent creation of project with identifier 'new' (#3602).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2821 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-18 08:06:51 +00:00
Jean-Philippe Lang
d41bd93acb Fixed: error raised when trying to add an empty comment to a news (#3615).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2820 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-18 07:51:17 +00:00
Eric Davis
1a9942ba99 Change subversion adapter to not cache authentication and run non interactively
#3424

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2819 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-10 03:24:46 +00:00
Eric Davis
a9ee946053 Change links to closed issues to be a grey color. (#3495)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2818 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-10 03:03:13 +00:00
Azamat Hackimov
6404945683 Russian update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2817 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-08 19:52:14 +00:00
Azamat Hackimov
03d1edaef1 Translation updates:
* Italian (#3501)
* Korean (#3552)


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2816 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-08 18:42:31 +00:00
Jean-Philippe Lang
9c8daee045 Adds permalinks to message replies (#3587).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2815 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-05 19:52:09 +00:00
Jean-Philippe Lang
6151e95e37 Adds links to issues in reminder HTML template (#3541).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2814 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-05 14:14:44 +00:00
Jean-Philippe Lang
e54d183d20 Ability to send an email with password when changing a user's password (#3566).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2813 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-05 14:06:14 +00:00
Jean-Philippe Lang
ad90811e40 Administration panel breadcrumbs (#3314).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2810 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-05 12:22:02 +00:00
Jean-Philippe Lang
937823a0d8 Keep status filter on the projects list when un/archiving a project (#3530).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2806 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-04 13:14:28 +00:00
Jean-Philippe Lang
ef8ef596de Fixes notice_failed_to_save_issues string in various locales (#3574).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2805 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-04 12:55:56 +00:00
Jean-Philippe Lang
da22a9c8d6 Do not require a non-word character after a comma in Redmine links (#3561).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2804 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-04 12:48:25 +00:00
Jean-Philippe Lang
5afa190a9a Adds issue last update timestamp (#3565).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2803 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-04 12:36:26 +00:00
Jean-Philippe Lang
00f7a02959 Set trunk version to latest stable release (#3233).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2802 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-04 12:17:02 +00:00
Jean-Philippe Lang
9c287a0f98 DE locale update.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2801 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-04 12:13:11 +00:00
Jean-Philippe Lang
6994d1c23b Actually block issues from closing when a blocking issue isn't closed (#1740).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2800 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-04 12:07:03 +00:00
Jean-Philippe Lang
85f634481e Fixed: login form fields have different width with IE (#3582).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2799 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-04 07:57:04 +00:00
Jean-Philippe Lang
202d01664a Do not use settings/settings partial name in sample plugin (#3557).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2798 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-02 18:40:06 +00:00
Jean-Philippe Lang
6ff5891100 Fixes links to wiki atom feed (#3581).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2797 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-07-02 18:21:45 +00:00
Jean-Philippe Lang
a7bb63a182 Fixed: case sensitivity in issue subject filtering (#3536).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2796 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-28 12:12:27 +00:00
Jean-Philippe Lang
aa07e8505e Fixes wiki diff escaping.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2794 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-28 11:55:49 +00:00
Jean-Philippe Lang
14c7a887ee Fixes broken --unknown-user option in mailhandler script (#3514).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2793 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-25 16:16:59 +00:00
Jean-Philippe Lang
72a396227a Fixes default data loader broken by r2777 (#3507).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2792 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-23 20:17:47 +00:00
Eric Davis
2221d68c4d Added workaround for a Ruby BigDecimal bug, CVE-2009-1904. (#3475)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2790 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-14 16:22:23 +00:00
Jean-Philippe Lang
b3afde14fa Ability to accept incoming emails from unknown users (#2230, #3003).
An option lets you specify how to handle emails from unknown users:
* ignore: the email is ignored (previous and default behaviour)
* accept: the sender is considered as an anonymous user
* create: a user account is created (username/password are sent back to the user)

Permissions have to be consistent with the chosen option. Eg. if you choose 'create', the 'Non member' role must have the 'Add issues' permission so that an issue can be created by an unknown user via email. If you choose 'accept', the 'Anonymous' role must have this permission.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2789 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-14 14:48:34 +00:00
Jean-Philippe Lang
c48193f8c1 Ignores test repositories.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2788 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-14 10:07:38 +00:00
Jean-Philippe Lang
7642b5a9ab Fixed: editing a message may cause sticky attribute to be NULL (#3356).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2787 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-14 09:19:20 +00:00
Jean-Philippe Lang
5e76040256 Fixes drop-down list selected value (#3472).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2786 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-13 09:32:23 +00:00
Eric Davis
724cf47605 Added the Rails i18n language file to the plugin generator.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2785 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-10 03:39:02 +00:00
Eric Davis
97383f78d0 Added an empty tmp/test directory so rake test will run out of the box.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2784 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-08 02:07:30 +00:00
Eric Davis
199b213bbb Converted script/about to use Unix line ending. It has DOS line endings and would fail.
(#3387)

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2783 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-07 18:22:27 +00:00
Jean-Philippe Lang
ddd14fe86b Fixes locales broken by r2781 (#3456).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2782 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-07 12:36:46 +00:00
Azamat Hackimov
43995a43a5 Translation updates (#3401, #3409, #3434, #3446)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2781 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-06 17:20:01 +00:00
Jean-Philippe Lang
6a54a0c94c Fixed: Bazaar "[merge]" tags parsing fails (#3445).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2780 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-06 10:49:36 +00:00
Jean-Philippe Lang
9c282842a9 Do not start user session when accessing atom feed with token-based authentication.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2779 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-06 10:20:27 +00:00
Jean-Philippe Lang
6da0542af4 Delete previous tokens when creating a new one.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2778 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-06-02 17:24:50 +00:00
Eric Davis
62e58f26b0 Changed Enumerations to use a Single Table Inheritance
* Added migrations to change Enumerations to an STI relationship
* Added TimeEntryActivity model (STI)
* Added DocumentCategory model (STI)
* Added IssuePriority model (STI)
* Added Enumeration#get_subclasses to get a list of the subclasses of Enumeration
* Changed Enumeration to use the STI type field instead of the opt field
* Changed Enumeration#opt to return the old opt values but with a deprecation warning.
* Removed Enumeration::OPTIONS
* Removed the dynamic named_scopes in favor of specific named_scopes.  Kept for
  compatibility reasons.
* Added Enumeration#default so each subclass can easily find it's default record.
* Fixed Enumeration#default to use the STI scoping with a fake default scope for finding Enumeration's default.
* Added a 'all' named scope for getting all records in order by position.
* Added Deprecation warnings to the old named_scopes in Enumerations.
* Moved various methods off of Enumeration and onto the concrete classes
* Changed the EnumerationsController to use types
* Updated the Enumeration list template
* Added has_many relationships to the Enumeration STI classes.
* Fixes for tests.

  #3007

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2777 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-30 23:30:36 +00:00
Eric Davis
fbfb349496 Added Redmine::Info.issue to get links to specific issue numbers.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2776 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-30 23:00:22 +00:00
Eric Davis
211b13c9ec Added plugin hook, :controller_timelog_edit_before_save. (#3341)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2775 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-30 22:49:39 +00:00
Eric Davis
4ba8308507 Added more plugin hooks:
* :controller_messages_new_after_save
* :controller_messages_reply_after_save
* :controller_wiki_edit_after_save

  #3306

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2774 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-30 22:43:01 +00:00
Eric Davis
ea7ff8dd76 Added hook, :model_changeset_scan_commit_for_issue_ids_pre_issue_update. #3279
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2773 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-30 22:34:27 +00:00
Eric Davis
3224773ada Added two plugin hooks to the account page:
* :view_account_left_bottom
* :view_account_right_bottom

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2772 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-30 22:09:49 +00:00
Eric Davis
964245e30a Added :view_versions_show_contextual hook. (#3036)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2771 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-30 21:57:47 +00:00
Eric Davis
68c7af6c91 Fixed failing test in #3391. Quotes (") are html escaped.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2770 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-30 21:47:10 +00:00
Jean-Philippe Lang
c082cfc90e FIxed: inline images not displayed in atom feeds (#3391).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2768 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-26 08:28:36 +00:00
Jean-Philippe Lang
5db407ca59 Render new message form if needed.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2767 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-25 20:38:59 +00:00
Jean-Philippe Lang
53b002b497 Add plugins information to script/about (#3387).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2766 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-25 19:10:26 +00:00
Jean-Philippe Lang
85ce903cfa Ability to watch a wiki or a single wiki page (#413).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2765 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-25 19:02:28 +00:00
Jean-Philippe Lang
9c630cc2b7 Fixed: Atom feeds leak email address (#3408).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2763 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-25 18:11:32 +00:00
Jean-Philippe Lang
bd42b7cd9e Validate project identifier only when changed (#3352).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2762 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-25 18:01:27 +00:00
Jean-Philippe Lang
70340910de Link to the appropriate repository (#3376).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2760 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-18 17:45:45 +00:00
Azamat Hackimov
2a5369e37d ru.yml translation, adding to zh.yml missed string
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2759 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-18 14:34:47 +00:00
Azamat Hackimov
d1ee28758f Translation updates (#3374, #3375, # 3378, #3380, #3381)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2758 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-18 14:11:02 +00:00
Eric Davis
6bdd07275b Fixed an interpolation argument error in nl.yml
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2757 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-17 18:10:28 +00:00
Jean-Philippe Lang
fe8f4e5b87 Fixes code comments.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2756 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-17 15:32:48 +00:00
Jean-Philippe Lang
1d5479c1af Translations updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2755 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-17 14:37:26 +00:00
Jean-Philippe Lang
52b5b29203 Adds a setting to choose which role is given to a non-admin user who creates a project (#1007).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2754 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-17 14:35:00 +00:00
Jean-Philippe Lang
ce8bd16020 Translations updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2752 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-17 14:04:08 +00:00
Jean-Philippe Lang
7f94e3446f Translations updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2751 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-17 13:56:53 +00:00
Jean-Philippe Lang
8141110eb2 Ability to allow non-admin users to create projects (#1007).
This can be enabled in permissions settings. A non-admin user who creates a project is automatically added as a project member (the first role is given, TODO: make this given role configurable).
Projects can be added from the public projects list.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2750 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-17 12:59:14 +00:00
Jean-Philippe Lang
9c9dc6e814 Adds email notification on wiki changes (#413). It's disabled by default and can be enabled in application settings.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2749 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-17 09:55:13 +00:00
Jean-Philippe Lang
6e0a818caf Makes issues/show accept rss key (#3362).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2745 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-17 08:35:57 +00:00
Jean-Philippe Lang
f7d7186c13 Fixed: issue status bulk edit broken by r2726 (#3347).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2744 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-14 18:18:29 +00:00
Jean-Philippe Lang
e9a6730f4a Ability to use any custom field as a cross-project custom query column (#3321).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2743 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-13 17:32:15 +00:00
Jean-Philippe Lang
b87753c90d Do not autologin if more that one token is found (#3351).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2742 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-13 16:56:31 +00:00
Jean-Philippe Lang
f5eb1be268 Add token value uniqueness validation (#3351).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2741 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-13 16:55:15 +00:00
Jean-Philippe Lang
3e52383988 Use ActiveSupport::SecureRandom to generate tokens (#3351).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2740 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-13 16:54:32 +00:00
Jean-Philippe Lang
e5ed2b0f73 Fixed: issue bulk edit view broken by r2726 (#3347).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2739 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-13 16:06:14 +00:00
Jean-Philippe Lang
566d0a6ceb Fixed: issue move by non-admin broken by r2726 (#3354).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2738 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-13 15:58:54 +00:00
Jean-Philippe Lang
09a613b035 Escape custom field name.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2737 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-12 19:08:04 +00:00
Jean-Philippe Lang
5ab33bca8e Sort roles on account view.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2736 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-12 19:06:45 +00:00
Jean-Philippe Lang
a09f0e0e74 Escape role name.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2735 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-12 19:06:22 +00:00
Jean-Philippe Lang
da2854cf75 Display all users roles on project overview (#3339).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2734 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-12 19:04:52 +00:00
Jean-Philippe Lang
3df4df3438 script/about updated (#3333).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2733 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-12 17:22:06 +00:00
Jean-Philippe Lang
bae7579a72 Fixes Redmine.pm broken by r2726 (#3330).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2732 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-12 16:56:23 +00:00
Jean-Philippe Lang
c6efc90041 Cleaning projects administration list.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2731 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 11:36:12 +00:00
Jean-Philippe Lang
fba02769f3 Removes untranslated string.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2730 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 11:33:02 +00:00
Jean-Philippe Lang
bbb5a47b2a Clear member_roles table before populating it.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2729 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 11:18:09 +00:00
Jean-Philippe Lang
88532b6817 Fixes project members copy with multiple roles.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2728 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 11:17:23 +00:00
Jean-Philippe Lang
682c5d1113 Follows r2720 (#3308).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2727 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 11:06:29 +00:00
Jean-Philippe Lang
7dccf9fda6 Allows multiple roles on the same project (#706). Prerequisite for user groups feature.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2726 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 10:54:31 +00:00
Jean-Philippe Lang
814e138c2a Slight change to the permissions report view, with folding support.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2725 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 10:43:19 +00:00
Jean-Philippe Lang
9f59538241 Fixes consistency of custom fields display on the issue detail view (#3190).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2724 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 10:36:40 +00:00
Jean-Philippe Lang
752e263d3a Accept any svn tunnel scheme in repository URL (#3278).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2723 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 10:08:02 +00:00
Jean-Philippe Lang
a7ea14f5af Prevent nil error when retrieving svn version (#3268).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2722 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 09:49:39 +00:00
Jean-Philippe Lang
7319e8e235 Adds wiki comments length validation (#3308).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2720 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 08:49:45 +00:00
Jean-Philippe Lang
7e1ac0e602 Same as fix as r2705 from Trac wiki pages attachments (#3291).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2718 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-10 08:39:23 +00:00
Jean-Philippe Lang
3704653c7a Removes hardcoded table names in Repository#clear_changesets.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2715 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-08 08:05:45 +00:00
Eric Davis
0985d4219c Link projects and users in the issue list.
#3086

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2713 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-06 04:47:20 +00:00
Eric Davis
f90b85f8be Link the project name in simple issue lists.
#3085

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2712 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-06 04:47:13 +00:00
Jean-Philippe Lang
1930cf3d46 Adds group folding on issue list (#2679).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2711 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-04 18:04:09 +00:00
Jean-Philippe Lang
19f44cd1cb Disable email notifications before importing data.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2710 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-04 17:41:52 +00:00
Jean-Philippe Lang
0a0d14d5a7 Fixes r2707.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2708 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-04 16:55:47 +00:00
Jean-Philippe Lang
476ea76efb Fixes Mantis importer broken by 2670.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2707 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-04 16:53:05 +00:00
Jean-Philippe Lang
ad1ffa06a0 Fixed Trac importer broken by r2670 (#3254).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2705 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-04 16:48:27 +00:00
Eric Davis
fa7bd1c71d Added the ability to copy a project in the Project Administration panel.
* Added Copy project button.
* Added Project#copy_from to duplicate a project to be modified and saved by the user
* Added a ProjectsController#copy based off the add method
** Used Project#copy_from to create a duplicate project in memory
* Implemented Project#copy to copy data for a project from another and save it.
** Members
** Project level queries
** Project custom fields
* Added a plugin hook for Project#copy.

  #1125  #1556  #886  #309

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2704 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-03 21:25:37 +00:00
Jean-Philippe Lang
29c0dae151 Adds hostname to Redmine links in atom feeds (#3275).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2699 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-01 11:39:22 +00:00
Jean-Philippe Lang
9fd14713c5 Fixes user edit urls (#3281).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2698 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-05-01 11:25:41 +00:00
Jean-Philippe Lang
d984422a1f Fixes self and alternate atom links (#3161).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2697 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-26 20:12:58 +00:00
Jean-Philippe Lang
b557393252 Ticket grouping (#2679).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2696 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-26 13:09:14 +00:00
Jean-Philippe Lang
24875be705 Adds .erb mime type.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2695 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-25 11:40:50 +00:00
Jean-Philippe Lang
32c09fd5cf Adds more css classes to the roadmap issues (#3214).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2694 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-25 11:28:48 +00:00
Jean-Philippe Lang
8887b6f3d3 Adds the filename to the html title of attachment view.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2693 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-25 09:35:14 +00:00
Jean-Philippe Lang
15a14e55cd Returns a 404 error when trying to view/download an attachment that can't be read from disk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2692 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-25 09:31:36 +00:00
Jean-Philippe Lang
914ef1cb25 Slight gantt export refactoring (#3229).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2690 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-25 09:05:31 +00:00
Jean-Philippe Lang
73ca9d9161 Fixes a javascript comment (#3238).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2689 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-25 08:53:14 +00:00
Eric Davis
5ab582387e Added some new view hooks to the welcome page.
* :view_welcome_index_left
* :view_welcome_index_right

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2687 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-24 21:25:10 +00:00
Eric Davis
1be0b8f0cb Added two new plugin hooks to the Project Overview page
* :view_projects_show_left
* :view_projects_show_right

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2686 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-24 21:25:03 +00:00
Jean-Philippe Lang
070c18746e Fixed: %0.2f is rendered as plain text in issues/destroy view (#3167).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2685 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-24 17:18:36 +00:00
Jean-Philippe Lang
5d77f92ae6 Fixes a translation key (#3167).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2684 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-24 17:03:22 +00:00
Jean-Philippe Lang
7a5fe1c875 Fixed: Calendar popup broken by i18n refactoring (#3168).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2683 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-24 17:00:25 +00:00
Jean-Philippe Lang
a7e32302a6 Adds single forum atom feed (#3181).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2682 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-24 16:51:07 +00:00
Jean-Philippe Lang
6385217be0 Prevent recursive calls to RepositoriesHelper#repository_field_tags (#3226).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2680 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-24 15:54:48 +00:00
Jean-Philippe Lang
ac6ecdb360 Fixed: Mercurial integration doesn't work if redmine is installed in folder path containing space (#3159).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2679 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-21 16:05:04 +00:00
Jean-Philippe Lang
31cf9be7ab Makes minimum password length configurable.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2678 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-21 13:43:57 +00:00
Jean-Philippe Lang
6bb5508387 Less aggressive textile image tag parsing (#3209).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2677 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-21 12:49:16 +00:00
Jean-Philippe Lang
bb44430b63 Ask user what to do with child pages when deleting a parent wiki page (#3202).
3 options are available:
* move child pages as root pages
* move child pages to another parent page
* delete all descendants

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2676 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-21 12:19:56 +00:00
Azamat Hackimov
65cbd94e42 fix for he.yml (#3204), thank Orgad Shaneh for patch
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2675 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-19 21:43:46 +00:00
Jean-Philippe Lang
32ed656789 Fixed: an error is raised when no tab is available on project settings (#3179).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2674 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-19 08:45:00 +00:00
Jean-Philippe Lang
2a3fe1604a Fixed: Issue status in the notify email's subject is the issue's old status, should be its new status (#3194).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2673 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-19 08:33:56 +00:00
Jean-Philippe Lang
43200e2122 Adds mime type specific css classes to the SCM browser.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2672 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-19 08:25:25 +00:00
Azamat Hackimov
df2e0dbcd7 Translation updates: ru #3067, pl #3076, sv #3124, sk #3164, cs #3166, nl #3198
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2671 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-17 18:00:27 +00:00
Jean-Philippe Lang
a59854ef9d Fixes memory consumption on file upload (#3116).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2670 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-10 16:37:42 +00:00
Jean-Philippe Lang
10cbdf5d96 Create the journal after issue save (#3140).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2669 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-08 19:11:30 +00:00
Jean-Philippe Lang
c90878c817 Slight change to french locale.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2668 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-08 18:53:58 +00:00
Jean-Philippe Lang
1c03b98e5d Fixes that custom values length and attachment size validation error raises an error.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2667 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-08 16:56:01 +00:00
Jean-Philippe Lang
a4a41e05a8 Inform about minimum/maximum field lengths in error messages (#3128).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2666 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-07 17:50:17 +00:00
Jean-Philippe Lang
a6acc77904 Add test for r2664 (#3127).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2665 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-07 17:38:52 +00:00
Jean-Philippe Lang
bab9b0d6ff Allow textile mailto links (#3127).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2664 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-07 17:35:52 +00:00
Jean-Philippe Lang
81b84f641d Fixed: links to changesets in activity and atom feeds uses project id instead project identifier (#3137).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2663 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-07 17:30:56 +00:00
Jean-Philippe Lang
dea072f506 Set a default scope for 'My page' block names translations (#3057).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2659 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-05 13:33:54 +00:00
Jean-Philippe Lang
66839c12dd Replaces List-Id header with Precedence and Auto-Submitted headers (#2984, #2879).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2655 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-05 12:33:45 +00:00
Jean-Philippe Lang
ca166b30e1 Adds the ability to move threads between project forums (#2452). 'Edit message' permission is required.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2649 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-05 10:08:11 +00:00
Jean-Philippe Lang
3d65ed7aa0 Follows r2647.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2648 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-04 10:01:31 +00:00
Jean-Philippe Lang
b7127e3c14 Fixed: Sorting a query on a value that isn't in the displayed columns fails (#3078).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2647 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-04 10:00:22 +00:00
Jean-Philippe Lang
e40241761a Set minimum identifier lenght to 1 (#3073).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2646 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-04 09:55:17 +00:00
Jean-Philippe Lang
8c9fd662f0 Email footer signature convention (#3084).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2645 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-02 17:37:43 +00:00
Jean-Philippe Lang
5fbbdf7cb6 Make sure that lock_version changes are not stored in journals table (#3004).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2644 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-04-02 17:28:48 +00:00
Jean-Philippe Lang
801ad70cb7 Show timelog reports for non-versioned issues (#3051).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2643 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-30 20:09:57 +00:00
Jean-Philippe Lang
c9c269abf7 Update SVG library to latest stable (0.6.1) (#3056).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2642 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-30 19:58:17 +00:00
Jean-Philippe Lang
8d6d9a80d2 Fixed: News summary field is not searchable (#2998).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2641 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-28 12:43:21 +00:00
Jean-Philippe Lang
3d78a1b3f3 Test failure (#3041).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2640 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-28 12:30:48 +00:00
Jean-Philippe Lang
f1b5127cbb Fixes content vertical overflow (#3053).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2639 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-28 12:13:58 +00:00
Jean-Philippe Lang
04e181b8b0 Adds a user search field with autocompleter on project members screen.
User selection with checkboxes is disabled if there are more than 300 users available (#2993).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2638 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-28 12:07:05 +00:00
Eric Davis
b4be8849c0 Added observers to watch model objects for mail delivery instead of calling Mailer.
* Added an IssueObserver to watch when Issues are created
* Added a JournalObserver to watch when Journals are created (Issue updates)
* Added a NewsObserver for News items.
* Added a DocumentObserver for Document notifications.
* Setup IssuesController#new to use the IssueObserver.
* Setup IssuesController#edit to use the IssueObserver.
* Setup IssuesController#bulk_edit to use the JournalObserver.
* Removed the Mailer call in Changeset#scan_commit_for_issue_ids, the
  JournalObserver will handle it.
* Removed Mailer calls in MailHandler in favor of the Observers.

  #2659

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2637 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-28 00:38:57 +00:00
Jean-Philippe Lang
3557e767e0 Escape member name.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2636 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-27 18:27:06 +00:00
Jean-Philippe Lang
90810c0741 Ability to add multiple project members in a single action (#1556).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2635 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-27 18:10:36 +00:00
Jean-Philippe Lang
c77806738a Fixes that "My Page" personalization was not storing reordered blocks (#2971).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2634 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-26 18:11:56 +00:00
Jean-Philippe Lang
b2a6176828 Slight change to fr locale.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2633 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-25 17:44:59 +00:00
Azamat Hackimov
064ba5d8cd Romanian update (#2900)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2630 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-24 19:20:53 +00:00
Azamat Hackimov
17cc0ba44a Bosnian (Bosanski), initial commit from Ernad Husremovic (#2976)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2629 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-24 19:16:02 +00:00
Azamat Hackimov
899aee4011 Translation updates: sv, zh, zh-TW (#3022, #3025, #3028)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2627 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-24 18:58:03 +00:00
Azamat Hackimov
53a8264436 fix #3038, thanks to Konstantin Kuznetsov for reporting
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2626 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-24 18:45:24 +00:00
Jean-Philippe Lang
72f3c7f921 Fixes Journal#save signature (#3014).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2615 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-23 17:18:04 +00:00
Azamat Hackimov
80acb00454 Fixing bug #3009, trivial updates of locales
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2614 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-22 20:33:21 +00:00
Azamat Hackimov
ad34778cb1 fast translation update (#2969, #2978, #2989, #2994, #2999, #3006)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2613 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-22 20:15:10 +00:00
Eric Davis
c2dfffd7f2 Added some RDoc documentation for some models.
Submitted by austenito on Github.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2612 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-21 00:39:53 +00:00
Eric Davis
451ef7f21f Added several more plugin hooks:
* :controller_custom_fields_new_after_save
* :controller_custom_fields_edit_after_save
* :view_custom_fields_form_upper_box
* :view_custom_fields_form_* (type of custom field)
* :view_issue_statuses_form
* :view_issues_show_description_bottom
* :view_my_account
* :view_users_form

  #2599

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2611 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-21 00:22:59 +00:00
Eric Davis
b67d624754 Added plugin hooks to the context menu
* :view_issues_context_menu_start
* :view_issues_context_menu_end

  #3013

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2610 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-21 00:01:48 +00:00
Eric Davis
e48cc150ec Added a plugin hook for :controller_account_success_authentication_after
#3019

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2609 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-20 23:52:42 +00:00
Eric Davis
40a039ce1c Updated INSTALL doc for Rails 2.2.2
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2600 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-19 03:34:45 +00:00
Eric Davis
7774642b03 Extend the settings.name column so it can fit longer plugin names.
#3010

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2599 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-19 00:01:24 +00:00
Jean-Philippe Lang
d516d9d9e5 Set sort orders on 'View all issues' links on my page.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2598 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-17 17:34:49 +00:00
Jean-Philippe Lang
ec378bb446 Makes the 'View all' link on 'Reported issues' list open and closed issues.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2597 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-17 17:31:02 +00:00
Jean-Philippe Lang
f0c676d3df Adds missing scope for r2595.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2596 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-17 17:30:14 +00:00
Jean-Philippe Lang
b44317b762 Adds issue count on assigned and reported 'My page' modules (#2986).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2595 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-17 17:27:30 +00:00
Jean-Philippe Lang
4181f85962 Fixes that user's last_login_on was not set when using registration with automatic activation.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2594 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-15 10:49:18 +00:00
Jean-Philippe Lang
175ac71b2c Rescue Redmine::DefaultData::DataAlreadyLoaded in redmine:load_default_data task.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2592 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-15 10:03:56 +00:00
Jean-Philippe Lang
47f264b15c Ability to set language for redmine:load_default_data task using REDMINE_LANG environment variable (#2847).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2591 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-15 10:01:04 +00:00
Jean-Philippe Lang
5fc7d097fe Fixing routes broken by r2581 (#2967, #2970).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2590 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-15 09:35:02 +00:00
Jean-Philippe Lang
9f45499936 Adds controller to urls in case the side bar is called from another controller (#2960).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2589 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-13 19:25:11 +00:00
Jean-Philippe Lang
7a3822448b pl update.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2588 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-13 19:08:44 +00:00
Jean-Philippe Lang
6a9fcb23d0 Adds a lang string with arguments for date ranges (#2305).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2587 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-13 19:03:34 +00:00
Jean-Philippe Lang
3f52a0d3e8 sv update.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2586 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-13 18:36:55 +00:00
Jean-Philippe Lang
57eadea091 Translations updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2585 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-13 18:35:19 +00:00
Jean-Philippe Lang
5eae20f3d4 zh-TW update.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2584 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-13 18:32:36 +00:00
Jean-Philippe Lang
48e7b11065 pt-BR update.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2583 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-13 18:27:51 +00:00
Jean-Philippe Lang
1e7962bfe9 Fixes broken CSV link on cross-project timelog report (#2941).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2582 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-13 18:06:47 +00:00
Jean-Philippe Lang
0100011e5c Fixing repository routes (#2967).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2581 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-13 17:45:47 +00:00
Jean-Philippe Lang
74d8739936 Adds a unique index on projects_trackers table (#2882).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2580 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-12 19:49:39 +00:00
Jean-Philippe Lang
eb7903c0ec Only retrieve query name for display.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2579 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-12 19:35:36 +00:00
Jean-Philippe Lang
a42e56f65d Fixes commit logs alignment in the repo browser (#2961).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2578 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-12 18:50:52 +00:00
Jean-Philippe Lang
adbe164246 Fixing tests (sort refactoring).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2577 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-12 18:43:19 +00:00
Jean-Philippe Lang
1852d907ba Fixes SortHelper examples (#2950).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2576 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-12 18:24:32 +00:00
Jean-Philippe Lang
73eb1580ae Fixed: zh-TW PDF export broken by new locales (#2940).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2575 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-12 18:22:54 +00:00
Jean-Philippe Lang
eb224378db Hide 'New file' link and form for printing (#2949).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2574 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-12 18:17:35 +00:00
Jean-Philippe Lang
b622e0f8ce Flush buffer when asking for language (#2948).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2573 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-12 18:11:44 +00:00
Jean-Philippe Lang
c7c8dc71f2 Ability to save "sort order" in custom queries (#2899).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2572 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-12 18:06:54 +00:00
Jean-Philippe Lang
2b585407cb SortHelper refactoring:
* multiple columns sort feature (#2871)
* CSS classes instead of an image tag to reflect the state of the column
* examples fixed (#2945)

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2571 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-10 21:11:36 +00:00
Jean-Philippe Lang
4f4d447224 Fixed: Files without Version aren't visible in the Activity page (#2930).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2569 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-08 14:31:15 +00:00
Jean-Philippe Lang
9701c33562 Adds css classes to the issues in the calendar (#2651).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2568 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-07 16:32:30 +00:00
Jean-Philippe Lang
89719e205e Adds 2 css classes (created-by-me and assigned-to-me) to issues created by or assigned to the current user (#2651).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2567 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-07 16:30:55 +00:00
Jean-Philippe Lang
03572ec569 Adds a sortable "Project" column to the issue list.
It's displayed by default on the cross-project issue list (#2889). It's also available for saved queries.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2566 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-07 15:58:39 +00:00
Jean-Philippe Lang
554e569de1 Adds a List-Id header to all emails (#2879).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2565 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-07 15:14:10 +00:00
Jean-Philippe Lang
04abeda1d7 Add doube quotes to user_name and password in email.yml.example (#2927).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2564 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-07 13:41:24 +00:00
Jean-Philippe Lang
7bd4590cd6 Transaction for each imported subversion changeset.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2563 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-07 13:32:33 +00:00
Jean-Philippe Lang
8375e98ade Fixes that Redmine::WikiFormatting.register did not raise ArgumentError when giving name as a string (#2856).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2562 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-07 12:57:16 +00:00
Jean-Philippe Lang
e67fbdc315 Test that account activation email contains the appropriate link (#2825).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2561 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-07 12:45:46 +00:00
Jean-Philippe Lang
36a55a0925 Change to line #'s links in file/repo viewer (#2835).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2555 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-07 11:02:40 +00:00
Jean-Philippe Lang
dc8b804eba Fixed: export links on the issue list lose project param after applying a filter (#2908).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2554 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-06 22:46:49 +00:00
Jean-Philippe Lang
009b685b1d Fixed: MailHandler raises an error when processing an email without From header (#2916).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2553 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-06 18:25:19 +00:00
Jean-Philippe Lang
02ecc8aa15 Fixes typo in locales (#2805).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2552 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-04 18:09:15 +00:00
Jean-Philippe Lang
608d6683da Fixed broken OpenID authentication after migration to Rails 2.2 (#2894).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2551 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-04 17:37:28 +00:00
Jean-Philippe Lang
0d86bbf3ad Added missing field_content translation (#2896).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2550 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-04 17:29:02 +00:00
Jean-Philippe Lang
bd8ae3547e Removes double % in locales (#2891).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2549 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-04 17:13:05 +00:00
Jean-Philippe Lang
3227b32763 Adds some functional tests.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2547 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-01 12:58:30 +00:00
Jean-Philippe Lang
47f5713b1e Reorder links refactoring (follows r2526).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2546 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-01 12:03:48 +00:00
Jean-Philippe Lang
56bdcf407f Adds some functional tests.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2545 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-01 11:39:01 +00:00
Jean-Philippe Lang
df6e29f766 Removes unused code.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2544 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-01 10:56:20 +00:00
Jean-Philippe Lang
59dec17ddf Fixes a failure when running rake test:coverage.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2543 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-01 10:55:01 +00:00
Jean-Philippe Lang
38db62f1e7 Fixed that some error messages were not displayed (#2866).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2542 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-01 10:15:38 +00:00
Jean-Philippe Lang
b78349d2ca Fixed: Undefined Method (l_YesNo) Being Called (#2867).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2541 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-01 10:00:52 +00:00
Azamat Hackimov
700df9da8e small update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2540 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-01 07:04:26 +00:00
Azamat Hackimov
71b55e54bf localization updates
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2539 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-03-01 07:03:07 +00:00
Jean-Philippe Lang
a95a8aa40b Fixes tests broken by r2532 (#2853).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2535 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-28 09:21:49 +00:00
Jean-Philippe Lang
e63ccffd59 Add links for one-click activity filtering (#2857).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2534 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-27 18:34:08 +00:00
Jean-Philippe Lang
9fe6bcac74 Fixes user's activity link (#2853).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2532 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-27 12:08:52 +00:00
Jean-Philippe Lang
4d892fd6d0 Fixes #2851.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2531 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-27 11:32:43 +00:00
Jean-Philippe Lang
0aebb69c45 Updates sample plugin README.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2530 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-26 16:37:48 +00:00
Jean-Philippe Lang
d40bf20131 Allow My Page blocks to be added to from a plugin (#2840).
Partials must be placed under the app/views/my/blocks directory of the plugin.
An example can be found in the sample plugin.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2529 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-26 16:36:56 +00:00
Jean-Philippe Lang
5b96d1b083 Allow underscore in block partial name (#2840).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2528 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-26 16:15:07 +00:00
Jean-Philippe Lang
4c28291a19 Clickable/linkable line #'s while browsing the repo or viewing a text file (#2835).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2527 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-26 09:28:33 +00:00
Jean-Philippe Lang
589320337d Trackers controller refactoring.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2526 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-26 09:21:41 +00:00
Jean-Philippe Lang
46f52d306d Applies r1723 on Engines update to allow view override (#2841).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2525 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-26 08:59:30 +00:00
Jean-Philippe Lang
21eb3c089d Fixed: When logging in via an autologin cookie the user's last_login_on should be updated (#2820).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2524 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-25 14:59:33 +00:00
Jean-Philippe Lang
94cc23f103 Adds missing time format in locales (#2831).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2523 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-25 09:41:58 +00:00
Eric Davis
4baf32b166 Fixing Plugin and Mailer default_url_options.
Both the plugin hooks and Mailer were setting default_url_options incorrectly
and causing ActionContoller::UrlWritter to cache the settings on the module
(mattr_accessor) causing several url generators to fail in either the plugin
hooks or the Mailer.

* Replaced Mailer's use of the default_url_options accessor with the proper class method
* Replaced Hook's use of the default_url_options accessor with the proper class method on the ViewListener class
* Added a test to reproduce the bugs in the Mailer when a hook is registered (thanks Chaoqun Zou)

  #2542

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2522 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-25 07:25:01 +00:00
Jean-Philippe Lang
0f68334f0b Fixed that MailHandler#plain_text_body was returning nil if there was nothing to strip (#2814).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2520 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-23 17:35:16 +00:00
Jean-Philippe Lang
0101e609f7 Sort changesets in the same order as comments on the issue details view (#1546).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2519 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-22 15:16:18 +00:00
Jean-Philippe Lang
a64b8695c8 Patch ActiveRecord::Errors#full_messages so that it contains custom values error messages.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2518 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-22 14:46:32 +00:00
Jean-Philippe Lang
67fd5f3dc9 Fixed: visited links to closed tickets are not striked through with IE6 (#2615).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2516 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-22 11:22:23 +00:00
Jean-Philippe Lang
7342a00e0a Slight changes to the files list (#2658, #2806).
* Adds a link to the version
* Moves styles to the stylesheet
* Indent the files

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2515 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-22 11:04:17 +00:00
Azamat Hackimov
0123f7cde0 New shiny locale config update - pt-BR (thank to Alexandre da Silva, #2802)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2511 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 19:20:49 +00:00
Jean-Philippe Lang
4596a35c0e Changes default english date/time formats.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2510 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 16:44:56 +00:00
Jean-Philippe Lang
82ea6c001d Translation update (#2798).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2509 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 16:41:31 +00:00
Jean-Philippe Lang
8cc8f5164d Adding more missing strings.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2508 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 16:25:38 +00:00
Jean-Philippe Lang
84de5fa3b0 Makes the default issue list columns selection look like other multi-checkbox fieldsets.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2507 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 16:15:44 +00:00
Jean-Philippe Lang
0624bdb6c9 Adds missing strings.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2506 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 16:10:51 +00:00
Jean-Philippe Lang
1c5a2ddfb0 Limit the size of repository files displayed inline too.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2505 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 16:04:51 +00:00
Jean-Philippe Lang
79c074dbe5 Fixes new setting name.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2504 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 15:44:00 +00:00
Jean-Philippe Lang
66afc8c054 Adds a setting to limit the size of text attachments that can be displayed by the file viewer (default set to 512KB). Larger files are sent for download.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2503 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 15:34:11 +00:00
Jean-Philippe Lang
2bd419f23b Process all translations files.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2502 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 15:21:31 +00:00
Jean-Philippe Lang
1770fe54be Adds locales:update task as a replacement for gloc:update.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2501 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 15:11:09 +00:00
Jean-Philippe Lang
1511b31377 Add missing 'support' section to he and it locales.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2500 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 14:10:46 +00:00
Jean-Philippe Lang
79fd564b61 Do not show the changed files list on the revision page if the user is not allowed to browse the repository (#2762).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2499 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 12:58:18 +00:00
Jean-Philippe Lang
9c4a86d96c Moves project menu tests to a dedicated integration test.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2498 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 12:28:19 +00:00
Jean-Philippe Lang
9819a6bfd1 Fixes menu translation.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2497 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 11:31:22 +00:00
Jean-Philippe Lang
95ba220b79 Adds rake config/initializers/session_store.rb to the install doc.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2496 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 11:13:37 +00:00
Jean-Philippe Lang
6b770fa70c Deprecated truncate call.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2495 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 11:10:34 +00:00
Jean-Philippe Lang
cf70f187dd Fixes UsersControllerTest.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2494 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 11:07:22 +00:00
Jean-Philippe Lang
fe28193e4e Merged Rails 2.2 branch. Redmine now requires Rails 2.2.2.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2493 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 11:04:50 +00:00
Eric Davis
9a986ac0a5 Refactored the mess known as Hook default_url_options in favor of the simpler
:only_path as suggested by splatteal on GitHub.

  #2542

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2491 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 00:23:28 +00:00
Eric Davis
00b568c194 Implementing the missing tests now that HookTest has a cleaner setup.
Controller and Views are now tested through the Redmine::Hook::Helper module.

  #2542

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2490 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 00:23:22 +00:00
Eric Davis
b6c4b21b47 Refactored the HookTest methods to use Redmine::Hook::Helper instead
of the Redmine::Hook class directly.  Redmine::Hook::Helper is the
public API so it should be one that is tested.

  #2542

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2489 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-21 00:23:16 +00:00
Eric Davis
3fa9535670 List the Default Columns displayed on the issue list vertically
instead of horizontally so it's easier to read.

  #2745

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2488 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-20 22:59:59 +00:00
Eric Davis
befe278fa9 Allow the other format links to appear on the issue list (ATOM, PDF, CSV).
#2779

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2487 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-20 22:45:16 +00:00
Jean-Philippe Lang
b9e95e7a70 Fixes "too few arguments" error on activerecord error translation (#2626).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2486 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-20 18:55:56 +00:00
Jean-Philippe Lang
33e7ae96ad Adds (a maximum of 3) links to project ancestors in the page title (#2788).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2485 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-20 18:34:57 +00:00
Jean-Philippe Lang
04c428e059 Send an email to the user when an administrator activates a registered user (#2656).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2484 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-20 17:04:47 +00:00
Eric Davis
24ee6b9a1b Fixed the bug in the OpenID registration where the form wouldn't take a login
AccountController#open_id_authenticate was adding an auth_source_registration
to the session which caused AccountController#register to use the wrong codepath.

  #2757

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2483 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-20 00:31:50 +00:00
Eric Davis
aed1787d51 Fixed a bug in the OpenID login when a user signed up with OpenID but hasn't
been activated yet.

When logging in the user would come back to the login page with the back_url
of My Page.  This was caused by open_id_authenticate sending the user to My Page
and My Page redirecting the user back to the login page because they haven't
been activated.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2482 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-20 00:16:45 +00:00
Jean-Philippe Lang
9525e5f147 Fixes Setting.openid? (#2764).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2481 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-19 21:03:08 +00:00
Azamat Hackimov
5966f71c73 translation updates: hu (#2737), sv (#2740), pl (#2741), pt-br (#2781)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2480 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-18 19:20:56 +00:00
Azamat Hackimov
ee4a754475 translation updates: zh, zh-tw (#2743, #2758), ru
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2479 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-18 19:15:10 +00:00
Nicolas Chuche
6359e51477 Fixed: add group option to set the repository gid. Default is root (#2747)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2478 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-16 19:33:43 +00:00
Jean-Philippe Lang
d643d9a94c Fixed: User#identity_url raises an error when invalid url is supplied (#2742).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2476 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-15 18:46:50 +00:00
Jean-Philippe Lang
3183445aea Makes the "type" field disabled when updating a custom field (#2744).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2475 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-15 16:55:48 +00:00
Jean-Philippe Lang
9e4a118528 Create a wiki with a default start page named 'Wiki' when enabling wiki module (#2657).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2474 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-15 16:43:06 +00:00
Jean-Philippe Lang
b05ed594a0 Do not DELETE/INSERT enabled_modules when updating project modules.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2473 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-15 16:26:48 +00:00
Jean-Philippe Lang
4601ed2f3a Replaces Enumeration.get_values and Enumeration.default with named scopes.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2472 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-15 13:33:09 +00:00
Jean-Philippe Lang
13e2c727cf Updates 0.8.1 CHANGELOG.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2468 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-15 09:02:41 +00:00
Jean-Philippe Lang
d77be4e908 Removes invalid css class on issue details (#2753).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2467 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-15 08:33:48 +00:00
Jean-Philippe Lang
d3038eee92 Adds an index on watchers table to speed up watched issue filtering.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2466 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-14 19:06:44 +00:00
Jean-Philippe Lang
1c2cdedf19 Fixes headings style in html emails (#2739).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2465 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-14 18:32:11 +00:00
Jean-Philippe Lang
f9c9b054ba Timelog is ignored when updating an issue if user is admin but not a project member (#2717).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2463 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-13 17:59:45 +00:00
Jean-Philippe Lang
bbc8d3d768 Removes invalid css class on issue details (#2733).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2461 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-13 17:29:49 +00:00
Jean-Philippe Lang
b8eddf2014 Adds watch/unwatch link on the issue context menu (#2730).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2460 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-12 22:14:22 +00:00
Jean-Philippe Lang
9586269a06 Issues pagination loses project param after applying or clearing filter (#2726).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2459 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-12 22:01:20 +00:00
Jean-Philippe Lang
ff9da0bab0 Removes the fat ruby-openid gem. Simply use 'gem install ruby-openid' to enable openid support.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2458 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-12 21:25:50 +00:00
Jean-Philippe Lang
5bdd429162 Link to watched issues list on my page.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2457 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-12 17:38:36 +00:00
Jean-Philippe Lang
e1b828de95 Adds ability to filter watched issues (#846).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2456 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-12 17:35:57 +00:00
Jean-Philippe Lang
571494a028 Adds missing strings (#699).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2455 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-12 17:30:56 +00:00
Jean-Philippe Lang
2807b9c927 Hide openid stuff on my account if disabled (#699).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2454 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-12 17:19:32 +00:00
Eric Davis
60dc357271 Normalize the identity_url when it's set.
OpenId uses a specific format for the url it uses which requires the protocol
and trailing slash.  This change will normalize the value to when a user sets it.

  #699

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2453 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-12 04:31:28 +00:00
Eric Davis
14e445c600 Fixed the bundled ruby-openid gem
* The open_id_authentication plugin will require the gem automatically so
  it doesn't need to be added to environment.rb
* Changed the version requirement on the open_id_authentication to match
  the latest stable version.  Rails config.gem looks for a directory named
  after that specific version and will not load newer versions.

  #699

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2452 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-12 01:32:50 +00:00
Jean-Philippe Lang
4500b606ce Slight changes to the issue lists displayed on My page.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2451 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 20:25:05 +00:00
Eric Davis
f1b8bf22a2 Added a space so words don't runtogeatherlikethis. #699
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2450 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:45:53 +00:00
Eric Davis
8d53e433c5 Added a system setting for allowing OpenID logins and registrations
* Defaults to off
* Is set in the Administration panel under Authentication

  #699

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2449 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:24:28 +00:00
Eric Davis
85ad791d81 Prevent registration via OpenID if self registration is off. #699
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2448 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:07:41 +00:00
Eric Davis
720f928cd2 Refactored common methods out of register and open_id_authenticate
* Extracted register_by_email_activation
* Extracted register_automatically
* Extracted register_manually_by_administrator

  #699

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2447 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:07:34 +00:00
Eric Davis
8194cfaf86 Added user setup needed based on the system's registration settings
* Copied the register action's chunk of code used to setup the account
  based on Setting.self_registration
* Extracted method for when onthefly_creation_failed
* Added tests to confirm the behavior

  #699

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2446 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:07:28 +00:00
Eric Davis
876fb69271 Added tests for the other OpenID authentication cases. #699
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2445 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:07:23 +00:00
Eric Davis
48e26aa75b Adding OpenID mock and test. #699
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2444 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:07:18 +00:00
Eric Davis
0310f43126 Hooked up on the fly OpenID user creation.
* Use OpenID registration fields for the user.
* Generate a random password when a user is created.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2443 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:07:12 +00:00
Eric Davis
896e64b759 Added the ability to login via OpenID.
* Refactored AccountController#login to use either
  password or openid based authentication
* Extracted AccountController#successful_authentication
  to setup a user's session cookies and redirect
* Implemented the start of AccountController#open_id_authentication
  which will check with the OpenID server and perform authentication.
* Added text field for the OpenID url to /login
* Added identity_url for OpenID to the user forms.
* Added option to login with OpenID to the register form.
* Added a root url route, which is used by the OpenID plugin

  #699

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2442 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:07:07 +00:00
Eric Davis
a4e6e13b70 Fixed a bug in open_id_authentication, where relative_url_root is defined
on ActionController:AbstractRequest not Base

  #699

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2441 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:07:00 +00:00
Eric Davis
dbf02e2654 Added identity_url to User. #699
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2440 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:06:55 +00:00
Eric Davis
ca3960dee5 Added OpenID tables. #699
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2439 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:06:50 +00:00
Eric Davis
30171f3ab6 Added open_id_authentication plugin
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2438 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:06:45 +00:00
Eric Davis
f70be197e0 Unpacked OpenID gem. #699
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2437 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-11 19:06:37 +00:00
Jean-Philippe Lang
70efee1bc5 Leave wiki links untouched if target project doesn't exist or have no wiki.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2436 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-10 22:54:22 +00:00
Jean-Philippe Lang
8cf3d7a492 Replaces the repositories management SOAP API with a simple REST API.
reposman usage is unchanged but the script now requires activeresource.
actionwebservice is now longer used and thus removed from plugins.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2435 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-10 22:03:25 +00:00
Jean-Philippe Lang
cf5658d7fe Fixes broken action url on time edit form (#2707).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2434 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-10 17:18:19 +00:00
Azamat Hackimov
99fefbef52 New language - Macedonian (mk). Thank to Ilin Tatabitovski for work.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2433 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-10 16:05:31 +00:00
Azamat Hackimov
2ce37783ea Translation updates (#2643, #2645, #2668)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2432 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-10 15:57:52 +00:00
Azamat Hackimov
73f52af6e3 Updated translations (#2577, #2640, #2644, #2652)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2431 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-10 15:41:05 +00:00
Eric Davis
b75a30a21a Renamed variables to be more descriptive. #2542
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2430 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-10 03:12:45 +00:00
Eric Davis
5b7a5c39a7 Added request and controller objects to the hooks by default.
The request and controller objects are now added to all hook contexts by
default.  This will also make url_for work better in hooks by setting up
the default_url_options :host, :port, and :protocol.

Finally a new helper method @render_or@ has been added to ViewListener.  This
will let a hook easily render a partial without a full method definition.

Thanks to Thomas Löber for the original patch.  #2542

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2429 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-10 03:12:40 +00:00
Eric Davis
a3fa56d988 Added two new plugin hooks:
* :view_layouts_base_sidebar
* :view_layouts_base_content

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2428 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-10 01:24:32 +00:00
Eric Davis
0d01e07430 Added plugin hook :view_projects_roadmap_version_bottom. #2543
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2427 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-10 01:18:49 +00:00
Jean-Philippe Lang
2d3b3cee15 Strip keywords from received email body (#2436).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2426 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-09 20:17:58 +00:00
Jean-Philippe Lang
c687219113 Removes hardcoded table names (#2701).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2424 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-09 17:24:06 +00:00
Jean-Philippe Lang
27e3b31c1e Fixed: TypeError (can't modify frozen string) on settings view (#2700).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2423 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-09 17:18:41 +00:00
Jean-Philippe Lang
fc6d6b1e3b Fixed: migration 98 breaks when using table name prefix.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2415 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-08 17:24:39 +00:00
Jean-Philippe Lang
b998572def Fixed: path parameter is not an array when changing diff style (#2695), broken by r2317.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2399 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-07 20:11:03 +00:00
Jean-Philippe Lang
ff0c96011f Fixed: inline attached image should not match partial filename (#2683).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2363 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-05 20:25:01 +00:00
Jean-Philippe Lang
4aa90cc072 Typo in wiki link example (#2673).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2362 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-05 17:43:49 +00:00
Jean-Philippe Lang
b11a1d852c Closed issue are not overdue, fixes r2140 (#2337).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2361 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-03 17:32:07 +00:00
Jean-Philippe Lang
c9ca635fa7 Typos/fixes in views (#2654).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2360 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-03 17:15:59 +00:00
Jean-Philippe Lang
33bd7f45e1 Fixes message search eager loading (#2654).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2359 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-03 17:13:37 +00:00
Jean-Philippe Lang
bf107f000d Adds a 'box' div around news comment form (#2632).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2353 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-02 17:34:12 +00:00
Jean-Philippe Lang
299f1b87aa Include both version date and name when sorting issues by target version (#1502).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2352 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-01 20:57:44 +00:00
Jean-Philippe Lang
2a220a9e42 Include both last and first name when sorting issues by assignee (#1841).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2351 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-01 20:56:10 +00:00
Jean-Philippe Lang
ab5e07e83e Adds a setting to limit the number of revisions displayed on a repository file log (default=100).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2350 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-01 19:54:50 +00:00
Jean-Philippe Lang
d3b2049851 Use estimated hours to weight issues in version completion calculation (#2182).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2349 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-01 18:54:05 +00:00
Jean-Philippe Lang
d08ac5628a Changes color of activity events/search results summary.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2348 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-01 16:00:20 +00:00
Jean-Philippe Lang
4c312f3d6b Show line breaks in activity events summary.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2347 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-01 15:57:01 +00:00
Jean-Philippe Lang
cbdf900629 Do not repeat one-line commit logs on the activity view.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2346 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-01 15:48:56 +00:00
Jean-Philippe Lang
c83b41611a Fixed: Contextual divs after attachments are placed incorrectly in FireFox (#2633).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2345 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-02-01 14:36:38 +00:00
Jean-Philippe Lang
2ca4eea244 Less strict textile links parsing (#2582).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2344 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-31 14:50:56 +00:00
Jean-Philippe Lang
f021c856c1 Fixed: issue details view discloses relations to issues that the user is not allowed to view (#2589).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2343 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-31 13:22:29 +00:00
Jean-Philippe Lang
2679150ed4 Removes Issue.visible_by
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2342 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-31 12:48:09 +00:00
Jean-Philippe Lang
bbe8326477 Updates footer year.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2341 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-31 12:42:02 +00:00
winterheart
f794ae582c New translation - Slovenian, thank to Nejc Vidmar for work (#2577), translation updates (#2129, #2586)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2340 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-31 12:02:37 +00:00
Jean-Philippe Lang
ef903ba70e Adds :async_smtp and :async_sendmail delivery methods to perform email deliveries asynchronously.
Code from http://www.datanoise.com/articles/2006/7/14/asynchronous-email-delivery.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2339 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-31 11:43:54 +00:00
Jean-Philippe Lang
da941734d7 Changes time related icons.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2338 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-30 17:50:28 +00:00
Jean-Philippe Lang
0f494a53c9 Cleaning test.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2337 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-29 16:34:00 +00:00
Jean-Philippe Lang
cf566a0c72 Fixed: TOC does not parse wiki page reference links with description (#2601).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2336 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-29 16:33:45 +00:00
Jean-Philippe Lang
945ec8942a Adds projects association on tracker form (#2578).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2335 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-29 14:22:56 +00:00
Jean-Philippe Lang
32d4378198 Adds rel='nofollow' attribute to other formats download links (#2491).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2334 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-29 13:54:44 +00:00
Jean-Philippe Lang
f1aa0df326 Adds an helper to render other formats download links.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2333 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-29 13:53:17 +00:00
Jean-Philippe Lang
bf76988ebc Fixed an error when downloading gantt png at global level.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2332 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-29 12:26:32 +00:00
Jean-Philippe Lang
d590317856 Fixes other formats download links on the project issue list (project_id lost) broken r2317.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2331 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-29 11:09:46 +00:00
Jean-Philippe Lang
1e1b34b567 Sort target versions list on bulk edit form (#2616).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2330 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-29 09:05:36 +00:00
Jean-Philippe Lang
01d6ef2e57 Fixed project news atom link broken by r2317.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2329 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-28 21:25:35 +00:00
Jean-Philippe Lang
c4af6efd25 Fixing calendar and gantt links broken by r2317.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2328 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-28 21:20:39 +00:00
Jean-Philippe Lang
d1d1c9bfd0 Fixed calendar navigation links broken by r2317.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2327 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-28 21:11:13 +00:00
Jean-Philippe Lang
83fe973c75 Fixed user's activity atom feed broken by r2317.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2326 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-28 20:52:39 +00:00
Eric Davis
0bbebedada Fixed clearing the Issue filters in the issue list, broken by #2317
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2325 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-27 20:59:02 +00:00
Eric Davis
2fc7897044 Fixes Issue sorting in a project, broken by #2317
Issues were sorting but the project id wasn't being added so the
IssuesController would return all issues (cross-project).


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2324 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-27 20:42:19 +00:00
Jean-Philippe Lang
10994e9027 Fixed: users should not be able to add relations with issues they're not allowed to view (#2589).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2323 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-27 19:33:03 +00:00
Jean-Philippe Lang
cd55529eaa Fixed that 'My page' blocks may display issues that the user is no longer allowed to view (#2590).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2322 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-27 18:19:27 +00:00
Jean-Philippe Lang
837f074346 Explicitly require 'rfpdf/fpdf' (#2584).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2321 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-27 17:58:56 +00:00
Jean-Philippe Lang
a4544b0b26 Fixed actions on issues (gantt, calendar, move, bulk_edit...) at global level broken by r2317.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2320 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-27 17:40:55 +00:00
Jean-Philippe Lang
e1f96ca4db Replaces the obsolete robots.txt with a cached action (#2491).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2319 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-27 17:27:50 +00:00
Jean-Philippe Lang
6c93b8d599 Fixes activity pagination broken by r2317.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2318 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-26 17:43:58 +00:00
Eric Davis
765f7abc60 Converted routing and urls to follow the Rails REST convention.
Patch supplied by commits from Gerrit Kaiser on Github.  Existing routes will
still work (backwards compatible) but any new urls will be generated using the
new routing rules.

Changes listed below:

* made the URLs for some project tabs and project settings follow the new rails RESTful conventions of /collection/:id/subcollection/:sub_id
* prettier URL for project roadmap
* more nice project URLs
* use GET for filtering form
* prettified URLs used on issues tab
* custom route for activity atom feeds
* prettier repository urls
* fixed broken route definition
* fixed failing tests for issuecontroller that were hardcoding the url string
* more RESTful routes for boards and messages
* RESTful routes for wiki pages
* RESTful routes for documents
* moved old routes that are retained for compatibility to the bottom and grouped them together
* added RESTful URIs for issues
* RESTfulness for the news section
* fixed route order
* changed hardcoded URLs in tests
* fixed badly written tests
* fixed forgotten parameter in routes
* changed hardcoded URLS to new scheme
* changed project add url to the standard POST to collection
* create new issue by POSTing to collection
* changed hardcoded URLs in integrations tests
* made project add form work again
* restful routes for project deletion
* prettier routes for project (un)archival
* made routes table more readable
* fixed note quoting
* user routing
* fixed bug
* always sort by GET
* Fixed: cross-project issue list should not show issues of projects for which the issue tracking module was disabled.
* prettified URLs used on issues tab
* urls for time log
* fixed reply routing
* eliminate revision query paremeter for diff and entry actions
* fixed test failures with hard-coded urls
* ensure ajax links always use get
* refactored ajax link generation into separate method

  #1901


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2317 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-26 01:47:51 +00:00
Jean-Philippe Lang
bcc9007196 Ability to bulk edit custom fields of type 'list' (#461).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2316 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-25 16:04:28 +00:00
Jean-Philippe Lang
e944fc74df Render the project list as a tree on Move form.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2315 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-25 13:52:40 +00:00
Jean-Philippe Lang
ad059f9637 Removes spaces before colons.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2314 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-25 13:18:44 +00:00
Jean-Philippe Lang
1ad2551559 Adds ability to bulk copy issues (#1847).
This can be done by checking the 'Copy' checkbox on the 'Move' form.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2313 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-25 13:12:56 +00:00
Jean-Philippe Lang
41f3bae917 Fixed that the project jump box does not preserve current tab after r2304.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2312 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-25 12:13:27 +00:00
Jean-Philippe Lang
90c742e4f1 Ignore archived subprojects in Project#rolled_up_trackers (#2550).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2311 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-25 11:15:28 +00:00
Jean-Philippe Lang
97e31c4026 Removes unused projects_count column from projects table.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2305 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-24 11:48:38 +00:00
Jean-Philippe Lang
c9906480d3 Merged nested projects branch. Removes limit on subproject nesting (#594).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2304 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-24 11:31:15 +00:00
Jean-Philippe Lang
51b745470c Fixes a test that was broken by r2294.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2303 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-24 09:02:55 +00:00
Jean-Philippe Lang
11346455a1 Fixed: Details time log report CSV export doesn't honour date format from settings (patch #2466 by Russell Hind).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2302 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-24 08:58:03 +00:00
winterheart
86adcdf8bb ru.yml update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2300 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-23 16:30:04 +00:00
winterheart
80eb3b85e4 removing \r\n
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2299 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-23 15:58:58 +00:00
winterheart
a2cca9ee87 Translation updates (#2453, #2463, #2551)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2298 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-23 15:46:22 +00:00
winterheart
1176f892d7 #2562, update for zh.yml
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2297 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-23 15:40:38 +00:00
winterheart
805b0c2a4e New Galician Translation (#2547), thanks to Martín Vázquez for intial translation
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2296 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-23 15:37:59 +00:00
Jean-Philippe Lang
4972084345 Automatically focus several form fields.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2295 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-22 16:34:54 +00:00
Jean-Philippe Lang
dfab202cde Accept replies to forum messages by subject recognition (#1616).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2294 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-21 18:22:30 +00:00
winterheart
b80caf762a #2540, pt-br update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2293 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-20 16:13:14 +00:00
winterheart
8594206538 #2463, partially solved
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2292 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-20 16:09:07 +00:00
winterheart
c155b07ecb #2445, nl.yml update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2291 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-20 15:53:09 +00:00
winterheart
1d70916064 #2453, sv.yml patch, some errors still exist (see ticket)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2290 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-20 15:45:34 +00:00
Jean-Philippe Lang
b9e3fbcd83 Allow email to reply to a forum message (#1616).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2289 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-19 19:03:53 +00:00
Jean-Philippe Lang
0c4e40b89c Use In-Reply-To and References headers to handle replies by email.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2288 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-19 18:29:07 +00:00
winterheart
254e224bd7 translation updates (#2535, #2505, #2524, #2434)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2287 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-19 16:43:28 +00:00
winterheart
5014b63611 #2442, small fix
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2286 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-19 16:06:39 +00:00
winterheart
2cd718862b #2429, translation update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2285 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-19 16:02:57 +00:00
winterheart
b5915d3906 #2442, translation update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2284 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-19 15:57:19 +00:00
winterheart
38d1b6e1bb #2439, translation update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2283 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-19 15:55:54 +00:00
Jean-Philippe Lang
dacddd9897 Fix in AttachmentsController#show.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2282 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-18 20:00:03 +00:00
Jean-Philippe Lang
1d783106a3 Adds Message-Id and References headers to email notifications so that issues and messages threads can be displayed by email clients (#1401).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2281 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-18 15:16:31 +00:00
Jean-Philippe Lang
a4882467cb Fixed that Trac importer was creating duplicate custom values (#2506).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2280 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-18 11:54:56 +00:00
Jean-Philippe Lang
08304afe54 Fixes 103_set_custom_fields_editable migration from r2276 (#2526).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2279 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-18 10:54:08 +00:00
Jean-Philippe Lang
12792d8068 User custom fields can now be set as editable so that users can edit them on 'My account'.
For existing user custom fields, this new attribute is set to false by default to preserve the prior behaviour (it can turned on by editing the custom field in admin area).

Note: on the registration form, *required* custom fields will be displayed even if they are not defined as editable so that the account can be created.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2276 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-17 11:18:04 +00:00
Jean-Philippe Lang
99c2e98975 Moves a few settings to a "Display" panel.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2275 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-17 09:04:10 +00:00
Jean-Philippe Lang
ee1bb54ab6 CustomFieldsController#list moved to #index.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2274 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-17 08:46:23 +00:00
Jean-Philippe Lang
48295a6c4b CustomFieldsController refactoring.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2273 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-17 08:41:30 +00:00
Jean-Philippe Lang
5ed2e78ae2 Make use of tracker_ids association in issue custom field form.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2272 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-17 08:25:55 +00:00
Jean-Philippe Lang
d1a1e25bb8 Project jump box fix.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2271 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-17 08:08:33 +00:00
Jean-Philippe Lang
27fd8a2436 Do not show Category field when categories are not defined.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2270 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-17 08:03:53 +00:00
Jean-Philippe Lang
00ce28f8d7 Slight visual changes on the issue form.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2269 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-17 07:53:32 +00:00
Jean-Philippe Lang
b4640a0904 Adds custom fields functional tests.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2268 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-16 21:02:56 +00:00
Jean-Philippe Lang
a276926f42 Use a textarea for custom fields possible values (#2472).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2267 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-16 21:02:03 +00:00
Jean-Philippe Lang
f6282f9600 Makes subject field get focus on 'New issue' form (#2522).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2266 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-16 20:57:18 +00:00
Jean-Philippe Lang
ffa6c5fe3e Adds a 'Create and continue' button on the new issue form, that will create the issue and display the form again (#2523).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2265 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-16 17:20:41 +00:00
Jean-Philippe Lang
d28d2d5ab8 Typo (#2489).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2263 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-12 17:46:53 +00:00
Jean-Philippe Lang
31b3ebf071 Fixes r2226: exporting an issue with attachments to PDF raises an error (#2492).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2262 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-12 17:45:23 +00:00
Eric Davis
75c10a1cac Added two new plugin hooks to IssuesController:
* :controller_issues_new_after_save
* :controller_issues_edit_after_save

  #2475


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2261 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-12 04:52:56 +00:00
Eric Davis
17d455c72f Codified instructions from RUNNING_TESTS as rake tasks for convenience
Rake tasks are in testing.rake and can be run by `rake test:scm:setup:<scm>`
Updated RUNNING_TESTS

Contributed by Gerrit Kaiser


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2260 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-12 04:44:01 +00:00
Jean-Philippe Lang
38b2c5a317 Use margin-right instead of padding-right on top menu links.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2259 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-11 19:48:16 +00:00
Jean-Philippe Lang
da01ee7c37 Ability to sort the issue list by text, int and float custom fields (#1139).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2258 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-11 18:38:07 +00:00
Jean-Philippe Lang
3a28848ca0 Ability to sort the issue list by text, list, date and boolean custom fields (#1139).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2257 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-11 16:33:51 +00:00
Jean-Philippe Lang
1ca69f2af1 Different icon for closed issues in search result (#992).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2256 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-11 11:01:35 +00:00
Jean-Philippe Lang
212bf1e2bb Makes email adress uniqueness case-insensitive (#2473).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2253 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-10 11:29:35 +00:00
Jean-Philippe Lang
6768bec456 Fixed: no error is raised when entering invalid hours on the issue update form (#2465).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2251 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-09 17:32:46 +00:00
Jean-Philippe Lang
c0f44db4f7 Adds 'closed' css class to closed issues in the issue list (#2458).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2250 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-07 20:30:02 +00:00
Jean-Philippe Lang
15996c348d Fixes a test failure with svn < 1.5 (#2455).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2249 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-07 20:22:06 +00:00
Jean-Philippe Lang
a64928fa6b Fixes functional test broken by r2246.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2248 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-07 20:21:27 +00:00
Jean-Philippe Lang
09620eef76 Refactor TabularFormBuilder field helpers (#2461).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2247 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-07 20:03:33 +00:00
Jean-Philippe Lang
bc270b31c3 Makes issue description a non-required field (#2456).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2246 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-07 19:47:24 +00:00
Jean-Philippe Lang
cd83f72da4 Slight changes in functional tests.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2235 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-04 18:14:51 +00:00
Jean-Philippe Lang
260373aed7 Slight changes to ease Rails 2.2 support.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2234 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-04 17:09:25 +00:00
Jean-Philippe Lang
4a5d3e0353 Scramble PDF title (#1204).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2233 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-04 14:54:19 +00:00
Jean-Philippe Lang
8590165238 Merged r2231 from 0.8-stable (#2402).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2232 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-04 13:27:48 +00:00
Jean-Philippe Lang
748fb30f58 Do not use compute_public_path.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2228 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-04 12:50:45 +00:00
Jean-Philippe Lang
2644141a36 Makes the app boot with Rails 2.2.2
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2227 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-04 12:14:05 +00:00
Jean-Philippe Lang
ceb2320ef0 Move PDF stuff to a single helper.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2226 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-04 12:03:39 +00:00
Jean-Philippe Lang
dfc937340d Fixed: email notification for changes I make still occurs when running Repository.fetch_changesets (#1957).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2225 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-03 16:03:12 +00:00
Jean-Philippe Lang
e0bda97b6f Display a warning if some attachments were not saved (#2008).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2224 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-03 14:44:12 +00:00
Jean-Philippe Lang
d25b6d4686 Moves flash messages rendering to a helper method.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2223 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-03 14:11:44 +00:00
Jean-Philippe Lang
a66b1e77da Fixed: syntax highlight doesn't appear in new ticket preview (#1976).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2222 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-03 13:14:28 +00:00
Jean-Philippe Lang
7a5ce28921 Lower the project identifier limit to a minimum of two characters (#2003).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2221 e93f8b46-1217-0410-a6f0-8f06a7374b81
2009-01-03 13:09:36 +00:00
Jean-Philippe Lang
16eb0421e5 IMAP: add options to move received emails.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2220 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-31 14:56:30 +00:00
Jean-Philippe Lang
538bd7cd7f Fix sv lang file
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2219 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-31 13:59:30 +00:00
Jean-Philippe Lang
a22f3d6aa7 Admin Info Screen: Display if plugin assets directory is writable (#2425).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2218 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-31 11:48:56 +00:00
Jean-Philippe Lang
e6aaa03cf0 Do not escape back_url twice when login fails.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2214 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-31 10:39:33 +00:00
Jean-Philippe Lang
24f3ee777d Changes pt-br decimal separator (#1372).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2213 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-30 16:43:26 +00:00
Jean-Philippe Lang
8b7fb7213f Stricter textile links parsing (#2417).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2212 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-30 16:23:05 +00:00
Jean-Philippe Lang
421539e3be Import custom fields values from emails (#2413).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2211 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-30 14:57:33 +00:00
Jean-Philippe Lang
2355324d73 Jump to the current tab when using the project quick-jump combo (#2364).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2210 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-30 14:24:51 +00:00
Jean-Philippe Lang
6eee9dbf88 Increment project files downloads.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2209 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-30 13:32:51 +00:00
Jean-Philippe Lang
0819f00380 CHANGELOG updated.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2208 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-30 13:32:14 +00:00
winterheart
7bb9dd75db #2373, fixing encoding
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2202 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-29 18:27:27 +00:00
Jean-Philippe Lang
2e8a0bd562 CHANGELOG updated.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2201 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-29 16:08:31 +00:00
Jean-Philippe Lang
102c551b5a Translations updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2199 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-29 15:43:42 +00:00
Jean-Philippe Lang
45db63c207 Renumbers projects_trackers fixtures (#2411).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2196 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-29 12:40:56 +00:00
Jean-Philippe Lang
94b04383f9 Mail handler: add watchers before sending notification (#2245).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2195 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-28 14:48:23 +00:00
Jean-Philippe Lang
35f5e36838 Disable textile inline styles to prevent XSS attacks (#2377).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2192 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-28 13:38:34 +00:00
Jean-Philippe Lang
a140c9bd74 Fixed bold syntax around single character in series (#2351).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2191 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-28 10:12:09 +00:00
Jean-Philippe Lang
bde72a5f40 Fixes functional tests fixtures (#2398).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2190 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-28 09:46:16 +00:00
Jean-Philippe Lang
a7a4c9f848 Fixed: deleted files should not be shown when browsing a Darcs repository (#2385).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2189 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-27 18:33:35 +00:00
Jean-Philippe Lang
76a9101998 Do not show a link to the current annotate or view page (#2367).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2188 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-27 18:10:36 +00:00
Jean-Philippe Lang
caf6b4b3f1 Fixes functional test failures.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2187 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-27 18:07:46 +00:00
Jean-Philippe Lang
9a0814253c Fixtures update.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2186 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-27 17:49:01 +00:00
Jean-Philippe Lang
fb1f72a09c Prevent SQL error with old sessions after r2171.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2183 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-27 14:05:03 +00:00
winterheart
03fd86034d #2386, korean translation update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2182 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-24 15:48:59 +00:00
winterheart
e42e09cf8b #2368, pt.yml update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2181 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-24 15:47:24 +00:00
winterheart
3c462ab9c2 #2329, swedish lang update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2180 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-24 15:44:43 +00:00
Jean-Philippe Lang
d775aee26d Fixes a JS error on context_menu with IE (#2390).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2178 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-24 13:29:43 +00:00
Jean-Philippe Lang
5c97a83a70 Validates sort_key and sort_order params (#2378).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2171 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-24 10:03:13 +00:00
Jean-Philippe Lang
7776b5b665 Escape textile titles and styles (#2377).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2170 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-23 17:05:38 +00:00
Jean-Philippe Lang
e48f0f04e7 Escape query names (#2379).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2169 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-23 00:19:15 +00:00
Jean-Philippe Lang
fee8ada214 Escape wiki annotate lines content (#2380).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2168 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-23 00:16:26 +00:00
Jean-Philippe Lang
3f80a89a69 Show view/annotate/download links on repositories/entries and repositories/annotate views (#2367).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2167 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-22 20:33:01 +00:00
Jean-Philippe Lang
4fd2e4aa90 Sligth change to fr.yml.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2166 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-22 19:25:07 +00:00
Jean-Philippe Lang
67f82e32b4 Do not hardcode Watcher string in r2164.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2165 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-22 19:24:17 +00:00
Jean-Philippe Lang
1f89dad23a Adds watchers selection on new issue form (#398). Permission 'add issue watchers' required.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2164 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-22 19:21:02 +00:00
Jean-Philippe Lang
3de4a1d788 CHANGELOG updated.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2147 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-19 14:13:24 +00:00
Jean-Philippe Lang
ede011243b Check that wiki page exists before processing (#2360).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2145 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-19 10:43:06 +00:00
Jean-Philippe Lang
4ec5b1600a Escape double-quotes in image titles.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2144 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-19 10:16:15 +00:00
Jean-Philippe Lang
3ce1be14f7 Escape textarea content when editing a issue note.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2143 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-19 08:10:35 +00:00
winterheart
22b4005fd3 Typo on translation, #2352
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2142 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-18 22:27:32 +00:00
Eric Davis
ce64a42250 Fixed a failing test caused by comparing a Time object (n.day.ago) with a Date object
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2141 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-18 07:10:23 +00:00
Jean-Philippe Lang
2564f05037 Adds a css class (overdue) to overdue issues on issue lists and detail views (#2337).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2140 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-16 21:13:35 +00:00
Jean-Philippe Lang
02c2a83494 Adds a helper that returns issues css classes.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2139 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-16 21:11:37 +00:00
Jean-Philippe Lang
7cea286c23 Fixes repository user mapping submission when a repository username is blank (#2339, Conflicting types for parameter containers).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2137 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-15 18:02:25 +00:00
Jean-Philippe Lang
3bb2fccaf1 Mail handler: strip tags when receiving a html-only email (#2312).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2136 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-14 17:10:16 +00:00
Jean-Philippe Lang
840bb53f5b Fixed: CVS browser should not show dead revisions (deleted files) (#2319).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2135 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-14 15:57:13 +00:00
Jean-Philippe Lang
e2952d3e5f Rails 2.1.2 deprecations (#2332).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2134 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-14 15:36:59 +00:00
winterheart
040d0a32d2 Fixing quotes
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2131 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-12 19:41:12 +00:00
winterheart
83c9fcb13e translation updates
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2130 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-12 19:34:31 +00:00
Jean-Philippe Lang
dcf5ba1ea6 Make use of User.find_by_mail
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2129 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-12 19:11:16 +00:00
Jean-Philippe Lang
a37f4b9cf6 Capture scm CLI stderr to log/scm.stderr.log when running in dev environment
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2128 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-12 16:07:14 +00:00
Jean-Philippe Lang
740ec7656f Undo unwanted change.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2127 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-12 16:04:54 +00:00
Jean-Philippe Lang
1bfbecbcab Rescue back_url param parsing on redirect.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2126 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-12 16:03:57 +00:00
Jean-Philippe Lang
29f364f63c Escape back_url field value (#2320).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2125 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-12 16:01:35 +00:00
Jean-Philippe Lang
b21b6c365c Fixed: default category ignored when adding a document (#2328).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2124 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-12 13:49:14 +00:00
Jean-Philippe Lang
c651184998 Fixed: default flag removed when editing a default enumeration (#2327).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2123 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-12 13:32:39 +00:00
Jean-Philippe Lang
72d0843c1f Makes User.find_by_mail case-insensitive (password reminder #2322, repo users mapping).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2122 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-12 12:07:09 +00:00
Eric Davis
da98386bf7 Added plugin hooks around Journal editing
* :controller_journals_edit_post
* :view_journals_notes_form_after_notes
* :view_journals_update_rjs_bottom


git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2121 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-10 23:44:22 +00:00
winterheart
f02717ab94 russian update
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2120 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-10 17:13:04 +00:00
winterheart
63faea0c42 Translation updates (#2310, #2309, #2306, #2304, #2302, #2300, #2299)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2119 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-10 17:01:39 +00:00
Jean-Philippe Lang
2aceccf285 Fixed: Firefox cuts off large diffs (#2234).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2118 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-09 18:30:22 +00:00
Jean-Philippe Lang
66ff4cb7de Files module: makes version field non required (#1053).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2117 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-09 18:00:27 +00:00
Jean-Philippe Lang
5d2899ee1b AttachmentsController now handles attachments deletion.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2116 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-09 16:54:46 +00:00
Jean-Philippe Lang
2b6e332318 Fixed: project activity truncated after viewing user's activity.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2114 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-08 18:20:26 +00:00
Jean-Philippe Lang
fec86a9ce1 Adds a setting to limit the number of diff lines that should be displayed (default to 1500).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2112 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-07 15:21:40 +00:00
Jean-Philippe Lang
02acc7fc28 Follows r2110.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2111 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-07 14:44:08 +00:00
Jean-Philippe Lang
ea603e4ea5 Use options hash in UnifiedDiff.new
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2110 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-07 14:40:33 +00:00
Jean-Philippe Lang
0ea3d150e1 Makes logged-in username in topbar linking to (#2291).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2109 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-07 13:12:19 +00:00
Jean-Philippe Lang
e93d02d228 UPGRADING updated
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2105 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-07 09:59:19 +00:00
Jean-Philippe Lang
d9714bff32 Update changelog for 0.8 rc1
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2104 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-07 09:56:28 +00:00
Jean-Philippe Lang
70393f491d Set version to 0.8
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2103 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-07 09:54:37 +00:00
Jean-Philippe Lang
657fa55118 Upgrade to Rails 2.1.2
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2102 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-07 09:53:27 +00:00
Jean-Philippe Lang
a02ee73181 Show project name in front of related issues if cross-project issue relations are enabled (#2282).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2101 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-07 08:48:29 +00:00
Jean-Philippe Lang
70fa891d48 Changelog updated.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2100 e93f8b46-1217-0410-a6f0-8f06a7374b81
2008-12-07 08:41:54 +00:00
1058 changed files with 68573 additions and 45229 deletions

19
.gitignore vendored Normal file
View File

@@ -0,0 +1,19 @@
/config/additional_environment.rb
/config/database.yml
/config/email.yml
/config/initializers/session_store.rb
/coverage
/db/*.db
/db/*.sqlite3
/db/schema.rb
/files/*
/log/*.log*
/log/mongrel_debug
/public/dispatch.*
/public/plugin_assets
/tmp/*
/tmp/cache/*
/tmp/sessions/*
/tmp/sockets/*
/tmp/test/*
/vendor/rails

5
README.rdoc Normal file
View File

@@ -0,0 +1,5 @@
= Redmine
Redmine is a flexible project management web application written using Ruby on Rails framework.
More details can be found at http://www.redmine.org

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang
# Copyright (C) 2006-2009 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
@@ -20,24 +20,7 @@ class AccountController < ApplicationController
include CustomFieldsHelper
# prevents login action to be filtered by check_if_login_required application scope filter
skip_before_filter :check_if_login_required, :only => [:login, :lost_password, :register, :activate]
# Show user's account
def show
@user = User.active.find(params[:id])
@custom_values = @user.custom_values
# show only public projects and private projects that the logged in user is also a member of
@memberships = @user.memberships.select do |membership|
membership.project.is_public? || (User.current.member_of?(membership.project))
end
events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
@events_by_day = events.group_by(&:event_date)
rescue ActiveRecord::RecordNotFound
render_404
end
skip_before_filter :check_if_login_required
# Login request and validation
def login
@@ -46,25 +29,10 @@ class AccountController < ApplicationController
self.logged_user = nil
else
# Authenticate user
user = User.try_to_login(params[:username], params[:password])
if user.nil?
# Invalid credentials
flash.now[:error] = l(:notice_account_invalid_creditentials)
elsif user.new_record?
# Onthefly creation failed, display the registration form to fill/fix attributes
@user = user
session[:auth_source_registration] = {:login => user.login, :auth_source_id => user.auth_source_id }
render :action => 'register'
if Setting.openid? && using_open_id?
open_id_authenticate(params[:openid_url])
else
# Valid user
self.logged_user = user
# generate a key and set cookie if autologin
if params[:autologin] && Setting.autologin?
token = Token.create(:user => user, :action => 'autologin')
cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now }
end
call_hook(:controller_account_success_authentication_after, {:user => user })
redirect_back_or_default :controller => 'my', :action => 'page'
password_authentication
end
end
end
@@ -99,9 +67,9 @@ class AccountController < ApplicationController
if request.post?
user = User.find_by_mail(params[:mail])
# user not found in db
flash.now[:error] = l(:notice_account_unknown_email) and return unless user
(flash.now[:error] = l(:notice_account_unknown_email); return) unless user
# user uses an external authentification
flash.now[:error] = l(:notice_can_t_change_password) and return if user.auth_source_id
(flash.now[:error] = l(:notice_can_t_change_password); return) if user.auth_source_id
# create a new token for password recovery
token = Token.new(:user => user, :action => "recovery")
if token.save
@@ -137,31 +105,14 @@ class AccountController < ApplicationController
else
@user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
case Setting.self_registration
when '1'
# Email activation
token = Token.new(:user => @user, :action => "register")
if @user.save and token.save
Mailer.deliver_register(token)
flash[:notice] = l(:notice_account_register_done)
redirect_to :action => 'login'
end
register_by_email_activation(@user)
when '3'
# Automatic activation
@user.status = User::STATUS_ACTIVE
if @user.save
self.logged_user = @user
flash[:notice] = l(:notice_account_activated)
redirect_to :controller => 'my', :action => 'account'
end
register_automatically(@user)
else
# Manual activation by the administrator
if @user.save
# Sends an email to the administrators
Mailer.deliver_account_activation_request(@user)
flash[:notice] = l(:notice_account_pending)
redirect_to :action => 'login'
end
register_manually_by_administrator(@user)
end
end
end
@@ -182,14 +133,131 @@ class AccountController < ApplicationController
redirect_to :action => 'login'
end
private
def logged_user=(user)
if user && user.is_a?(User)
User.current = user
session[:user_id] = user.id
private
def password_authentication
user = User.try_to_login(params[:username], params[:password])
if user.nil?
invalid_credentials
elsif user.new_record?
onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id })
else
User.current = User.anonymous
session[:user_id] = nil
# Valid user
successful_authentication(user)
end
end
def open_id_authenticate(openid_url)
authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url) do |result, identity_url, registration|
if result.successful?
user = User.find_or_initialize_by_identity_url(identity_url)
if user.new_record?
# Self-registration off
redirect_to(home_url) && return unless Setting.self_registration?
# Create on the fly
user.login = registration['nickname'] unless registration['nickname'].nil?
user.mail = registration['email'] unless registration['email'].nil?
user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
user.random_password
user.status = User::STATUS_REGISTERED
case Setting.self_registration
when '1'
register_by_email_activation(user) do
onthefly_creation_failed(user)
end
when '3'
register_automatically(user) do
onthefly_creation_failed(user)
end
else
register_manually_by_administrator(user) do
onthefly_creation_failed(user)
end
end
else
# Existing record
if user.active?
successful_authentication(user)
else
account_pending
end
end
end
end
end
def successful_authentication(user)
# Valid user
self.logged_user = user
# generate a key and set cookie if autologin
if params[:autologin] && Setting.autologin?
token = Token.create(:user => user, :action => 'autologin')
cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now }
end
call_hook(:controller_account_success_authentication_after, {:user => user })
redirect_back_or_default :controller => 'my', :action => 'page'
end
# Onthefly creation failed, display the registration form to fill/fix attributes
def onthefly_creation_failed(user, auth_source_options = { })
@user = user
session[:auth_source_registration] = auth_source_options unless auth_source_options.empty?
render :action => 'register'
end
def invalid_credentials
flash.now[:error] = l(:notice_account_invalid_creditentials)
end
# Register a user for email activation.
#
# Pass a block for behavior when a user fails to save
def register_by_email_activation(user, &block)
token = Token.new(:user => user, :action => "register")
if user.save and token.save
Mailer.deliver_register(token)
flash[:notice] = l(:notice_account_register_done)
redirect_to :action => 'login'
else
yield if block_given?
end
end
# Automatically register a user
#
# Pass a block for behavior when a user fails to save
def register_automatically(user, &block)
# Automatic activation
user.status = User::STATUS_ACTIVE
user.last_login_on = Time.now
if user.save
self.logged_user = user
flash[:notice] = l(:notice_account_activated)
redirect_to :controller => 'my', :action => 'account'
else
yield if block_given?
end
end
# Manual activation by the administrator
#
# Pass a block for behavior when a user fails to save
def register_manually_by_administrator(user, &block)
if user.save
# Sends an email to the administrators
Mailer.deliver_account_activation_request(user)
account_pending
else
yield if block_given?
end
end
def account_pending
flash[:notice] = l(:notice_account_pending)
redirect_to :action => 'login'
end
end

View File

@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AdminController < ApplicationController
layout 'admin'
before_filter :require_admin
helper :sort
@@ -26,9 +28,6 @@ class AdminController < ApplicationController
end
def projects
sort_init 'name', 'asc'
sort_update %w(name is_public created_on)
@status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
@@ -37,14 +36,8 @@ class AdminController < ApplicationController
c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name]
end
@project_count = Project.count(:conditions => c.conditions)
@project_pages = Paginator.new self, @project_count,
per_page_option,
params['page']
@projects = Project.find :all, :order => sort_clause,
:conditions => c.conditions,
:limit => @project_pages.items_per_page,
:offset => @project_pages.current.offset
@projects = Project.find :all, :order => 'lft',
:conditions => c.conditions
render :action => "projects", :layout => false if request.xhr?
end
@@ -83,11 +76,11 @@ class AdminController < ApplicationController
def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name
@flags = {
:default_admin_changed => User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?,
:file_repository_writable => File.writable?(Attachment.storage_path),
:plugin_assets_writable => File.writable?(Engines.public_directory),
:rmagick_available => Object.const_defined?(:Magick)
}
@checklist = [
[:text_default_administrator_account_changed, User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
[:text_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
[:text_rmagick_available, Object.const_defined?(:Magick)]
]
end
end

View File

@@ -19,22 +19,36 @@ require 'uri'
require 'cgi'
class ApplicationController < ActionController::Base
include Redmine::I18n
layout 'base'
# Remove broken cookie after upgrade from 0.8.x (#4292)
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
# TODO: remove it when Rails is fixed
before_filter :delete_broken_cookies
def delete_broken_cookies
if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
cookies.delete '_redmine_session'
redirect_to home_path
return false
end
end
before_filter :user_setup, :check_if_login_required, :set_localization
filter_parameter_logging :password
protect_from_forgery
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
include Redmine::Search::Controller
include Redmine::MenuManager::MenuController
helper Redmine::MenuManager::MenuHelper
REDMINE_SUPPORTED_SCM.each do |scm|
require_dependency "repository/#{scm.underscore}"
end
def current_role
@current_role ||= User.current.role_for_project(@project)
end
def user_setup
# Check the settings cache for each request
Setting.check_cache
@@ -43,16 +57,40 @@ class ApplicationController < ActionController::Base
end
# Returns the current user or nil if no user is logged in
# and starts a session if needed
def find_current_user
if session[:user_id]
# existing session
(User.active.find(session[:user_id]) rescue nil)
elsif cookies[:autologin] && Setting.autologin?
# auto-login feature
User.find_by_autologin_key(cookies[:autologin])
elsif params[:key] && accept_key_auth_actions.include?(params[:action])
# RSS key authentication
# auto-login feature starts a new session
user = User.try_to_autologin(cookies[:autologin])
session[:user_id] = user.id if user
user
elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
# RSS key authentication does not start a session
User.find_by_rss_key(params[:key])
elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format]) && accept_key_auth_actions.include?(params[:action])
if params[:key].present?
# Use API key
User.find_by_api_key(params[:key])
else
# HTTP Basic, either username/password or API key/random
authenticate_with_http_basic do |username, password|
User.try_to_login(username, password) || User.find_by_api_key(username)
end
end
end
end
# Sets the logged in user
def logged_user=(user)
reset_session
if user && user.is_a?(User)
User.current = user
session[:user_id] = user.id
else
User.current = User.anonymous
end
end
@@ -64,25 +102,34 @@ class ApplicationController < ActionController::Base
end
def set_localization
User.current.language = nil unless User.current.logged?
lang = begin
if !User.current.language.blank? && GLoc.valid_language?(User.current.language)
User.current.language
elsif request.env['HTTP_ACCEPT_LANGUAGE']
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
if !accept_lang.blank? && (GLoc.valid_language?(accept_lang) || GLoc.valid_language?(accept_lang = accept_lang.split('-').first))
User.current.language = accept_lang
end
lang = nil
if User.current.logged?
lang = find_language(User.current.language)
end
if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
if !accept_lang.blank?
lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
end
rescue
nil
end || Setting.default_language
set_language_if_valid(lang)
end
lang ||= Setting.default_language
set_language_if_valid(lang)
end
def require_login
if !User.current.logged?
redirect_to :controller => "account", :action => "login", :back_url => url_for(params)
# Extract only the basic url parameters on non-GET requests
if request.get?
url = url_for(params)
else
url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
end
respond_to do |format|
format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
format.xml { head :unauthorized }
format.json { head :unauthorized }
end
return false
end
true
@@ -102,10 +149,15 @@ class ApplicationController < ActionController::Base
end
# Authorize the user for the requested action
def authorize(ctrl = params[:controller], action = params[:action])
allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project)
def authorize(ctrl = params[:controller], action = params[:action], global = false)
allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project, :global => global)
allowed ? true : deny_access
end
# Authorize the user for the requested action outside a project
def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
authorize(ctrl, action, global)
end
# make sure that the user is a member of the project (or admin) if project is private
# used as a before_filter for actions that do not require any particular permission on the project
@@ -130,7 +182,8 @@ class ApplicationController < ActionController::Base
uri = URI.parse(back_url)
# do not redirect user to another host or to the login or register page
if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
redirect_to(back_url) and return
redirect_to(back_url)
return
end
rescue URI::InvalidURIError
# redirect to default
@@ -141,7 +194,7 @@ class ApplicationController < ActionController::Base
def render_403
@project = nil
render :template => "common/403", :layout => !request.xhr?, :status => 403
render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403
return false
end
@@ -152,7 +205,11 @@ class ApplicationController < ActionController::Base
def render_error(msg)
flash.now[:error] = msg
render :nothing => true, :layout => !request.xhr?, :status => 500
render :text => '', :layout => !request.xhr?, :status => 500
end
def invalid_authenticity_token
render_error "Invalid form authenticity token."
end
def render_feed(items, options={})
@@ -225,6 +282,8 @@ class ApplicationController < ActionController::Base
tmp.collect!{|val, q| val}
end
return tmp
rescue
nil
end
# Returns a string that can be used as filename value in Content-Disposition header

View File

@@ -17,7 +17,7 @@
class AttachmentsController < ApplicationController
before_filter :find_project
before_filter :read_authorize, :except => :destroy
before_filter :file_readable, :read_authorize, :except => :destroy
before_filter :delete_authorize, :only => :destroy
verify :method => :post, :only => :destroy
@@ -26,10 +26,10 @@ class AttachmentsController < ApplicationController
if @attachment.is_diff?
@diff = File.new(@attachment.diskfile, "rb").read
render :action => 'diff'
elsif @attachment.is_text?
elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
@content = File.new(@attachment.diskfile, "rb").read
render :action => 'file'
elsif
else
download
end
end
@@ -41,7 +41,7 @@ class AttachmentsController < ApplicationController
# images are sent inline
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
:type => @attachment.content_type,
:type => detect_content_type(@attachment),
:disposition => (@attachment.image? ? 'inline' : 'attachment')
end
@@ -64,6 +64,11 @@ private
render_404
end
# Checks that the file exists and is readable
def file_readable
@attachment.readable? ? true : render_404
end
def read_authorize
@attachment.visible? ? true : deny_access
end
@@ -71,4 +76,12 @@ private
def delete_authorize
@attachment.deletable? ? true : deny_access
end
def detect_content_type(attachment)
content_type = attachment.content_type
if content_type.blank?
content_type = Redmine::MimeType.of(attachment.filename)
end
content_type.to_s
end
end

View File

@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AuthSourcesController < ApplicationController
layout 'admin'
before_filter :require_admin
def index

View File

@@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class BoardsController < ApplicationController
default_search_scope :messages
before_filter :find_project, :authorize
helper :messages
@@ -35,18 +36,29 @@ class BoardsController < ApplicationController
end
def show
sort_init 'updated_on', 'desc'
sort_update 'created_on' => "#{Message.table_name}.created_on",
'replies' => "#{Message.table_name}.replies_count",
'updated_on' => "#{Message.table_name}.updated_on"
@topic_count = @board.topics.count
@topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
@topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '),
:include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset
render :action => 'show', :layout => !request.xhr?
respond_to do |format|
format.html {
sort_init 'updated_on', 'desc'
sort_update 'created_on' => "#{Message.table_name}.created_on",
'replies' => "#{Message.table_name}.replies_count",
'updated_on' => "#{Message.table_name}.updated_on"
@topic_count = @board.topics.count
@topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
@topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '),
:include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset
@message = Message.new
render :action => 'show', :layout => !request.xhr?
}
format.atom {
@messages = @board.messages.find :all, :order => 'created_on DESC',
:include => [:author, :board],
:limit => Setting.feeds_limit.to_i
render_feed(@messages, :title => "#{@project}: #{@board}")
}
end
end
verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
@@ -62,12 +74,6 @@ class BoardsController < ApplicationController
def edit
if request.post? && @board.update_attributes(params[:board])
case params[:position]
when 'highest'; @board.move_to_top
when 'higher'; @board.move_higher
when 'lower'; @board.move_lower
when 'lowest'; @board.move_to_bottom
end if params[:position]
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end
end

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -16,38 +16,28 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CustomFieldsController < ApplicationController
layout 'admin'
before_filter :require_admin
def index
list
render :action => 'list' unless request.xhr?
end
def list
@custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
@tab = params[:tab] || 'IssueCustomField'
render :action => "list", :layout => false if request.xhr?
end
def new
case params[:type]
when "IssueCustomField"
@custom_field = IssueCustomField.new(params[:custom_field])
@custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids]
when "UserCustomField"
@custom_field = UserCustomField.new(params[:custom_field])
when "ProjectCustomField"
@custom_field = ProjectCustomField.new(params[:custom_field])
when "TimeEntryCustomField"
@custom_field = TimeEntryCustomField.new(params[:custom_field])
else
redirect_to :action => 'list'
return
end
@custom_field = begin
if params[:type].to_s.match(/.+CustomField$/)
params[:type].to_s.constantize.new(params[:custom_field])
end
rescue
end
(redirect_to(:action => 'index'); return) unless @custom_field.is_a?(CustomField)
if request.post? and @custom_field.save
flash[:notice] = l(:notice_successful_create)
call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
redirect_to :action => 'list', :tab => @custom_field.class.name
redirect_to :action => 'index', :tab => @custom_field.class.name
end
@trackers = Tracker.find(:all, :order => 'position')
end
@@ -55,36 +45,18 @@ class CustomFieldsController < ApplicationController
def edit
@custom_field = CustomField.find(params[:id])
if request.post? and @custom_field.update_attributes(params[:custom_field])
if @custom_field.is_a? IssueCustomField
@custom_field.trackers = params[:tracker_ids] ? Tracker.find(params[:tracker_ids]) : []
end
flash[:notice] = l(:notice_successful_update)
call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
redirect_to :action => 'list', :tab => @custom_field.class.name
redirect_to :action => 'index', :tab => @custom_field.class.name
end
@trackers = Tracker.find(:all, :order => 'position')
end
def move
@custom_field = CustomField.find(params[:id])
case params[:position]
when 'highest'
@custom_field.move_to_top
when 'higher'
@custom_field.move_higher
when 'lower'
@custom_field.move_lower
when 'lowest'
@custom_field.move_to_bottom
end if params[:position]
redirect_to :action => 'list', :tab => @custom_field.class.name
end
def destroy
@custom_field = CustomField.find(params[:id]).destroy
redirect_to :action => 'list', :tab => @custom_field.class.name
redirect_to :action => 'index', :tab => @custom_field.class.name
rescue
flash[:error] = "Unable to delete custom field"
redirect_to :action => 'list'
redirect_to :action => 'index'
end
end

View File

@@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class DocumentsController < ApplicationController
default_search_scope :documents
before_filter :find_project, :only => [:index, :new]
before_filter :find_document, :except => [:index, :new]
before_filter :authorize
@@ -27,7 +28,7 @@ class DocumentsController < ApplicationController
documents = @project.documents.find :all, :include => [:attachments, :category]
case @sort_by
when 'date'
@grouped = documents.group_by {|d| d.created_on.to_date }
@grouped = documents.group_by {|d| d.updated_on.to_date }
when 'title'
@grouped = documents.group_by {|d| d.title.first.upcase}
when 'author'
@@ -48,13 +49,12 @@ class DocumentsController < ApplicationController
if request.post? and @document.save
attach_files(@document, params[:attachments])
flash[:notice] = l(:notice_successful_create)
Mailer.deliver_document_added(@document) if Setting.notified_events.include?('document_added')
redirect_to :action => 'index', :project_id => @project
end
end
def edit
@categories = Enumeration::get_values('DCAT')
@categories = DocumentCategory.all
if request.post? and @document.update_attributes(params[:document])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @document

View File

@@ -16,7 +16,12 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class EnumerationsController < ApplicationController
layout 'admin'
before_filter :require_admin
helper :custom_fields
include CustomFieldsHelper
def index
list
@@ -31,14 +36,19 @@ class EnumerationsController < ApplicationController
end
def new
@enumeration = Enumeration.new(:opt => params[:opt])
begin
@enumeration = params[:type].constantize.new
rescue NameError
@enumeration = Enumeration.new
end
end
def create
@enumeration = Enumeration.new(params[:enumeration])
@enumeration.type = params[:enumeration][:type]
if @enumeration.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list', :opt => @enumeration.opt
redirect_to :action => 'list', :type => @enumeration.type
else
render :action => 'new'
end
@@ -50,28 +60,14 @@ class EnumerationsController < ApplicationController
def update
@enumeration = Enumeration.find(params[:id])
@enumeration.type = params[:enumeration][:type] if params[:enumeration][:type]
if @enumeration.update_attributes(params[:enumeration])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list', :opt => @enumeration.opt
redirect_to :action => 'list', :type => @enumeration.type
else
render :action => 'edit'
end
end
def move
@enumeration = Enumeration.find(params[:id])
case params[:position]
when 'highest'
@enumeration.move_to_top
when 'higher'
@enumeration.move_higher
when 'lower'
@enumeration.move_lower
when 'lowest'
@enumeration.move_to_bottom
end if params[:position]
redirect_to :action => 'index'
end
def destroy
@enumeration = Enumeration.find(params[:id])
@@ -80,12 +76,12 @@ class EnumerationsController < ApplicationController
@enumeration.destroy
redirect_to :action => 'index'
elsif params[:reassign_to_id]
if reassign_to = Enumeration.find_by_opt_and_id(@enumeration.opt, params[:reassign_to_id])
if reassign_to = Enumeration.find_by_type_and_id(@enumeration.type, params[:reassign_to_id])
@enumeration.destroy(reassign_to)
redirect_to :action => 'index'
end
end
@enumerations = Enumeration.get_values(@enumeration.opt) - [@enumeration]
@enumerations = Enumeration.find(:all, :conditions => ['type = (?)', @enumeration.type]) - [@enumeration]
#rescue
# flash[:error] = 'Unable to delete enumeration'
# redirect_to :action => 'index'

View File

@@ -0,0 +1,163 @@
# Redmine - project management software
# Copyright (C) 2006-2009 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 GroupsController < ApplicationController
layout 'admin'
before_filter :require_admin
helper :custom_fields
# GET /groups
# GET /groups.xml
def index
@groups = Group.find(:all, :order => 'lastname')
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @groups }
end
end
# GET /groups/1
# GET /groups/1.xml
def show
@group = Group.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @group }
end
end
# GET /groups/new
# GET /groups/new.xml
def new
@group = Group.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @group }
end
end
# GET /groups/1/edit
def edit
@group = Group.find(params[:id])
end
# POST /groups
# POST /groups.xml
def create
@group = Group.new(params[:group])
respond_to do |format|
if @group.save
flash[:notice] = l(:notice_successful_create)
format.html { redirect_to(groups_path) }
format.xml { render :xml => @group, :status => :created, :location => @group }
else
format.html { render :action => "new" }
format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
end
end
end
# PUT /groups/1
# PUT /groups/1.xml
def update
@group = Group.find(params[:id])
respond_to do |format|
if @group.update_attributes(params[:group])
flash[:notice] = l(:notice_successful_update)
format.html { redirect_to(groups_path) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /groups/1
# DELETE /groups/1.xml
def destroy
@group = Group.find(params[:id])
@group.destroy
respond_to do |format|
format.html { redirect_to(groups_url) }
format.xml { head :ok }
end
end
def add_users
@group = Group.find(params[:id])
users = User.find_all_by_id(params[:user_ids])
@group.users << users if request.post?
respond_to do |format|
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js {
render(:update) {|page|
page.replace_html "tab-content-users", :partial => 'groups/users'
users.each {|user| page.visual_effect(:highlight, "user-#{user.id}") }
}
}
end
end
def remove_user
@group = Group.find(params[:id])
@group.users.delete(User.find(params[:user_id])) if request.post?
respond_to do |format|
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
format.js { render(:update) {|page| page.replace_html "tab-content-users", :partial => 'groups/users'} }
end
end
def autocomplete_for_user
@group = Group.find(params[:id])
@users = User.active.like(params[:q]).find(:all, :limit => 100) - @group.users
render :layout => false
end
def edit_membership
@group = Group.find(params[:id])
@membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:principal => @group)
@membership.attributes = params[:membership]
@membership.save if request.post?
respond_to do |format|
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
format.js {
render(:update) {|page|
page.replace_html "tab-content-memberships", :partial => 'groups/memberships'
page.visual_effect(:highlight, "member-#{@membership.id}")
}
}
end
end
def destroy_membership
@group = Group.find(params[:id])
Member.find(params[:membership_id]).destroy if request.post?
respond_to do |format|
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'groups/memberships'} }
end
end
end

View File

@@ -21,6 +21,9 @@ class IssueRelationsController < ApplicationController
def new
@relation = IssueRelation.new(params[:relation])
@relation.issue_from = @issue
if params[:relation] && !params[:relation][:issue_to_id].blank?
@relation.issue_to = Issue.visible.find_by_id(params[:relation][:issue_to_id])
end
@relation.save if request.post?
respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }

View File

@@ -16,9 +16,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueStatusesController < ApplicationController
layout 'admin'
before_filter :require_admin
verify :method => :post, :only => [ :destroy, :create, :update, :move ],
verify :method => :post, :only => [ :destroy, :create, :update, :move, :update_issue_done_ratio ],
:redirect_to => { :action => :list }
def index
@@ -58,21 +60,6 @@ class IssueStatusesController < ApplicationController
render :action => 'edit'
end
end
def move
@issue_status = IssueStatus.find(params[:id])
case params[:position]
when 'highest'
@issue_status.move_to_top
when 'higher'
@issue_status.move_higher
when 'lower'
@issue_status.move_lower
when 'lowest'
@issue_status.move_to_bottom
end if params[:position]
redirect_to :action => 'list'
end
def destroy
IssueStatus.find(params[:id]).destroy
@@ -81,4 +68,13 @@ class IssueStatusesController < ApplicationController
flash[:error] = "Unable to delete issue status"
redirect_to :action => 'list'
end
def update_issue_done_ratio
if IssueStatus.update_issue_done_ratios
flash[:notice] = l(:notice_issue_done_ratios_updated)
else
flash[:error] = l(:error_issue_done_ratios_not_updated)
end
redirect_to :action => 'list'
end
end

View File

@@ -17,14 +17,17 @@
class IssuesController < ApplicationController
menu_item :new_issue, :only => :new
default_search_scope :issues
before_filter :find_issue, :only => [:show, :edit, :reply]
before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
before_filter :find_project, :only => [:new, :update_form, :preview]
before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :context_menu]
before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
accept_key_auth :index, :changes
accept_key_auth :index, :show, :changes
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
helper :journals
helper :projects
include ProjectsHelper
@@ -43,31 +46,37 @@ class IssuesController < ApplicationController
helper :timelog
include Redmine::Export::PDF
verify :method => :post,
:only => :destroy,
:render => { :nothing => true, :status => :method_not_allowed }
def index
retrieve_query
sort_init 'id', 'desc'
sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
if @query.valid?
limit = per_page_option
respond_to do |format|
format.html { }
format.atom { }
format.atom { limit = Setting.feeds_limit.to_i }
format.csv { limit = Setting.issues_export_limit.to_i }
format.pdf { limit = Setting.issues_export_limit.to_i }
end
@issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
@issue_count = @query.issue_count
@issue_pages = Paginator.new self, @issue_count, limit, params['page']
@issues = Issue.find :all, :order => sort_clause,
:include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
:conditions => @query.statement,
:limit => limit,
:offset => @issue_pages.current.offset
@issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
:order => sort_clause,
:offset => @issue_pages.current.offset,
:limit => limit)
@issue_count_by_group = @query.issue_count_by_group
respond_to do |format|
format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') }
format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
end
else
# Send html if the query is not valid
@@ -80,13 +89,11 @@ class IssuesController < ApplicationController
def changes
retrieve_query
sort_init 'id', 'desc'
sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
if @query.valid?
@journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
:conditions => @query.statement,
:limit => 25,
:order => "#{Journal.table_name}.created_on DESC"
@journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
:limit => 25)
end
@title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
render :layout => false, :content_type => 'application/atom+xml'
@@ -98,9 +105,11 @@ class IssuesController < ApplicationController
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
@journals.each_with_index {|j,i| j.indice = i+1}
@journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@priorities = Enumeration::get_values('IPRI')
@priorities = IssuePriority.all
@time_entry = TimeEntry.new
respond_to do |format|
format.html { render :template => 'issues/show.rhtml' }
@@ -118,8 +127,7 @@ class IssuesController < ApplicationController
# Tracker must be set before custom field values
@issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
if @issue.tracker.nil?
flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
render :nothing => true, :layout => true
render_error l(:error_no_tracker_in_project)
return
end
if params[:issue].is_a?(Hash)
@@ -130,12 +138,11 @@ class IssuesController < ApplicationController
default_status = IssueStatus.default
unless default_status
flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
render :nothing => true, :layout => true
render_error l(:error_no_default_issue_status)
return
end
@issue.status = default_status
@allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
@allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
if request.get? || request.xhr?
@issue.start_date ||= Date.today
@@ -143,16 +150,17 @@ class IssuesController < ApplicationController
requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
# Check that the user is allowed to apply the requested status
@issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
if @issue.save
attach_files(@issue, params[:attachments])
flash[:notice] = l(:notice_successful_create)
Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
{ :action => 'show', :id => @issue })
return
end
end
@priorities = Enumeration::get_values('IPRI')
@priorities = IssuePriority.all
render :layout => !request.xhr?
end
@@ -162,7 +170,7 @@ class IssuesController < ApplicationController
def edit
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@priorities = Enumeration::get_values('IPRI')
@priorities = IssuePriority.all
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@time_entry = TimeEntry.new
@@ -192,14 +200,16 @@ class IssuesController < ApplicationController
if !journal.new_record?
# Only send notification if something was actually changed
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
end
call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
end
end
rescue ActiveRecord::StaleObjectError
# Optimistic locking exception
flash.now[:error] = l(:notice_locking_conflict)
# Remove the previously added attachments if issue was not updated
attachments.each(&:destroy)
end
def reply
@@ -225,15 +235,18 @@ class IssuesController < ApplicationController
# Bulk edit a set of issues
def bulk_edit
if request.post?
tracker = params[:tracker_id].blank? ? nil : @project.trackers.find_by_id(params[:tracker_id])
status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
priority = params[:priority_id].blank? ? nil : IssuePriority.find_by_id(params[:priority_id])
assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.shared_versions.find_by_id(params[:fixed_version_id])
custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
unsaved_issue_ids = []
@issues.each do |issue|
journal = issue.init_journal(User.current, params[:notes])
issue.tracker = tracker if tracker
issue.priority = priority if priority
issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
issue.category = category if category || params[:category_id] == 'none'
@@ -241,12 +254,10 @@ class IssuesController < ApplicationController
issue.start_date = params[:start_date] unless params[:start_date].blank?
issue.due_date = params[:due_date] unless params[:due_date].blank?
issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
# Don't save any change to the issue if the user is not authorized to apply the requested status
if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
# Send notification for each issue (if changed)
Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
else
unless (status.nil? || (issue.new_statuses_allowed_to(User.current).include?(status) && issue.status = status)) && issue.save
# Keep unsaved issue ids to display them in flash error
unsaved_issue_ids << issue.id
end
@@ -254,41 +265,65 @@ class IssuesController < ApplicationController
if unsaved_issue_ids.empty?
flash[:notice] = l(:notice_successful_update) unless @issues.empty?
else
flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
:total => @issues.size,
:ids => '#' + unsaved_issue_ids.join(', #'))
end
redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
return
end
# Find potential statuses the user could be allowed to switch issues to
@available_statuses = Workflow.find(:all, :include => :new_status,
:conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
@available_statuses = Workflow.available_statuses(@project)
@custom_fields = @project.all_issue_custom_fields
end
def move
@copy = params[:copy_options] && params[:copy_options][:copy]
@allowed_projects = []
# find projects to which the user is allowed to move the issue
if User.current.admin?
# admin is allowed to move issues to any active (visible) project
@allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
@allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
else
User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
User.current.memberships.each {|m| @allowed_projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
end
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
@target_project ||= @project
@trackers = @target_project.trackers
@available_statuses = Workflow.available_statuses(@project)
if request.post?
new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
unsaved_issue_ids = []
moved_issues = []
@issues.each do |issue|
changed_attributes = {}
[:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute|
unless params[valid_attribute].blank?
changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
end
end
issue.init_journal(User.current)
unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
if r = issue.move_to(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes})
moved_issues << r
else
unsaved_issue_ids << issue.id
end
end
if unsaved_issue_ids.empty?
flash[:notice] = l(:notice_successful_update) unless @issues.empty?
else
flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
:total => @issues.size,
:ids => '#' + unsaved_issue_ids.join(', #'))
end
if params[:follow]
if @issues.size == 1 && moved_issues.size == 1
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
else
redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
end
else
redirect_to :controller => 'issues', :action => 'index', :project_id => @project
end
redirect_to :controller => 'issues', :action => 'index', :project_id => @project
return
end
render :layout => false if request.xhr?
@@ -325,28 +360,27 @@ class IssuesController < ApplicationController
if @query.valid?
events = []
# Issues that have start and due dates
events += Issue.find(:all,
:order => "start_date, due_date",
:include => [:tracker, :status, :assigned_to, :priority, :project],
:conditions => ["(#{@query.statement}) AND (((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)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
)
events += @query.issues(:include => [:tracker, :assigned_to, :priority],
: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)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
)
# Issues that don't have a due date but that are assigned to a version with a date
events += Issue.find(:all,
:order => "start_date, effective_date",
:include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
:conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
)
events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
:order => "start_date, effective_date",
:conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
)
# Versions
events += Version.find(:all, :include => :project,
:conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
@gantt.events = events
end
basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
respond_to do |format|
format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
end
end
@@ -364,12 +398,10 @@ class IssuesController < ApplicationController
retrieve_query
if @query.valid?
events = []
events += Issue.find(:all,
:include => [:tracker, :status, :assigned_to, :priority, :project],
:conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
)
events += Version.find(:all, :include => :project,
:conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
events += @query.issues(:include => [:tracker, :assigned_to, :priority],
:conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
)
events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
@calendar.events = events
end
@@ -396,18 +428,28 @@ class IssuesController < ApplicationController
if @project
@assignables = @project.assignable_users
@assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
@trackers = @project.trackers
end
@priorities = Enumeration.get_values('IPRI').reverse
@priorities = IssuePriority.all.reverse
@statuses = IssueStatus.find(:all, :order => 'position')
@back = request.env['HTTP_REFERER']
@back = params[:back_url] || request.env['HTTP_REFERER']
render :layout => false
end
def update_form
@issue = Issue.new(params[:issue])
render :action => :new, :layout => false
if params[:id].blank?
@issue = Issue.new
@issue.project = @project
else
@issue = @project.issues.visible.find(params[:id])
end
@issue.attributes = params[:issue]
@allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
@priorities = IssuePriority.all
render :partial => 'attributes'
end
def preview
@@ -434,7 +476,8 @@ private
@project = projects.first
else
# TODO: let users bulk edit/move/destroy issues from different projects
render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
render_error 'Can not bulk edit/move/destroy issues from different projects'
return false
end
rescue ActiveRecord::RecordNotFound
render_404
@@ -462,6 +505,7 @@ private
@query = Query.find(params[:query_id], :conditions => cond)
@query.project = @project
session[:query] = {:id => @query.id, :project_id => @query.project_id}
sort_clear
else
if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
# Give it a name, required to be valid
@@ -476,12 +520,22 @@ private
@query.add_short_filter(field, params[field]) if params[field]
end
end
session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
@query.group_by = params[:group_by]
@query.column_names = params[:query] && params[:query][:column_names]
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
else
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
@query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
@query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
@query.project = @project
end
end
end
# Rescues an invalid query statement. Just in case...
def query_statement_invalid(exception)
logger.error "Query::StatementInvalid: #{exception.message}" if logger
session.delete(:query)
sort_clear
render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
end
end

View File

@@ -33,7 +33,7 @@ class JournalsController < ApplicationController
private
def find_journal
@journal = Journal.find(params[:id])
render_403 and return false unless @journal.editable_by?(User.current)
(render_403; return false) unless @journal.editable_by?(User.current)
@project = @journal.journalized.project
rescue ActiveRecord::RecordNotFound
render_404

View File

@@ -37,8 +37,8 @@ class MailHandlerController < ActionController::Base
def check_credential
User.current = nil
unless Setting.mail_handler_api_enabled? && params[:key] == Setting.mail_handler_api_key
render :nothing => true, :status => 403
unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key
render :text => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403
end
end
end

View File

@@ -16,15 +16,31 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class MembersController < ApplicationController
before_filter :find_member, :except => :new
before_filter :find_project, :only => :new
before_filter :find_member, :except => [:new, :autocomplete_for_member]
before_filter :find_project, :only => [:new, :autocomplete_for_member]
before_filter :authorize
def new
@project.members << Member.new(params[:member]) if request.post?
members = []
if params[:member] && request.post?
attrs = params[:member].dup
if (user_ids = attrs.delete(:user_ids))
user_ids.each do |user_id|
members << Member.new(attrs.merge(:user_id => user_id))
end
else
members << Member.new(attrs)
end
@project.members << members
end
respond_to do |format|
format.html { redirect_to :action => 'settings', :tab => 'members', :id => @project }
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js {
render(:update) {|page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
}
}
end
end
@@ -32,18 +48,30 @@ class MembersController < ApplicationController
if request.post? and @member.update_attributes(params[:member])
respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
format.js {
render(:update) {|page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
page.visual_effect(:highlight, "member-#{@member.id}")
}
}
end
end
end
def destroy
@member.destroy
respond_to do |format|
if request.post? && @member.deletable?
@member.destroy
end
respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
end
end
def autocomplete_for_member
@principals = Principal.active.like(params[:q]).find(:all, :limit => 100) - @project.principals
render :layout => false
end
private
def find_project

View File

@@ -17,6 +17,7 @@
class MessagesController < ApplicationController
menu_item :boards
default_search_scope :messages
before_filter :find_board, :only => [:new, :preview]
before_filter :find_message, :except => [:new, :preview]
before_filter :authorize, :except => [:preview, :edit, :destroy]
@@ -46,6 +47,7 @@ class MessagesController < ApplicationController
@message.sticky = params[:message]['sticky']
end
if request.post? && @message.save
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
attach_files(@message, params[:attachments])
redirect_to :action => 'show', :id => @message
end
@@ -58,6 +60,7 @@ class MessagesController < ApplicationController
@reply.board = @board
@topic.children << @reply
if !@reply.new_record?
call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
attach_files(@reply, params[:attachments])
end
redirect_to :action => 'show', :id => @topic
@@ -65,7 +68,7 @@ class MessagesController < ApplicationController
# Edit a message
def edit
render_403 and return false unless @message.editable_by?(User.current)
(render_403; return false) unless @message.editable_by?(User.current)
if params[:message]
@message.locked = params[:message]['locked']
@message.sticky = params[:message]['sticky']
@@ -73,13 +76,14 @@ class MessagesController < ApplicationController
if request.post? && @message.update_attributes(params[:message])
attach_files(@message, params[:attachments])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @topic
@message.reload
redirect_to :action => 'show', :board_id => @message.board, :id => @message.root
end
end
# Delete a messages
def destroy
render_403 and return false unless @message.destroyable_by?(User.current)
(render_403; return false) unless @message.destroyable_by?(User.current)
@message.destroy
redirect_to @message.parent.nil? ?
{ :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
@@ -89,9 +93,12 @@ class MessagesController < ApplicationController
def quote
user = @message.author
text = @message.content
subject = @message.subject.gsub('"', '\"')
subject = "RE: #{subject}" unless subject.starts_with?('RE:')
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
render(:update) { |page|
page << "$('reply_subject').value = \"#{subject}\";"
page.<< "$('message_content').value = \"#{content}\";"
page.show 'reply'
page << "Form.Element.focus('message_content');"

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -19,6 +19,7 @@ class MyController < ApplicationController
before_filter :require_login
helper :issues
helper :custom_fields
BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
'issuesreportedbyme' => :label_reported_issues,
@@ -27,14 +28,13 @@ class MyController < ApplicationController
'calendar' => :label_calendar,
'documents' => :label_document_plural,
'timelog' => :label_spent_time
}.freeze
}.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
'right' => ['issuesreportedbyme']
}.freeze
verify :xhr => true,
:session => :page_layout,
:only => [:add_block, :remove_block, :order_blocks]
def index
@@ -77,7 +77,11 @@ class MyController < ApplicationController
# Manage user's password
def password
@user = User.current
flash[:error] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id
if @user.auth_source_id
flash[:error] = l(:notice_can_t_change_password)
redirect_to :action => 'account'
return
end
if request.post?
if @user.check_password?(params[:password])
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
@@ -93,43 +97,65 @@ class MyController < ApplicationController
# Create a new feeds key
def reset_rss_key
if request.post? && User.current.rss_token
User.current.rss_token.destroy
if request.post?
if User.current.rss_token
User.current.rss_token.destroy
User.current.reload
end
User.current.rss_key
flash[:notice] = l(:notice_feeds_access_key_reseted)
end
redirect_to :action => 'account'
end
# Create a new API key
def reset_api_key
if request.post?
if User.current.api_token
User.current.api_token.destroy
User.current.reload
end
User.current.api_key
flash[:notice] = l(:notice_api_access_key_reseted)
end
redirect_to :action => 'account'
end
# User's page layout configuration
def page_layout
@user = User.current
@blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
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]}
BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
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
block = params[:block]
render(:nothing => true) and return unless block && (BLOCKS.keys.include? block)
block = params[:block].to_s.underscore
(render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
@user = User.current
layout = @user.pref[:my_page_layout] || {}
# remove if already present in a group
%w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
%w(top left right).each {|f| (layout[f] ||= []).delete block }
# add it on top
session[:page_layout]['top'].unshift block
layout['top'].unshift block
@user.pref[:my_page_layout] = layout
@user.pref.save
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]
block = params[:block].to_s.underscore
@user = User.current
# remove block in all groups
%w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
layout = @user.pref[:my_page_layout] || {}
%w(top left right).each {|f| (layout[f] ||= []).delete block }
@user.pref[:my_page_layout] = layout
@user.pref.save
render :nothing => true
end
@@ -138,23 +164,20 @@ class MyController < ApplicationController
# 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
@user = User.current
if group.is_a?(String)
group_items = (params["list-#{group}"] || []).collect(&:underscore)
if group_items and group_items.is_a? Array
layout = @user.pref[:my_page_layout] || {}
# remove group blocks if they are presents in other groups
%w(top left right).each {|f|
layout[f] = (layout[f] || []) - group_items
}
layout[group] = group_items
@user.pref[:my_page_layout] = layout
@user.pref.save
end
end
render :nothing => true
end
# Save user's page layout
def page_layout_save
@user = User.current
@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

@@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class NewsController < ApplicationController
default_search_scope :news
before_filter :find_news, :except => [:new, :index, :preview]
before_filter :find_project, :only => [:new, :preview]
before_filter :authorize, :except => [:index, :preview]
@@ -25,11 +26,13 @@ class NewsController < ApplicationController
def index
@news_pages, @newss = paginate :news,
:per_page => 10,
:conditions => (@project ? {:project_id => @project.id} : Project.visible_by(User.current)),
:conditions => Project.allowed_to_condition(User.current, :view_news, :project => @project),
:include => [:author, :project],
:order => "#{News.table_name}.created_on DESC"
respond_to do |format|
format.html { render :layout => false if request.xhr? }
format.xml { render :xml => @newss.to_xml }
format.json { render :json => @newss.to_json }
format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
end
end
@@ -45,7 +48,6 @@ class NewsController < ApplicationController
@news.attributes = params[:news]
if @news.save
flash[:notice] = l(:notice_successful_create)
Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
redirect_to :controller => 'news', :action => 'index', :project_id => @project
end
end
@@ -65,6 +67,7 @@ class NewsController < ApplicationController
flash[:notice] = l(:label_comment_added)
redirect_to :action => 'show', :id => @news
else
show
render :action => 'show'
end
end

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -21,14 +21,20 @@ class ProjectsController < ApplicationController
menu_item :roadmap, :only => :roadmap
menu_item :files, :only => [:list_files, :add_file]
menu_item :settings, :only => :settings
menu_item :issues, :only => [:changelog]
before_filter :find_project, :except => [ :index, :list, :add, :activity ]
before_filter :find_project, :except => [ :index, :list, :add, :copy, :activity ]
before_filter :find_optional_project, :only => :activity
before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy, :activity ]
before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy, :activity ]
before_filter :authorize_global, :only => :add
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
accept_key_auth :activity
after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller|
if controller.request.post?
controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
end
end
helper :sort
include SortHelper
helper :custom_fields
@@ -43,17 +49,14 @@ class ProjectsController < ApplicationController
# Lists visible projects
def index
projects = Project.find :all,
:conditions => Project.visible_by(User.current),
:include => :parent
respond_to do |format|
format.html {
@project_tree = projects.group_by {|p| p.parent || p}
@project_tree.keys.each {|p| @project_tree[p] -= [p]}
@projects = Project.visible.find(:all, :order => 'lft')
}
format.atom {
render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
:title => "#{Setting.app_title}: #{l(:label_project_latest)}")
projects = Project.visible.find(:all, :order => 'created_on DESC',
:limit => Setting.feeds_limit.to_i)
render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
}
end
end
@@ -62,22 +65,59 @@ class ProjectsController < ApplicationController
def add
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@root_projects = Project.find(:all,
:conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
:order => 'name')
@project = Project.new(params[:project])
if request.get?
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
@project.trackers = Tracker.all
@project.is_public = Setting.default_projects_public?
@project.enabled_module_names = Redmine::AccessControl.available_project_modules
@project.enabled_module_names = Setting.default_projects_modules
else
@project.enabled_module_names = params[:enabled_modules]
if @project.save
if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
# Add current user as a project member if he is not admin
unless User.current.admin?
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
m = Member.new(:user => User.current, :roles => [r])
@project.members << m
end
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'settings', :id => @project
end
end
end
def copy
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@root_projects = Project.find(:all,
:conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
:order => 'name')
@source_project = Project.find(params[:id])
if request.get?
@project = Project.copy_from(@source_project)
if @project
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
else
redirect_to :controller => 'admin', :action => 'projects'
end
else
@project = Project.new(params[:project])
@project.enabled_module_names = params[:enabled_modules]
if validate_parent_id && @project.copy(@source_project, :only => params[:only])
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'admin', :action => 'projects'
end
end
elsif !@project.new_record?
# Project was created
# But some objects were not copied due to validation failures
# (eg. issues from disabled trackers)
# TODO: inform about that
redirect_to :controller => 'admin', :action => 'projects'
end
end
rescue ActiveRecord::RecordNotFound
redirect_to :controller => 'admin', :action => 'projects'
end
# Show @project
@@ -87,20 +127,20 @@ class ProjectsController < ApplicationController
redirect_to_project_menu_item(@project, params[:jump]) && return
end
@members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
@subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
@users_by_role = @project.users_by_role
@subprojects = @project.children.visible
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@trackers = @project.rolled_up_trackers
cond = @project.project_condition(Setting.display_subprojects_issues?)
Issue.visible_by(User.current) do
@open_issues_by_tracker = Issue.count(:group => :tracker,
@open_issues_by_tracker = Issue.visible.count(:group => :tracker,
:include => [:project, :status, :tracker],
:conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
@total_issues_by_tracker = Issue.count(:group => :tracker,
@total_issues_by_tracker = Issue.visible.count(:group => :tracker,
:include => [:project, :status, :tracker],
:conditions => cond)
end
TimeEntry.visible_by(User.current) do
@total_hours = TimeEntry.sum(:hours,
:include => :project,
@@ -110,9 +150,6 @@ class ProjectsController < ApplicationController
end
def settings
@root_projects = Project.find(:all,
:conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
:order => 'name')
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@issue_category ||= IssueCategory.new
@member ||= @project.members.new
@@ -125,7 +162,8 @@ class ProjectsController < ApplicationController
def edit
if request.post?
@project.attributes = params[:project]
if @project.save
if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project
else
@@ -141,13 +179,17 @@ class ProjectsController < ApplicationController
end
def archive
@project.archive if request.post? && @project.active?
redirect_to :controller => 'admin', :action => 'projects'
if request.post?
unless @project.archive
flash[:error] = l(:error_can_not_archive_project)
end
end
redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end
def unarchive
@project.unarchive if request.post? && !@project.active?
redirect_to :controller => 'admin', :action => 'projects'
redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
end
# Delete @project
@@ -164,17 +206,26 @@ class ProjectsController < ApplicationController
# Add a new issue category to @project
def add_issue_category
@category = @project.issue_categories.build(params[:category])
if request.post? and @category.save
respond_to do |format|
format.html do
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'settings', :tab => 'categories', :id => @project
if request.post?
if @category.save
respond_to do |format|
format.html do
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'settings', :tab => 'categories', :id => @project
end
format.js do
# IE doesn't support the replace_html rjs method for select box options
render(:update) {|page| page.replace "issue_category_id",
content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
}
end
end
format.js do
# IE doesn't support the replace_html rjs method for select box options
render(:update) {|page| page.replace "issue_category_id",
content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
}
else
respond_to do |format|
format.html
format.js do
render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
end
end
end
end
@@ -182,10 +233,34 @@ class ProjectsController < ApplicationController
# 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', :tab => 'versions', :id => @project
@version = @project.versions.build
if params[:version]
attributes = params[:version].dup
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
@version.attributes = attributes
end
if request.post?
if @version.save
respond_to do |format|
format.html do
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'settings', :tab => 'versions', :id => @project
end
format.js do
# IE doesn't support the replace_html rjs method for select box options
render(:update) {|page| page.replace "issue_fixed_version_id",
content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
}
end
end
else
respond_to do |format|
format.html
format.js do
render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
end
end
end
end
end
@@ -201,6 +276,25 @@ class ProjectsController < ApplicationController
end
@versions = @project.versions.sort
end
def save_activities
if request.post? && params[:enumerations]
Project.transaction do
params[:enumerations].each do |id, activity|
@project.update_or_create_time_entry_activity(id, activity)
end
end
end
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end
def reset_activities
@project.time_entry_activities.each do |time_entry_activity|
time_entry_activity.destroy(time_entry_activity.parent)
end
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end
def list_files
sort_init 'filename', 'asc'
@@ -213,19 +307,31 @@ class ProjectsController < ApplicationController
@containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
render :layout => !request.xhr?
end
# Show changelog for @project
def changelog
@trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
retrieve_selected_tracker_ids(@trackers)
@versions = @project.versions.sort
end
def roadmap
@trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
retrieve_selected_tracker_ids(@trackers)
@versions = @project.versions.sort
@versions = @versions.select {|v| !v.completed? } unless params[:completed]
@trackers = @project.trackers.find(:all, :order => 'position')
retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
@versions = @project.shared_versions.sort
@versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
@issues_by_version = {}
unless @selected_tracker_ids.empty?
@versions.each do |version|
conditions = {:tracker_id => @selected_tracker_ids}
if !@project.versions.include?(version)
conditions.merge!(:project_id => project_ids)
end
issues = version.fixed_issues.visible.find(:all,
:include => [:project, :status, :tracker, :priority],
:conditions => conditions,
:order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
@issues_by_version[version] = issues
end
end
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].empty?}
end
def activity
@@ -248,20 +354,22 @@ class ProjectsController < ApplicationController
events = @activity.events(@date_from, @date_to)
respond_to do |format|
format.html {
@events_by_day = events.group_by(&:event_date)
render :layout => false if request.xhr?
}
format.atom {
title = l(:label_activity)
if @author
title = @author.name
elsif @activity.scope.size == 1
title = l("label_#{@activity.scope.first.singularize}_plural")
end
render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
}
if events.empty? || stale?(:etag => [events.first, User.current])
respond_to do |format|
format.html {
@events_by_day = events.group_by(&:event_date)
render :layout => false if request.xhr?
}
format.atom {
title = l(:label_activity)
if @author
title = @author.name
elsif @activity.scope.size == 1
title = l("label_#{@activity.scope.first.singularize}_plural")
end
render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
}
end
end
rescue ActiveRecord::RecordNotFound
@@ -286,11 +394,26 @@ private
render_404
end
def retrieve_selected_tracker_ids(selectable_trackers)
def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
if ids = params[:tracker_ids]
@selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
else
@selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
@selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
end
end
# Validates parent_id param according to user's permissions
# TODO: move it to Project model in a validation that depends on User.current
def validate_parent_id
return true if User.current.admin?
parent_id = params[:project] && params[:project][:parent_id]
if parent_id || @project.new_record?
parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
unless @project.allowed_parents.include?(parent)
@project.errors.add :parent_id, :invalid
return false
end
end
true
end
end

View File

@@ -24,12 +24,13 @@ class QueriesController < ApplicationController
@query = Query.new(params[:query])
@query.project = params[:query_is_for_all] ? nil : @project
@query.user = User.current
@query.is_public = false unless (@query.project && current_role.allowed_to?(:manage_public_queries)) || User.current.admin?
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.column_names = nil if params[:default_columns]
params[:fields].each do |field|
@query.add_filter(field, params[:operators][field], params[:values][field])
end if params[:fields]
@query.group_by ||= params[:group_by]
if request.post? && params[:confirm] && @query.save
flash[:notice] = l(:notice_successful_create)
@@ -47,7 +48,7 @@ class QueriesController < ApplicationController
end if params[:fields]
@query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all]
@query.is_public = false unless (@query.project && current_role.allowed_to?(:manage_public_queries)) || User.current.admin?
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.column_names = nil if params[:default_columns]
if @query.save

View File

@@ -31,13 +31,13 @@ class ReportsController < ApplicationController
render :template => "reports/issue_report_details"
when "version"
@field = "fixed_version_id"
@rows = @project.versions.sort
@rows = @project.shared_versions.sort
@data = issues_by_version
@report_title = l(:field_version)
render :template => "reports/issue_report_details"
when "priority"
@field = "priority_id"
@rows = Enumeration::get_values('IPRI')
@rows = IssuePriority.all
@data = issues_by_priority
@report_title = l(:field_priority)
render :template => "reports/issue_report_details"
@@ -61,18 +61,18 @@ class ReportsController < ApplicationController
render :template => "reports/issue_report_details"
when "subproject"
@field = "project_id"
@rows = @project.active_children
@rows = @project.descendants.active
@data = issues_by_subproject
@report_title = l(:field_subproject)
render :template => "reports/issue_report_details"
else
@trackers = @project.trackers
@versions = @project.versions.sort
@priorities = Enumeration::get_values('IPRI')
@versions = @project.shared_versions.sort
@priorities = IssuePriority.all
@categories = @project.issue_categories
@assignees = @project.members.collect { |m| m.user }
@authors = @project.members.collect { |m| m.user }
@subprojects = @project.active_children
@subprojects = @project.descendants.active
issues_by_tracker
issues_by_version
issues_by_priority
@@ -85,42 +85,6 @@ class ReportsController < ApplicationController
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
@@ -166,7 +130,7 @@ private
p.id as priority_id,
count(i.id) as total
from
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{Enumeration.table_name} p
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{IssuePriority.table_name} p
where
i.status_id=s.id
and i.priority_id=p.id
@@ -229,8 +193,8 @@ private
#{Issue.table_name} i, #{IssueStatus.table_name} s
where
i.status_id=s.id
and i.project_id IN (#{@project.active_children.collect{|p| p.id}.join(',')})
group by s.id, s.is_closed, i.project_id") if @project.active_children.any?
and i.project_id IN (#{@project.descendants.active.collect{|p| p.id}.join(',')})
group by s.id, s.is_closed, i.project_id") if @project.descendants.active.any?
@issues_by_subproject ||= []
end
end

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -24,6 +24,8 @@ class InvalidRevisionParam < Exception; end
class RepositoriesController < ApplicationController
menu_item :repository
default_search_scope :changesets
before_filter :find_repository, :except => :edit
before_filter :find_project, :only => :edit
before_filter :authorize
@@ -64,31 +66,26 @@ class RepositoriesController < ApplicationController
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
end
def show
# check if new revisions have been committed in the repository
@repository.fetch_changesets if Setting.autofetch_changesets?
# root entries
@entries = @repository.entries('', @rev)
# latest changesets
@changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
show_error_not_found unless @entries || @changesets.any?
end
def browse
def show
@repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
@entries = @repository.entries(@path, @rev)
if request.xhr?
@entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
else
show_error_not_found and return unless @entries
(show_error_not_found; return) unless @entries
@changesets = @repository.latest_changesets(@path, @rev)
@properties = @repository.properties(@path, @rev)
render :action => 'browse'
render :action => 'show'
end
end
alias_method :browse, :show
def changes
@entry = @repository.entry(@path, @rev)
show_error_not_found and return unless @entry
@changesets = @repository.changesets_for_path(@path)
(show_error_not_found; return) unless @entry
@changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
@properties = @repository.properties(@path, @rev)
end
@@ -100,7 +97,7 @@ class RepositoriesController < ApplicationController
@changesets = @repository.changesets.find(:all,
:limit => @changeset_pages.items_per_page,
:offset => @changeset_pages.current.offset,
:include => :user)
:include => [:user, :repository])
respond_to do |format|
format.html { render :layout => false if request.xhr? }
@@ -110,15 +107,15 @@ class RepositoriesController < ApplicationController
def entry
@entry = @repository.entry(@path, @rev)
show_error_not_found and return unless @entry
(show_error_not_found; return) unless @entry
# If the entry is a dir, show the browser
browse and return if @entry.is_dir?
(show; return) if @entry.is_dir?
@content = @repository.cat(@path, @rev)
show_error_not_found and return unless @content
if 'raw' == params[:format] || @content.is_binary_data?
# Force the download if it's a binary file
(show_error_not_found; return) unless @content
if 'raw' == params[:format] || @content.is_binary_data? || (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte)
# Force the download
send_data @content, :filename => @path.split('/').last
else
# Prevent empty lines when displaying a file with Windows style eol
@@ -128,14 +125,14 @@ class RepositoriesController < ApplicationController
def annotate
@entry = @repository.entry(@path, @rev)
show_error_not_found and return unless @entry
(show_error_not_found; return) unless @entry
@annotate = @repository.scm.annotate(@path, @rev)
render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
(render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
end
def revision
@changeset = @repository.changesets.find_by_revision(@rev)
@changeset = @repository.find_changeset_by_name(@rev)
raise ChangesetNotFound unless @changeset
respond_to do |format|
@@ -149,7 +146,7 @@ class RepositoriesController < ApplicationController
def diff
if params[:format] == 'diff'
@diff = @repository.diff(@path, @rev, @rev_to)
show_error_not_found and return unless @diff
(show_error_not_found; return) unless @diff
filename = "changeset_r#{@rev}"
filename << "_r#{@rev_to}" if @rev_to
send_data @diff.join, :filename => "#{filename}.diff",
@@ -199,17 +196,14 @@ private
render_404
end
REV_PARAM_RE = %r{^[a-f0-9]*$}
def find_repository
@project = Project.find(params[:id])
@repository = @project.repository
render_404 and return false unless @repository
(render_404; return false) unless @repository
@path = params[:path].join('/') unless params[:path].nil?
@path ||= ''
@rev = params[:rev]
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
@rev_to = params[:rev_to]
raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
rescue ActiveRecord::RecordNotFound
render_404
rescue InvalidRevisionParam
@@ -238,8 +232,7 @@ private
changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
fields = []
month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
graph = SVG::Graph::Bar.new(
:height => 300,
@@ -268,7 +261,7 @@ private
def graph_commits_per_author(repository)
commits_by_author = repository.changesets.count(:all, :group => :committer)
commits_by_author.sort! {|x, y| x.last <=> y.last}
commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
changes_by_author = repository.changes.count(:all, :group => :committer)
h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}

View File

@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class RolesController < ApplicationController
layout 'admin'
before_filter :require_admin
verify :method => :post, :only => [ :destroy, :move ],
@@ -40,7 +42,7 @@ class RolesController < ApplicationController
@role.workflows.copy(copy_from)
end
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list'
redirect_to :action => 'index'
end
@permissions = @role.setable_permissions
@roles = Role.find :all, :order => 'builtin, position'
@@ -50,7 +52,7 @@ class RolesController < ApplicationController
@role = Role.find(params[:id])
if request.post? and @role.update_attributes(params[:role])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list'
redirect_to :action => 'index'
end
@permissions = @role.setable_permissions
end
@@ -58,27 +60,12 @@ class RolesController < ApplicationController
def destroy
@role = Role.find(params[:id])
@role.destroy
redirect_to :action => 'list'
redirect_to :action => 'index'
rescue
flash[:error] = 'This role is in use and can not be deleted.'
redirect_to :action => 'index'
end
def move
@role = Role.find(params[:id])
case params[:position]
when 'highest'
@role.move_to_top
when 'higher'
@role.move_higher
when 'lower'
@role.move_lower
when 'lowest'
@role.move_to_bottom
end if params[:position]
redirect_to :action => 'list'
end
def report
@roles = Role.find(:all, :order => 'builtin, position')
@permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
@@ -88,7 +75,7 @@ class RolesController < ApplicationController
role.save
end
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list'
redirect_to :action => 'index'
end
end
end

View File

@@ -34,7 +34,7 @@ class SearchController < ApplicationController
when 'my_projects'
User.current.memberships.collect(&:project)
when 'subprojects'
@project ? ([ @project ] + @project.active_children) : nil
@project ? (@project.self_and_descendants.active) : nil
else
@project
end
@@ -43,7 +43,7 @@ class SearchController < ApplicationController
begin; offset = params[:offset].to_time if params[:offset]; rescue; end
# quick jump to an issue
if @question.match(/^#?(\d+)$/) && Issue.find_by_id($1, :include => :project, :conditions => Project.visible_by(User.current))
if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1)
redirect_to :controller => "issues", :action => "show", :id => $1
return
end

View File

@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class SettingsController < ApplicationController
layout 'admin'
before_filter :require_admin
def index
@@ -24,7 +26,7 @@ class SettingsController < ApplicationController
end
def edit
@notifiables = %w(issue_added issue_updated news_added document_added file_added message_posted)
@notifiables = %w(issue_added issue_updated news_added document_added file_added message_posted wiki_content_added wiki_content_updated)
if request.post? && params[:settings] && params[:settings].is_a?(Hash)
settings = (params[:settings] || {}).dup.symbolize_keys
settings.each do |name, value|
@@ -41,7 +43,7 @@ class SettingsController < ApplicationController
@deliveries = ActionMailer::Base.perform_deliveries
@guessed_host_and_path = request.host_with_port.dup
@guessed_host_and_path << ('/'+ request.relative_url_root.gsub(%r{^\/}, '')) unless request.relative_url_root.blank?
@guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
end
def plugin

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -16,31 +16,52 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class SysController < ActionController::Base
wsdl_service_name 'Sys'
web_service_api SysApi
web_service_scaffold :invoke
before_filter :check_enabled
before_invocation :check_enabled
def projects
p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
render :xml => p.to_xml(:include => :repository)
end
# Returns the projects list, with their repositories
def projects_with_repository_enabled
Project.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
def create_project_repository
project = Project.find(params[:id])
if project.repository
render :nothing => true, :status => 409
else
logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
project.repository = Repository.factory(params[:vendor], params[:repository])
if project.repository && project.repository.save
render :xml => project.repository, :status => 201
else
render :nothing => true, :status => 422
end
end
end
def fetch_changesets
projects = []
if params[:id]
projects << Project.active.has_module(:repository).find(params[:id])
else
projects = Project.active.has_module(:repository).find(:all, :include => :repository)
end
projects.each do |project|
if project.repository
project.repository.fetch_changesets
end
end
render :nothing => true, :status => 200
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => 404
end
# Registers a repository for the given project identifier
def repository_created(identifier, vendor, url)
project = Project.find_by_identifier(identifier)
# Do not create the repository if the project has already one
return 0 unless project && project.repository.nil?
logger.debug "Repository for #{project.name} was created"
repository = Repository.factory(vendor, :project => project, :url => url)
repository.save
repository.id || 0
end
protected
protected
def check_enabled(name, args)
Setting.sys_api_enabled?
def check_enabled
User.current = nil
unless Setting.sys_api_enabled? && params[:key].to_s == Setting.sys_api_key
render :text => 'Access denied. Repository management WS is disabled or key is invalid.', :status => 403
return false
end
end
end

View File

@@ -46,7 +46,7 @@ class TimelogController < ApplicationController
:klass => Tracker,
:label => :label_tracker},
'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
:klass => Enumeration,
:klass => TimeEntryActivity,
:label => :label_activity},
'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
:klass => Issue,
@@ -67,7 +67,14 @@ class TimelogController < ApplicationController
:format => cf.field_format,
:label => cf.name}
end
# Add list and boolean time entry activity custom fields
TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id)",
:format => cf.field_format,
:label => cf.name}
end
@criterias = params[:criterias] || []
@criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
@criterias.uniq!
@@ -80,15 +87,23 @@ class TimelogController < ApplicationController
unless @criterias.empty?
sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
sql_condition = ''
if @project.nil?
sql_condition = Project.allowed_to_condition(User.current, :view_time_entries)
elsif @issue.nil?
sql_condition = @project.project_condition(Setting.display_subprojects_issues?)
else
sql_condition = "#{TimeEntry.table_name}.issue_id = #{@issue.id}"
end
sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
sql << " FROM #{TimeEntry.table_name}"
sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
sql << " WHERE"
sql << " (%s) AND" % @project.project_condition(Setting.display_subprojects_issues?) if @project
sql << " (%s) AND" % Project.allowed_to_condition(User.current, :view_time_entries)
sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from.to_time), ActiveRecord::Base.connection.quoted_date(@to.to_time)]
sql << " (%s) AND" % sql_condition
sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)]
sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
@hours = ActiveRecord::Base.connection.select_all(sql)
@@ -132,7 +147,7 @@ class TimelogController < ApplicationController
respond_to do |format|
format.html { render :layout => !request.xhr? }
format.csv { send_data(report_to_csv(@criterias, @periods, @hours).read, :type => 'text/csv; header=present', :filename => 'timelog.csv') }
format.csv { send_data(report_to_csv(@criterias, @periods, @hours), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
end
end
@@ -187,16 +202,19 @@ class TimelogController < ApplicationController
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:conditions => cond.conditions,
:order => sort_clause)
send_data(entries_to_csv(@entries).read, :type => 'text/csv; header=present', :filename => 'timelog.csv')
send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
}
end
end
end
def edit
render_403 and return if @time_entry && !@time_entry.editable_by?(User.current)
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
(render_403; return) if @time_entry && !@time_entry.editable_by?(User.current)
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
if request.post? and @time_entry.save
flash[:notice] = l(:notice_successful_update)
redirect_back_or_default :action => 'details', :project_id => @time_entry.project
@@ -205,8 +223,8 @@ class TimelogController < ApplicationController
end
def destroy
render_404 and return unless @time_entry
render_403 and return unless @time_entry.editable_by?(User.current)
(render_404; return) unless @time_entry
(render_403; return) unless @time_entry.editable_by?(User.current)
@time_entry.destroy
flash[:notice] = l(:notice_successful_delete)
redirect_to :back

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -16,15 +16,16 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class TrackersController < ApplicationController
layout 'admin'
before_filter :require_admin
def index
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, :move ], :redirect_to => { :action => :list }
verify :method => :post, :only => :destroy, :redirect_to => { :action => :list }
def list
@tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
@@ -40,8 +41,10 @@ class TrackersController < ApplicationController
end
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list'
return
end
@trackers = Tracker.find :all, :order => 'position'
@projects = Project.find(:all)
end
def edit
@@ -49,22 +52,9 @@ class TrackersController < ApplicationController
if request.post? and @tracker.update_attributes(params[:tracker])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list'
return
end
end
def move
@tracker = Tracker.find(params[:id])
case params[:position]
when 'highest'
@tracker.move_to_top
when 'higher'
@tracker.move_higher
when 'lower'
@tracker.move_lower
when 'lowest'
@tracker.move_to_bottom
end if params[:position]
redirect_to :action => 'list'
@projects = Project.find(:all)
end
def destroy

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -16,7 +16,9 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class UsersController < ApplicationController
before_filter :require_admin
layout 'admin'
before_filter :require_admin, :except => :show
helper :sort
include SortHelper
@@ -24,11 +26,6 @@ class UsersController < ApplicationController
include CustomFieldsHelper
def index
list
render :action => 'list' unless request.xhr?
end
def list
sort_init 'login', 'asc'
sort_update %w(login firstname lastname mail admin created_on last_login_on)
@@ -37,7 +34,7 @@ class UsersController < ApplicationController
unless params[:name].blank?
name = "%#{params[:name].strip.downcase}%"
c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ?", name, name, name]
c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
end
@user_count = User.count(:conditions => c.conditions)
@@ -49,7 +46,29 @@ class UsersController < ApplicationController
:limit => @user_pages.items_per_page,
:offset => @user_pages.current.offset
render :action => "list", :layout => false if request.xhr?
render :layout => !request.xhr?
end
def show
@user = User.active.find(params[:id])
@custom_values = @user.custom_values
# show only public projects and private projects that the logged in user is also a member of
@memberships = @user.memberships.select do |membership|
membership.project.is_public? || (User.current.member_of?(membership.project))
end
events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
@events_by_day = events.group_by(&:event_date)
if @user != User.current && !User.current.admin? && @memberships.empty? && events.empty?
render_404
return
end
render :layout => 'base'
rescue ActiveRecord::RecordNotFound
render_404
end
def add
@@ -63,7 +82,9 @@ class UsersController < ApplicationController
if @user.save
Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list'
redirect_to(params[:continue] ? {:controller => 'users', :action => 'add'} :
{:controller => 'users', :action => 'edit', :id => @user})
return
end
end
@auth_sources = AuthSource.find(:all)
@@ -75,34 +96,51 @@ class UsersController < ApplicationController
@user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
@user.group_ids = params[:user][:group_ids] if params[:user][:group_ids]
@user.attributes = params[:user]
# Was the account actived ? (do it before User#save clears the change)
was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
if @user.save
Mailer.deliver_account_activated(@user) if was_activated
if was_activated
Mailer.deliver_account_activated(@user)
elsif @user.active? && params[:send_information] && !params[:password].blank? && @user.auth_source_id.nil?
Mailer.deliver_account_information(@user, params[:password])
end
flash[:notice] = l(:notice_successful_update)
# Give a string to redirect_to otherwise it would use status param as the response code
redirect_to(url_for(:action => 'list', :status => params[:status], :page => params[:page]))
redirect_to :back
end
end
@auth_sources = AuthSource.find(:all)
@roles = Role.find_all_givable
@projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects
@membership ||= Member.new
@memberships = @user.memberships
rescue ::ActionController::RedirectBackError
redirect_to :controller => 'users', :action => 'edit', :id => @user
end
def edit_membership
@user = User.find(params[:id])
@membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:user => @user)
@membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:principal => @user)
@membership.attributes = params[:membership]
@membership.save if request.post?
redirect_to :action => 'edit', :id => @user, :tab => 'memberships'
respond_to do |format|
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
format.js {
render(:update) {|page|
page.replace_html "tab-content-memberships", :partial => 'users/memberships'
page.visual_effect(:highlight, "member-#{@membership.id}")
}
}
end
end
def destroy_membership
@user = User.find(params[:id])
Member.find(params[:membership_id]).destroy if request.post?
redirect_to :action => 'edit', :id => @user, :tab => 'memberships'
@membership = Member.find(params[:membership_id])
if request.post? && @membership.deletable?
@membership.destroy
end
respond_to do |format|
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
end
end
end

View File

@@ -17,17 +17,33 @@
class VersionsController < ApplicationController
menu_item :roadmap
before_filter :find_project, :authorize
before_filter :find_version, :except => :close_completed
before_filter :find_project, :only => :close_completed
before_filter :authorize
helper :custom_fields
helper :projects
def show
end
def edit
if request.post? and @version.update_attributes(params[:version])
flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
if request.post? && params[:version]
attributes = params[:version].dup
attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
if @version.update_attributes(attributes)
flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end
end
end
def close_completed
if request.post?
@project.close_completed_versions
end
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end
def destroy
@version.destroy
@@ -45,10 +61,16 @@ class VersionsController < ApplicationController
end
private
def find_project
def find_version
@version = Version.find(params[:id])
@project = @version.project
rescue ActiveRecord::RecordNotFound
render_404
end
end
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -18,14 +18,18 @@
class WatchersController < ApplicationController
before_filter :find_project
before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
before_filter :authorize, :only => :new
before_filter :authorize, :only => [:new, :destroy]
verify :method => :post,
:only => [ :watch, :unwatch ],
:render => { :nothing => true, :status => :method_not_allowed }
def watch
set_watcher(User.current, true)
if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
render_403
else
set_watcher(User.current, true)
end
end
def unwatch
@@ -48,6 +52,18 @@ class WatchersController < ApplicationController
render :text => 'Watcher added.', :layout => true
end
def destroy
@watched.set_watcher(User.find(params[:user_id]), false) if request.post?
respond_to do |format|
format.html { redirect_to :back }
format.js do
render :update do |page|
page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
end
end
end
end
private
def find_project
klass = Object.const_get(params[:object_type].camelcase)
@@ -60,9 +76,24 @@ private
def set_watcher(user, watching)
@watched.set_watcher(user, watching)
if params[:replace].present?
if params[:replace].is_a? Array
replace_ids = params[:replace]
else
replace_ids = [params[:replace]]
end
else
replace_ids = 'watcher'
end
respond_to do |format|
format.html { redirect_to :back }
format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
format.js do
render(:update) do |page|
replace_ids.each do |replace_id|
page.replace_html replace_id, watcher_link(@watched, user, :replace => replace_ids)
end
end
end
end
rescue ::ActionController::RedirectBackError
render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true

View File

@@ -16,9 +16,15 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class WelcomeController < ApplicationController
caches_action :robots
def index
@news = News.latest User.current
@projects = Project.latest User.current
end
def robots
@projects = Project.all_public.active
render :layout => false, :content_type => 'text/plain'
end
end

View File

@@ -18,6 +18,7 @@
require 'diff'
class WikiController < ApplicationController
default_search_scope :wiki_pages
before_filter :find_wiki, :authorize
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
@@ -25,6 +26,7 @@ class WikiController < ApplicationController
helper :attachments
include AttachmentsHelper
helper :watchers
# display a page (in editing mode if it doesn't exist)
def index
@@ -45,11 +47,11 @@ class WikiController < ApplicationController
return
end
@content = @page.content_for_version(params[:version])
if params[:export] == 'html'
if params[:format] == 'html'
export = render_to_string :action => 'export', :layout => false
send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
return
elsif params[:export] == 'txt'
elsif params[:format] == 'txt'
send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
return
end
@@ -82,6 +84,7 @@ class WikiController < ApplicationController
@content.author = User.current
# if page is new @page.save will also save content, but not if page isn't a new record
if (@page.new_record? ? @page.save : @content.save)
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
redirect_to :action => 'index', :id => @project, :page => @page.title
end
end
@@ -131,9 +134,31 @@ class WikiController < ApplicationController
render_404 unless @annotate
end
# remove a wiki page and its history
# Removes a wiki page and its history
# Children can be either set as root pages, removed or reassigned to another parent page
def destroy
return render_403 unless editable?
@descendants_count = @page.descendants.size
if @descendants_count > 0
case params[:todo]
when 'nullify'
# Nothing to do
when 'destroy'
# Removes all its descendants
@page.descendants.each(&:destroy)
when 'reassign'
# Reassign children to another parent page
reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i)
return unless reassign_to
@page.children.each do |child|
child.update_attribute(:parent, reassign_to)
end
else
@reassignable_to = @wiki.pages - @page.self_and_descendants
return
end
end
@page.destroy
redirect_to :action => 'special', :id => @project, :page => 'Page_index'
end
@@ -158,7 +183,8 @@ class WikiController < ApplicationController
return
else
# requested special page doesn't exist, redirect to default page
redirect_to :action => 'index', :id => @project, :page => nil and return
redirect_to :action => 'index', :id => @project, :page => nil
return
end
render :action => "special_#{page_title}"
end

View File

@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class WorkflowsController < ApplicationController
layout 'admin'
before_filter :require_admin
def index
@@ -40,6 +42,42 @@ class WorkflowsController < ApplicationController
end
@roles = Role.find(:all, :order => 'builtin, position')
@trackers = Tracker.find(:all, :order => 'position')
@statuses = IssueStatus.find(:all, :order => 'position')
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
@statuses = @tracker.issue_statuses
end
@statuses ||= IssueStatus.find(:all, :order => 'position')
end
def copy
@trackers = Tracker.find(:all, :order => 'position')
@roles = Role.find(:all, :order => 'builtin, position')
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
@source_tracker = nil
else
@source_tracker = Tracker.find_by_id(params[:source_tracker_id].to_i)
end
if params[:source_role_id].blank? || params[:source_role_id] == 'any'
@source_role = nil
else
@source_role = Role.find_by_id(params[:source_role_id].to_i)
end
@target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids])
@target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids])
if request.post?
if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?)
flash.now[:error] = l(:error_workflow_copy_source)
elsif @target_trackers.nil? || @target_roles.nil?
flash.now[:error] = l(:error_workflow_copy_target)
else
Workflow.copy(@source_tracker, @source_role, @target_trackers, @target_roles)
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role
end
end
end
end

View File

@@ -20,4 +20,12 @@ module AdminHelper
options_for_select([[l(:label_all), ''],
[l(:status_active), 1]], selected)
end
def css_project_classes(project)
s = 'project'
s << ' root' if project.root?
s << ' child' if project.child?
s << (project.leaf? ? ' leaf' : ' parent')
s
end
end

View File

@@ -22,15 +22,12 @@ require 'cgi'
module ApplicationHelper
include Redmine::WikiFormatting::Macros::Definitions
include Redmine::I18n
include GravatarHelper::PublicMethods
extend Forwardable
def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
def current_role
@current_role ||= User.current.role_for_project(@project)
end
# Return true if user is authorized for controller/action, otherwise false
def authorize_for(controller, action)
User.current.allowed_to?({:controller => controller, :action => action}, @project)
@@ -47,16 +44,45 @@ module ApplicationHelper
link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
end
# Display a link to user's account page
# Displays a link to user's account page if active
def link_to_user(user, options={})
(user && !user.anonymous?) ? link_to(user.name(options[:format]), :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
if user.is_a?(User)
name = h(user.name(options[:format]))
if user.active?
link_to name, :controller => 'users', :action => 'show', :id => user
else
name
end
else
h(user.to_s)
end
end
# Displays a link to +issue+ with its subject.
# Examples:
#
# link_to_issue(issue) # => Defect #6: This is the subject
# link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
# link_to_issue(issue, :subject => false) # => Defect #6
# link_to_issue(issue, :project => true) # => Foo - Defect #6
#
def link_to_issue(issue, options={})
options[:class] ||= ''
options[:class] << ' issue'
options[:class] << ' closed' if issue.closed?
link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
title = nil
subject = nil
if options[:subject] == false
title = truncate(issue.subject, :length => 60)
else
subject = issue.subject
if options[:truncate]
subject = truncate(subject, :length => options[:truncate])
end
end
s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
:class => issue.css_classes,
:title => title
s << ": #{h subject}" if subject
s = "#{h issue.project} - " + s if options[:project]
s
end
# Generates a link to an attachment.
@@ -70,6 +96,15 @@ module ApplicationHelper
link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
end
# Generates a link to a SCM revision
# Options:
# * :text - Link text (default to the formatted revision)
def link_to_revision(revision, project, options={})
text = options.delete(:text) || format_revision(revision)
link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
end
def toggle_link(name, id, options={})
onclick = "Element.toggle('#{id}'); "
onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
@@ -89,26 +124,9 @@ module ApplicationHelper
html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
link_to name, {}, html_options
end
def format_date(date)
return nil unless date
# "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
@date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
date.strftime(@date_format)
end
def format_time(time, include_date = true)
return nil unless time
time = time.to_time if time.is_a?(String)
zone = User.current.time_zone
local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
@date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
@time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
end
def format_activity_title(text)
h(truncate_single_line(text, 100))
h(truncate_single_line(text, :length => 100))
end
def format_activity_day(date)
@@ -116,16 +134,17 @@ module ApplicationHelper
end
def format_activity_description(text)
h(truncate(text.to_s, 250).gsub(%r{<(pre|code)>.*$}m, '...'))
h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
end
def distance_of_date_in_words(from_date, to_date = 0)
from_date = from_date.to_date if from_date.respond_to?(:to_date)
to_date = to_date.to_date if to_date.respond_to?(:to_date)
distance_in_days = (to_date - from_date).abs
lwr(:actionview_datehelper_time_in_words_day, distance_in_days)
def format_version_name(version)
if version.project == @project
h(version)
else
h("#{version.project} - #{version}")
end
end
def due_date_distance_in_words(date)
if date
l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
@@ -156,10 +175,90 @@ module ApplicationHelper
end
s
end
# Renders tabs and their content
def render_tabs(tabs)
if tabs.any?
render :partial => 'common/tabs', :locals => {:tabs => tabs}
else
content_tag 'p', l(:label_no_data), :class => "nodata"
end
end
# Renders the project quick-jump box
def render_project_jump_box
# Retrieve them now to avoid a COUNT query
projects = User.current.projects.all
if projects.any?
s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
"<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
'<option value="" disabled="disabled">---</option>'
s << project_tree_options_for_select(projects, :selected => @project) do |p|
{ :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
end
s << '</select>'
s
end
end
def project_tree_options_for_select(projects, options = {})
s = ''
project_tree(projects) do |project, level|
name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
tag_options = {:value => project.id, :selected => ((project == options[:selected]) ? 'selected' : nil)}
tag_options.merge!(yield(project)) if block_given?
s << content_tag('option', name_prefix + h(project), tag_options)
end
s
end
# Yields the given block for each project with its level in the tree
def project_tree(projects, &block)
ancestors = []
projects.sort_by(&:lft).each do |project|
while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
ancestors.pop
end
yield project, ancestors.size
ancestors << project
end
end
def project_nested_ul(projects, &block)
s = ''
if projects.any?
ancestors = []
projects.sort_by(&:lft).each do |project|
if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
s << "<ul>\n"
else
ancestors.pop
s << "</li>"
while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
ancestors.pop
s << "</ul></li>\n"
end
end
s << "<li>"
s << yield(project).to_s
ancestors << project
end
s << ("</li></ul>\n" * ancestors.size)
end
s
end
def principals_check_box_tags(name, principals)
s = ''
principals.sort.each do |principal|
s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
end
s
end
# Truncates and returns the string as a single line
def truncate_single_line(string, *args)
truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
end
def html_hours(text)
@@ -167,25 +266,16 @@ module ApplicationHelper
end
def authoring(created, author, options={})
time_tag = @project.nil? ? content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created)) :
link_to(distance_of_time_in_words(Time.now, created),
{:controller => 'projects', :action => 'activity', :id => @project, :from => created.to_date},
:title => format_time(created))
author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
l(options[:label] || :label_added_time_by, author_tag, time_tag)
l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
end
def l_or_humanize(s, options={})
k = "#{options[:prefix]}#{s}".to_sym
l_has_string?(k) ? l(k) : s.to_s.humanize
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]
def time_tag(time)
text = distance_of_time_in_words(Time.now, time)
if @project
link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
else
content_tag('acronym', text, :title => format_time(time))
end
end
def syntax_highlight(name, content)
@@ -200,52 +290,82 @@ module ApplicationHelper
def pagination_links_full(paginator, count=nil, options={})
page_param = options.delete(:page_param) || :page
url_param = params.dup
# don't reuse params if filters are present
url_param.clear if url_param.has_key?(:set_filter)
# don't reuse query params if filters are present
url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
html = ''
html << link_to_remote(('&#171; ' + l(:label_previous)),
{:update => 'content',
:url => url_param.merge(page_param => paginator.current.previous),
:complete => 'window.scrollTo(0,0)'},
{:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
if paginator.current.previous
html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
end
html << (pagination_links_each(paginator, options) do |n|
link_to_remote(n.to_s,
{:url => {:params => url_param.merge(page_param => n)},
:update => 'content',
:complete => 'window.scrollTo(0,0)'},
{:href => url_for(:params => url_param.merge(page_param => n))})
link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
end || '')
html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
{:update => 'content',
:url => url_param.merge(page_param => paginator.current.next),
:complete => 'window.scrollTo(0,0)'},
{:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
if paginator.current.next
html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
end
unless count.nil?
html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
html << [
" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
per_page_links(paginator.items_per_page)
].compact.join(' | ')
end
html
end
def per_page_links(selected=nil)
url_param = params.dup
url_param.clear if url_param.has_key?(:set_filter)
links = Setting.per_page_options_array.collect do |n|
n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
n == selected ? n : link_to_remote(n, {:update => "content",
:url => params.dup.merge(:per_page => n),
:method => :get},
{:href => url_for(url_param.merge(:per_page => n))})
end
links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
end
def reorder_links(name, url)
link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
end
def breadcrumb(*args)
elements = args.flatten
elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
end
def other_formats_links(&block)
concat('<p class="other-formats">' + l(:label_export_to))
yield Redmine::Views::OtherFormatsBuilder.new(self)
concat('</p>')
end
def page_header_title
if @project.nil? || @project.new_record?
h(Setting.app_title)
else
b = []
ancestors = (@project.root? ? [] : @project.ancestors.visible)
if ancestors.any?
root = ancestors.shift
b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
if ancestors.size > 2
b << '&#8230;'
ancestors = ancestors[-2, 2]
end
b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
end
b << h(@project)
b.join(' &#187; ')
end
end
def html_title(*args)
if args.empty?
@@ -253,7 +373,7 @@ module ApplicationHelper
title << @project.name if @project
title += @html_title if @html_title
title << Setting.app_title
title.compact.join(' - ')
title.select {|t| !t.blank? }.join(' - ')
else
@html_title ||= []
@html_title += args
@@ -352,7 +472,7 @@ module ApplicationHelper
:class => ('wiki-page' + (wiki_page ? '' : ' new')))
else
# project or wiki doesn't exist
title || page
all
end
else
all
@@ -385,7 +505,7 @@ module ApplicationHelper
# export:some/file -> Force the download of the file
# Forum messages:
# message#1218 -> Link to message with id 1218
text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m|
leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
link = nil
if esc.nil?
@@ -393,17 +513,16 @@ module ApplicationHelper
if project && (changeset = project.changesets.find_by_revision(oid))
link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
:class => 'changeset',
:title => truncate_single_line(changeset.comments, 100))
:title => truncate_single_line(changeset.comments, :length => 100))
end
elsif sep == '#'
oid = oid.to_i
case prefix
when nil
if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
if issue = Issue.visible.find_by_id(oid, :include => :status)
link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
:class => (issue.closed? ? 'issue closed' : 'issue'),
:title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
link = content_tag('del', link) if issue.closed?
:class => issue.css_classes,
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
end
when 'document'
if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
@@ -417,7 +536,7 @@ module ApplicationHelper
end
when 'message'
if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
link = link_to h(truncate(message.subject, 60)), {:only_path => only_path,
link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
:controller => 'messages',
:action => 'show',
:board_id => message.board,
@@ -444,7 +563,7 @@ module ApplicationHelper
if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
:class => 'changeset',
:title => truncate_single_line(changeset.comments, 100)
:title => truncate_single_line(changeset.comments, :length => 100)
end
when 'source', 'export'
if project && project.repository
@@ -479,46 +598,9 @@ module ApplicationHelper
gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
end
def error_messages_for(object_name, options = {})
options = options.symbolize_keys
object = instance_variable_get("@#{object_name}")
if object && !object.errors.empty?
# build full_messages here with controller current language
full_messages = []
object.errors.each do |attr, msg|
next if msg.nil?
msg = [msg] unless msg.is_a?(Array)
if attr == "base"
full_messages << l(*msg)
else
full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(*msg) unless attr == "custom_values"
end
end
# retrieve custom values error messages
if object.errors[:custom_values]
object.custom_values.each do |v|
v.errors.each do |attr, msg|
next if msg.nil?
msg = [msg] unless msg.is_a?(Array)
full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(*msg)
end
end
end
content_tag("div",
content_tag(
options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
) +
content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
"id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
)
else
""
end
end
def lang_options_for_select(blank=true)
(blank ? [["(auto)", ""]] : []) +
GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
end
def label_tag_for(name, option_tags = nil, options = {})
@@ -546,15 +628,16 @@ module ApplicationHelper
def progress_bar(pcts, options={})
pcts = [pcts, pcts] unless pcts.is_a?(Array)
pcts = pcts.collect(&:round)
pcts[1] = pcts[1] - pcts[0]
pcts << (100 - pcts[1] - pcts[0])
width = options[:width] || '100px;'
legend = options[:legend] || ''
content_tag('table',
content_tag('tr',
(pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0].floor}%;", :class => 'closed') : '') +
(pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1].floor}%;", :class => 'done') : '') +
(pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2].floor}%;", :class => 'todo') : '')
(pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
(pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
(pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
), :class => 'progress', :style => "width: #{width};") +
content_tag('p', legend, :class => 'pourcent')
end
@@ -585,8 +668,18 @@ module ApplicationHelper
unless @calendar_headers_tags_included
@calendar_headers_tags_included = true
content_for :header_tags do
start_of_week = case Setting.start_of_week.to_i
when 1
'Calendar._FD = 1;' # Monday
when 7
'Calendar._FD = 0;' # Sunday
else
'' # use language
end
javascript_include_tag('calendar/calendar') +
javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
javascript_tag(start_of_week) +
javascript_include_tag('calendar/calendar-setup') +
stylesheet_link_tag('calendar')
end
@@ -607,6 +700,7 @@ module ApplicationHelper
# +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
def avatar(user, options = { })
if Setting.gravatar_enabled?
options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
email = nil
if user.respond_to?(:mail)
email = user.mail
@@ -624,4 +718,12 @@ module ApplicationHelper
extend helper
return self
end
def link_to_remote_content_update(text, url_params)
link_to_remote(text,
{:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
{:href => url_for(:params => url_params)}
)
end
end

View File

@@ -18,10 +18,15 @@
module CustomFieldsHelper
def custom_fields_tabs
tabs = [{:name => 'IssueCustomField', :label => :label_issue_plural},
{:name => 'TimeEntryCustomField', :label => :label_spent_time},
{:name => 'ProjectCustomField', :label => :label_project_plural},
{:name => 'UserCustomField', :label => :label_user_plural}
tabs = [{:name => 'IssueCustomField', :partial => 'custom_fields/index', :label => :label_issue_plural},
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', :label => :label_spent_time},
{:name => 'ProjectCustomField', :partial => 'custom_fields/index', :label => :label_project_plural},
{:name => 'VersionCustomField', :partial => 'custom_fields/index', :label => :label_version_plural},
{:name => 'UserCustomField', :partial => 'custom_fields/index', :label => :label_user_plural},
{:name => 'GroupCustomField', :partial => 'custom_fields/index', :label => :label_group_plural},
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', :label => TimeEntryActivity::OptionName},
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', :label => IssuePriority::OptionName},
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName}
]
end
@@ -38,7 +43,7 @@ module CustomFieldsHelper
when "text"
text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
when "bool"
check_box_tag(field_name, '1', custom_value.true?, :id => field_id) + hidden_field_tag(field_name, '0')
hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, :id => field_id)
when "list"
blank_option = custom_field.is_required? ?
(custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
@@ -61,6 +66,26 @@ module CustomFieldsHelper
def custom_field_tag_with_label(name, custom_value)
custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
end
def custom_field_tag_for_bulk_edit(custom_field)
field_name = "custom_field_values[#{custom_field.id}]"
field_id = "custom_field_values_#{custom_field.id}"
case custom_field.field_format
when "date"
text_field_tag(field_name, '', :id => field_id, :size => 10) +
calendar_for(field_id)
when "text"
text_area_tag(field_name, '', :id => field_id, :rows => 3, :style => 'width:90%')
when "bool"
select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
[l(:general_text_yes), '1'],
[l(:general_text_no), '0']]), :id => field_id)
when "list"
select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values), :id => field_id)
else
text_field_tag(field_name, '', :id => field_id)
end
end
# Return a string used to display a custom value
def show_value(custom_value)
@@ -75,7 +100,7 @@ module CustomFieldsHelper
when "date"
begin; format_date(value.to_date); rescue; value end
when "bool"
l_YesNo(value == "1")
l(value == "1" ? :general_text_Yes : :general_text_No)
else
value
end

View File

@@ -0,0 +1,34 @@
# Redmine - project management software
# Copyright (C) 2006-2009 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 GroupsHelper
# Options for the new membership projects combo-box
def options_for_membership_project_select(user, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
options << project_tree_options_for_select(projects) do |p|
{:disabled => (user.projects.include?(p))}
end
options
end
def group_settings_tabs
tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general},
{:name => 'users', :partial => 'groups/users', :label => :label_user_plural},
{:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural}
]
end
end

View File

@@ -15,8 +15,6 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'csv'
module IssuesHelper
include ApplicationHelper
@@ -26,20 +24,29 @@ module IssuesHelper
@cached_label_assigned_to ||= l(:field_assigned_to)
@cached_label_priority ||= l(:field_priority)
link_to_issue(issue) + ": #{h(issue.subject)}<br /><br />" +
link_to_issue(issue) + "<br /><br />" +
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
"<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
"<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
end
# Returns a string of css classes that apply to the given issue
def css_issue_classes(issue)
s = "issue status-#{issue.status.position} priority-#{issue.priority.position}"
s << ' closed' if issue.closed?
s << ' overdue' if issue.overdue?
s << ' created-by-me' if User.current.logged? && issue.author_id == User.current.id
s << ' assigned-to-me' if User.current.logged? && issue.assigned_to_id == User.current.id
def render_custom_fields_rows(issue)
return if issue.custom_field_values.empty?
ordered_values = []
half = (issue.custom_field_values.size / 2.0).ceil
half.times do |i|
ordered_values << issue.custom_field_values[i]
ordered_values << issue.custom_field_values[i + half]
end
s = "<tr>\n"
n = 0
ordered_values.compact.each do |value|
s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0
s << "\t<th>#{ h(value.custom_field.name) }:</th><td>#{ simple_format_without_paragraph(h(show_value(value))) }</td>\n"
n += 1
end
s << "</tr>\n"
s
end
@@ -50,6 +57,7 @@ module IssuesHelper
# Project specific queries and global queries
visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
@sidebar_queries = Query.find(:all,
:select => 'id, name',
:order => "name ASC",
:conditions => visible.conditions)
end
@@ -77,14 +85,14 @@ module IssuesHelper
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
e = IssuePriority.find_by_id(detail.value) and value = e.name if detail.value
e = IssuePriority.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
v = Version.find_by_id(detail.value) and value = format_version_name(v) if detail.value
v = Version.find_by_id(detail.old_value) and old_value = format_version_name(v) if detail.old_value
when 'estimated_hours'
value = "%0.02f" % detail.value.to_f unless detail.value.blank?
old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
@@ -121,28 +129,22 @@ module IssuesHelper
case detail.property
when 'attr', 'cf'
if !detail.old_value.blank?
label + " " + l(:text_journal_changed, old_value, value)
l(:text_journal_changed, :label => label, :old => old_value, :new => value)
else
label + " " + l(:text_journal_set_to, value)
l(:text_journal_set_to, :label => label, :value => value)
end
when 'attachment'
"#{label} #{value} #{l(:label_added)}"
l(:text_journal_added, :label => label, :value => value)
end
else
case detail.property
when 'attr', 'cf'
label + " " + l(:text_journal_deleted) + " (#{old_value})"
when 'attachment'
"#{label} #{old_value} #{l(:label_deleted)}"
end
l(:text_journal_deleted, :label => label, :old => old_value)
end
end
def issues_to_csv(issues, project = nil)
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
decimal_separator = l(:general_csv_decimal_separator)
export = StringIO.new
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
headers = [ "#",
l(:field_status),
@@ -192,7 +194,6 @@ module IssuesHelper
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
end
end
export.rewind
export
end
end

View File

@@ -30,7 +30,9 @@ module JournalsHelper
end
content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty?
content << textilizable(journal, :notes)
content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => (editable ? 'wiki editable' : 'wiki'))
css_classes = "wiki"
css_classes << " editable" if editable
content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => css_classes)
end
def link_to_in_place_notes_editor(text, field_id, url, options={})

View File

@@ -19,7 +19,7 @@ module MessagesHelper
def link_to_message(message)
return '' unless message
link_to h(truncate(message.subject, 60)), :controller => 'messages',
link_to h(truncate(message.subject, :length => 60)), :controller => 'messages',
:action => 'show',
:board_id => message.board_id,
:id => message.root,

View File

@@ -18,7 +18,7 @@
module ProjectsHelper
def link_to_version(version, options = {})
return '' unless version && version.is_a?(Version)
link_to h(version.name), { :controller => 'versions', :action => 'show', :id => version }, options
link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, options
end
def project_settings_tabs
@@ -29,8 +29,76 @@ module ProjectsHelper
{:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
{:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
{:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
{:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}
{:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural},
{:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
]
tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
end
def parent_project_select_tag(project)
selected = project.parent
# retrieve the requested parent project
parent_id = (params[:project] && params[:project][:parent_id]) || params[:parent_id]
if parent_id
selected = (parent_id.blank? ? nil : Project.find(parent_id))
end
options = ''
options << "<option value=''></option>" if project.allowed_parents.include?(nil)
options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
content_tag('select', options, :name => 'project[parent_id]')
end
# Renders a tree of projects as a nested set of unordered lists
# The given collection may be a subset of the whole project tree
# (eg. some intermediate nodes are private and can not be seen)
def render_project_hierarchy(projects)
s = ''
if projects.any?
ancestors = []
projects.each do |project|
if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
else
ancestors.pop
s << "</li>"
while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
ancestors.pop
s << "</ul></li>\n"
end
end
classes = (ancestors.empty? ? 'root' : 'child')
s << "<li class='#{classes}'><div class='#{classes}'>" +
link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
s << "</div>\n"
ancestors << project
end
s << ("</li></ul>\n" * ancestors.size)
end
s
end
# Returns a set of options for a select field, grouped by project.
def version_options_for_select(versions, selected=nil)
grouped = Hash.new {|h,k| h[k] = []}
versions.each do |version|
grouped[version.project.name] << [version.name, version.id]
end
# Add in the selected
if selected && !versions.include?(selected)
grouped[selected.project.name] << [selected.name, selected.id]
end
if grouped.keys.size > 1
grouped_options_for_select(grouped, selected && selected.id)
else
options_for_select((grouped.values.first || []), selected && selected.id)
end
end
def format_version_sharing(sharing)
sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing)
l("label_version_sharing_#{sharing}")
end
end

View File

@@ -28,28 +28,37 @@ module QueriesHelper
end
def column_content(column, issue)
if column.is_a?(QueryCustomFieldColumn)
cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
show_value(cv)
else
value = issue.send(column.name)
if value.is_a?(Date)
format_date(value)
elsif value.is_a?(Time)
format_time(value)
value = column.value(issue)
case value.class.name
when 'String'
if column.name == :subject
link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
else
case column.name
when :subject
h((!@project.nil? && @project != issue.project) ? "#{issue.project.name} - " : '') +
link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
when :done_ratio
progress_bar(value, :width => '80px')
when :fixed_version
link_to(h(value), { :controller => 'versions', :action => 'show', :id => issue.fixed_version_id })
else
h(value)
end
h(value)
end
when 'Time'
format_time(value)
when 'Date'
format_date(value)
when 'Fixnum', 'Float'
if column.name == :done_ratio
progress_bar(value, :width => '80px')
else
value.to_s
end
when 'User'
link_to_user value
when 'Project'
link_to(h(value), :controller => 'projects', :action => 'show', :id => value)
when 'Version'
link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
when 'TrueClass'
l(:general_text_Yes)
when 'FalseClass'
l(:general_text_No)
else
h(value)
end
end
end

View File

@@ -121,7 +121,7 @@ module RepositoriesHelper
def repository_field_tags(form, repository)
method = repository.class.name.demodulize.underscore + "_field_tags"
send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method)
send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method) && method != 'repository_field_tags'
end
def scm_select_tag(repository)
@@ -147,7 +147,7 @@ module RepositoriesHelper
def subversion_field_tags(form, repository)
content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
'<br />(http://, https://, svn://, file:///)') +
'<br />(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
content_tag('p', form.text_field(:login, :size => 30)) +
content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
:value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),

View File

@@ -27,8 +27,9 @@ module SearchHelper
result << '...'
break
end
words = words.mb_chars
if i.even?
result << h(words.length > 100 ? "#{words[0..44]} ... #{words[-45..-1]}" : words)
result << h(words.length > 100 ? "#{words.slice(0..44)} ... #{words.slice(-45..-1)}" : words)
else
t = (tokens.index(words.downcase) || 0) % 4
result << content_tag('span', h(words), :class => "highlight token-#{t}")
@@ -44,7 +45,7 @@ module SearchHelper
def project_select_tag
options = [[l(:label_project_all), 'all']]
options << [l(:label_my_projects), 'my_projects'] unless User.current.memberships.empty?
options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.active_children.empty?
options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.descendants.active.empty?
options << [@project.name, ''] unless @project.nil?
select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1
end

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -18,12 +18,57 @@
module SettingsHelper
def administration_settings_tabs
tabs = [{:name => 'general', :partial => 'settings/general', :label => :label_general},
{:name => 'display', :partial => 'settings/display', :label => :label_display},
{:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication},
{:name => 'projects', :partial => 'settings/projects', :label => :label_project_plural},
{:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking},
{:name => 'notifications', :partial => 'settings/notifications', :label => l(:field_mail_notification)},
{:name => 'mail_handler', :partial => 'settings/mail_handler', :label => l(:label_incoming_emails)},
{:name => 'notifications', :partial => 'settings/notifications', :label => :field_mail_notification},
{:name => 'mail_handler', :partial => 'settings/mail_handler', :label => :label_incoming_emails},
{:name => 'repositories', :partial => 'settings/repositories', :label => :label_repository_plural}
]
end
def setting_select(setting, choices, options={})
if blank_text = options.delete(:blank)
choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices
end
setting_label(setting, options) +
select_tag("settings[#{setting}]", options_for_select(choices, Setting.send(setting).to_s), options)
end
def setting_multiselect(setting, choices, options={})
setting_values = Setting.send(setting)
setting_values = [] unless setting_values.is_a?(Array)
setting_label(setting, options) +
hidden_field_tag("settings[#{setting}][]", '') +
choices.collect do |choice|
text, value = (choice.is_a?(Array) ? choice : [choice, choice])
content_tag('label',
check_box_tag("settings[#{setting}][]", value, Setting.send(setting).include?(value)) + text.to_s,
:class => 'block'
)
end.join
end
def setting_text_field(setting, options={})
setting_label(setting, options) +
text_field_tag("settings[#{setting}]", Setting.send(setting), options)
end
def setting_text_area(setting, options={})
setting_label(setting, options) +
text_area_tag("settings[#{setting}]", Setting.send(setting), options)
end
def setting_check_box(setting, options={})
setting_label(setting, options) +
hidden_field_tag("settings[#{setting}]", 0) +
check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options)
end
def setting_label(setting, options={})
label = options.delete(:label)
label != false ? content_tag("label", l(label || "setting_#{setting}")) : ''
end
end

View File

@@ -1,11 +1,12 @@
# Helpers to sort tables using clickable column headers.
#
# Author: Stuart Rackham <srackham@methods.co.nz>, March 2005.
# Jean-Philippe Lang, 2009
# License: This source code is released under the MIT license.
#
# - Consecutive clicks toggle the column's sort order.
# - Sort state is maintained by a session hash entry.
# - Icon image identifies sort column and state.
# - CSS classes identify sort column and state.
# - Typically used in conjunction with the Pagination module.
#
# Example code snippets:
@@ -17,7 +18,7 @@
#
# def list
# sort_init 'last_name'
# sort_update
# sort_update %w(first_name last_name)
# @items = Contact.find_all nil, sort_clause
# end
#
@@ -28,7 +29,7 @@
#
# def list
# sort_init 'last_name'
# sort_update
# sort_update %w(first_name last_name)
# @contact_pages, @items = paginate :contacts,
# :order_by => sort_clause,
# :per_page => 10
@@ -45,85 +46,161 @@
# </tr>
# </thead>
#
# - The ascending and descending sort icon images are sort_asc.png and
# sort_desc.png and reside in the application's images directory.
# - Introduces instance variables: @sort_name, @sort_default.
# - Introduces params :sort_key and :sort_order.
# - Introduces instance variables: @sort_default, @sort_criteria
# - Introduces param :sort
#
module SortHelper
# Initializes the default sort column (default_key) and sort order
# (default_order).
module SortHelper
class SortCriteria
def initialize
@criteria = []
end
def available_criteria=(criteria)
unless criteria.is_a?(Hash)
criteria = criteria.inject({}) {|h,k| h[k] = k; h}
end
@available_criteria = criteria
end
def from_param(param)
@criteria = param.to_s.split(',').collect {|s| s.split(':')[0..1]}
normalize!
end
def criteria=(arg)
@criteria = arg
normalize!
end
def to_param
@criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',')
end
def to_sql
sql = @criteria.collect do |k,o|
if s = @available_criteria[k]
(o ? s.to_a : s.to_a.collect {|c| "#{c} DESC"}).join(', ')
end
end.compact.join(', ')
sql.blank? ? nil : sql
end
def add!(key, asc)
@criteria.delete_if {|k,o| k == key}
@criteria = [[key, asc]] + @criteria
normalize!
end
def add(*args)
r = self.class.new.from_param(to_param)
r.add!(*args)
r
end
def first_key
@criteria.first && @criteria.first.first
end
def first_asc?
@criteria.first && @criteria.first.last
end
def empty?
@criteria.empty?
end
private
def normalize!
@criteria ||= []
@criteria = @criteria.collect {|s| s = s.to_a; [s.first, (s.last == false || s.last == 'desc') ? false : true]}
@criteria = @criteria.select {|k,o| @available_criteria.has_key?(k)} if @available_criteria
@criteria.slice!(3)
self
end
end
def sort_name
controller_name + '_' + action_name + '_sort'
end
# Initializes the default sort.
# Examples:
#
# sort_init 'name'
# sort_init 'id', 'desc'
# sort_init ['name', ['id', 'desc']]
# sort_init [['name', 'desc'], ['id', 'desc']]
#
# - default_key is a column attribute name.
# - default_order is 'asc' or 'desc'.
# - name is the name of the session hash entry that stores the sort state,
# defaults to '<controller_name>_sort'.
#
def sort_init(default_key, default_order='asc', name=nil)
@sort_name = name || params[:controller] + params[:action] + '_sort'
@sort_default = {:key => default_key, :order => default_order}
def sort_init(*args)
case args.size
when 1
@sort_default = args.first.is_a?(Array) ? args.first : [[args.first]]
when 2
@sort_default = [[args.first, args.last]]
else
raise ArgumentError
end
end
# Updates the sort state. Call this in the controller prior to calling
# sort_clause.
# sort_keys can be either an array or a hash of allowed keys
def sort_update(sort_keys)
sort_key = params[:sort_key]
sort_key = nil unless (sort_keys.is_a?(Array) ? sort_keys.include?(sort_key) : sort_keys[sort_key])
sort_order = (params[:sort_order] == 'desc' ? 'DESC' : 'ASC')
if sort_key
sort = {:key => sort_key, :order => sort_order}
elsif session[@sort_name]
sort = session[@sort_name] # Previous sort.
else
sort = @sort_default
end
session[@sort_name] = sort
sort_column = (sort_keys.is_a?(Hash) ? sort_keys[sort[:key]] : sort[:key])
@sort_clause = (sort_column.blank? ? nil : "#{sort_column} #{sort[:order]}")
# - criteria can be either an array or a hash of allowed keys
#
def sort_update(criteria)
@sort_criteria = SortCriteria.new
@sort_criteria.available_criteria = criteria
@sort_criteria.from_param(params[:sort] || session[sort_name])
@sort_criteria.criteria = @sort_default if @sort_criteria.empty?
session[sort_name] = @sort_criteria.to_param
end
# Clears the sort criteria session data
#
def sort_clear
session[sort_name] = nil
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()
@sort_clause
@sort_criteria.to_sql
end
# Returns a link which sorts by the named column.
#
# - column is the name of an attribute in the sorted record collection.
# - The optional caption explicitly specifies the displayed link text.
# - A sort icon image is positioned to the right of the sort link.
# - the optional caption explicitly specifies the displayed link text.
# - 2 CSS classes reflect the state of the link: sort and asc or desc
#
def sort_link(column, caption, default_order)
key, order = session[@sort_name][:key], session[@sort_name][:order]
if key == column
if order.downcase == 'asc'
icon = 'sort_asc.png'
css, order = nil, default_order
if column.to_s == @sort_criteria.first_key
if @sort_criteria.first_asc?
css = 'sort asc'
order = 'desc'
else
icon = 'sort_desc.png'
css = 'sort desc'
order = 'asc'
end
else
icon = nil
order = default_order
end
caption = titleize(Inflector::humanize(column)) unless caption
caption = column.to_s.humanize unless caption
sort_options = { :sort_key => column, :sort_order => order }
sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param }
# don't reuse params if filters are present
url_options = params.has_key?(:set_filter) ? sort_options : params.merge(sort_options)
# Add project_id to url_options
url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id)
link_to_remote(caption,
{:update => "content", :url => url_options},
{:href => url_for(url_options)}) +
(icon ? nbsp(2) + image_tag(icon) : '')
{:update => "content", :url => url_options, :method => :get},
{:href => url_for(url_options),
:class => css})
end
# Returns a table header <th> tag with a sort link for the named column
@@ -139,30 +216,11 @@ module SortHelper
#
# <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %>
#
# Renders:
#
# <th title="Sort by contact ID" width="40">
# <a href="/contact/list?sort_order=desc&amp;sort_key=id">Id</a>
# &nbsp;&nbsp;<img alt="Sort_asc" src="/images/sort_asc.png" />
# </th>
#
def sort_header_tag(column, options = {})
caption = options.delete(:caption) || titleize(Inflector::humanize(column))
caption = options.delete(:caption) || column.to_s.humanize
default_order = options.delete(:default_order) || 'asc'
options[:title]= l(:label_sort_by, "\"#{caption}\"") unless options[:title]
options[:title] = l(:label_sort_by, "\"#{caption}\"") unless options[:title]
content_tag('th', sort_link(column, caption, default_order), options)
end
private
# Return n non-breaking spaces.
def nbsp(n)
'&nbsp;' * n
end
# Return capitalized title.
def titleize(title)
title.split.map {|w| w.capitalize }.join(' ')
end
end

View File

@@ -22,14 +22,33 @@ module TimelogHelper
links = []
links << link_to(l(:label_project_all), {:project_id => nil, :issue_id => nil})
links << link_to(h(@project), {:project_id => @project, :issue_id => nil}) if @project
links << link_to_issue(@issue) if @issue
if @issue
if @issue.visible?
links << link_to_issue(@issue, :subject => false)
else
links << "##{@issue.id}"
end
end
breadcrumb links
end
def activity_collection_for_select_options
activities = Enumeration::get_values('ACTI')
# Returns a collection of activities for a select field. time_entry
# is optional and will be used to check if the selected TimeEntryActivity
# is active.
def activity_collection_for_select_options(time_entry=nil, project=nil)
project ||= @project
if project.nil?
activities = TimeEntryActivity.shared.active
else
activities = project.activities
end
collection = []
collection << [ "--- #{l(:actionview_instancetag_blank_option)} ---", '' ] unless activities.detect(&:is_default)
if time_entry && time_entry.activity && !time_entry.activity.active?
collection << [ "--- #{l(:actionview_instancetag_blank_option)} ---", '' ]
else
collection << [ "--- #{l(:actionview_instancetag_blank_option)} ---", '' ] unless activities.detect(&:is_default)
end
activities.each { |a| collection << [a.name, a.id] }
collection
end
@@ -68,8 +87,7 @@ module TimelogHelper
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
decimal_separator = l(:general_csv_decimal_separator)
custom_fields = TimeEntryCustomField.find(:all)
export = StringIO.new
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
headers = [l(:field_spent_on),
l(:field_user),
@@ -102,17 +120,26 @@ module TimelogHelper
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
end
end
export.rewind
export
end
def format_criteria_value(criteria, value)
value.blank? ? l(:label_none) : ((k = @available_criterias[criteria][:klass]) ? k.find_by_id(value.to_i) : format_value(value, @available_criterias[criteria][:format]))
if value.blank?
l(:label_none)
elsif k = @available_criterias[criteria][:klass]
obj = k.find_by_id(value.to_i)
if obj.is_a?(Issue)
obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}"
else
obj
end
else
format_value(value, @available_criterias[criteria][:format])
end
end
def report_to_csv(criterias, periods, hours)
export = StringIO.new
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# Column headers
headers = criterias.collect {|criteria| l(@available_criterias[criteria][:label]) }
headers += periods
@@ -131,7 +158,6 @@ module TimelogHelper
row << "%.2f" %total
csv << row
end
export.rewind
export
end

View File

@@ -25,21 +25,16 @@ module UsersHelper
end
# Options for the new membership projects combo-box
def projects_options_for_select(projects)
def options_for_membership_project_select(user, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
projects_by_root = projects.group_by(&:root)
projects_by_root.keys.sort.each do |root|
options << content_tag('option', h(root.name), :value => root.id, :disabled => (!projects.include?(root)))
projects_by_root[root].sort.each do |project|
next if project == root
options << content_tag('option', '&#187; ' + h(project.name), :value => project.id)
end
options << project_tree_options_for_select(projects) do |p|
{:disabled => (user.projects.include?(p))}
end
options
end
def change_status_link(user)
url = {:action => 'edit', :id => user, :page => params[:page], :status => params[:status]}
url = {:controller => 'users', :action => 'edit', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
if user.locked?
link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :post, :class => 'icon icon-unlock'
@@ -54,5 +49,9 @@ module UsersHelper
tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
{:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
]
if Group.all.any?
tabs.insert 1, {:name => 'groups', :partial => 'users/groups', :label => :label_group_plural}
end
tabs
end
end

View File

@@ -16,17 +16,28 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module WatchersHelper
def watcher_tag(object, user)
content_tag("span", watcher_link(object, user), :id => 'watcher')
# Valid options
# * :id - the element id
# * :replace - a string or array of element ids that will be
# replaced
def watcher_tag(object, user, options={:replace => 'watcher'})
id = options[:id]
id ||= options[:replace] if options[:replace].is_a? String
content_tag("span", watcher_link(object, user, options), :id => id)
end
def watcher_link(object, user)
# Valid options
# * :replace - a string or array of element ids that will be
# replaced
def watcher_link(object, user, options={:replace => 'watcher'})
return '' unless user && user.logged? && object.respond_to?('watched_by?')
watched = object.watched_by?(user)
url = {:controller => 'watchers',
:action => (watched ? 'unwatch' : 'watch'),
:object_type => object.class.to_s.underscore,
:object_id => object.id}
:object_id => object.id,
:replace => options[:replace]}
link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
{:url => url},
:href => url_for(url),
@@ -36,6 +47,21 @@ module WatchersHelper
# Returns a comma separated list of users watching the given object
def watchers_list(object)
object.watcher_users.collect {|u| content_tag('span', link_to_user(u), :class => 'user') }.join(",\n")
remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
object.watcher_users.collect do |user|
s = content_tag('span', link_to_user(user), :class => 'user')
if remove_allowed
url = {:controller => 'watchers',
:action => 'destroy',
:object_type => object.class.to_s.underscore,
:object_id => object.id,
:user_id => user}
s += ' ' + link_to_remote(image_tag('delete.png'),
{:url => url},
:href => url_for(url),
:style => "vertical-align: middle")
end
s
end.join(",\n")
end
end

View File

@@ -17,6 +17,19 @@
module WikiHelper
def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
s = ''
pages.select {|p| p.parent == parent}.each do |page|
attrs = "value='#{page.id}'"
attrs << " selected='selected'" if selected == page
indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : nil
s << "<option value='#{page.id}'>#{indent}#{h page.pretty_title}</option>\n" +
wiki_page_options_for_select(pages, selected, page, level + 1)
end
s
end
def html_diff(wdiff)
words = wdiff.words.collect{|word| h(word)}
words_add = 0
@@ -36,7 +49,7 @@ module WikiHelper
words_add += 1
else
del_at = pos unless del_at
deleted << ' ' + change[2]
deleted << ' ' + h(change[2])
words_del += 1
end
end

View File

@@ -46,7 +46,9 @@ class Attachment < ActiveRecord::Base
@@storage_path = "#{RAILS_ROOT}/files"
def validate
errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
if self.filesize > Setting.attachment_max_size.to_i.kilobytes
errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
end
end
def file=(incoming_file)
@@ -56,6 +58,9 @@ class Attachment < ActiveRecord::Base
self.filename = sanitize_filename(@temp_file.original_filename)
self.disk_filename = Attachment.disk_filename(filename)
self.content_type = @temp_file.content_type.to_s.chomp
if content_type.blank?
self.content_type = Redmine::MimeType.of(filename)
end
self.filesize = @temp_file.size
end
end
@@ -65,14 +70,20 @@ class Attachment < ActiveRecord::Base
nil
end
# Copy temp file to its final location
# Copies the temporary file to its final location
# and computes its MD5 hash
def before_save
if @temp_file && (@temp_file.size > 0)
logger.debug("saving '#{self.diskfile}'")
md5 = Digest::MD5.new
File.open(diskfile, "wb") do |f|
f.write(@temp_file.read)
buffer = ""
while (buffer = @temp_file.read(8192))
f.write(buffer)
md5.update(buffer)
end
end
self.digest = self.class.digest(diskfile)
self.digest = md5.hexdigest
end
# Don't save the content type if it's longer than the authorized length
if self.content_type && self.content_type.length > 255
@@ -118,6 +129,11 @@ class Attachment < ActiveRecord::Base
self.filename =~ /\.(patch|diff)$/i
end
# Returns true if the file is readable
def readable?
File.readable?(diskfile)
end
private
def sanitize_filename(value)
# get only the filename, not the whole path
@@ -141,11 +157,4 @@ private
end
df
end
# Returns the MD5 digest of the file at given path
def self.digest(filename)
File.open(filename, 'rb') do |f|
Digest::MD5.hexdigest(f.read)
end
end
end

View File

@@ -26,4 +26,25 @@ class Board < ActiveRecord::Base
validates_presence_of :name, :description
validates_length_of :name, :maximum => 30
validates_length_of :description, :maximum => 255
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_messages, project)
end
def to_s
name
end
def reset_counters!
self.class.reset_counters!(id)
end
# Updates topics_count, messages_count and last_message_id attributes for +board_id+
def self.reset_counters!(board_id)
board_id = board_id.to_i
update_all("topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id} AND parent_id IS NULL)," +
" messages_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id})," +
" last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})",
["id = ?", board_id])
end
end

View File

@@ -23,10 +23,10 @@ class Changeset < ActiveRecord::Base
has_many :changes, :dependent => :delete_all
has_and_belongs_to_many :issues
acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.comments.blank? ? '' : (': ' + o.comments))},
:description => :comments,
acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
:description => :long_comments,
:datetime => :committed_on,
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.revision}}
acts_as_searchable :columns => 'comments',
:include => {:repository => :project},
@@ -35,7 +35,7 @@ class Changeset < ActiveRecord::Base
acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
:author_key => :user_id,
:find_options => {:include => {:repository => :project}}
:find_options => {:include => [:user, {:repository => :project}]}
validates_presence_of :repository_id, :revision, :committed_on, :commit_date
validates_uniqueness_of :revision, :scope => :repository_id
@@ -89,7 +89,7 @@ class Changeset < ActiveRecord::Base
if ref_keywords.delete('*')
# find any issue ID in the comments
target_issue_ids = []
comments.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
comments.scan(%r{([\s\(\[,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
end
@@ -109,11 +109,12 @@ class Changeset < ActiveRecord::Base
if self.scmid && (! (csettext =~ /^r[0-9]+$/))
csettext = "commit:\"#{self.scmid}\""
end
journal = issue.init_journal(user || User.anonymous, l(:text_status_changed_by_changeset, csettext))
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
issue.status = fix_status
issue.done_ratio = done_ratio if done_ratio
Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
{ :changeset => self, :issue => issue })
issue.save
Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
end
end
referenced_issues += target_issues
@@ -122,6 +123,14 @@ class Changeset < ActiveRecord::Base
self.issues = referenced_issues.uniq
end
def short_comments
@short_comments || split_comments.first
end
def long_comments
@long_comments || split_comments.last
end
# Returns the previous changeset
def previous
@previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
@@ -139,6 +148,12 @@ class Changeset < ActiveRecord::Base
private
def split_comments
comments =~ /\A(.+?)\r?\n(.*)$/m
@short_comments = $1 || comments
@long_comments = $2.to_s.strip
return @short_comments, @long_comments
end
def self.to_utf8(str)
return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii

View File

@@ -41,8 +41,6 @@ class CustomField < ActiveRecord::Base
end
def before_validation
# remove empty values
self.possible_values = self.possible_values.collect{|v| v unless v.empty?}.compact
# make sure these fields are not searchable
self.searchable = false if %w(int float date bool).include?(field_format)
true
@@ -50,20 +48,77 @@ class CustomField < ActiveRecord::Base
def validate
if self.field_format == "list"
errors.add(:possible_values, :activerecord_error_blank) if self.possible_values.nil? || self.possible_values.empty?
errors.add(:possible_values, :activerecord_error_invalid) unless self.possible_values.is_a? Array
errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
end
# validate default value
v = CustomValue.new(:custom_field => self.clone, :value => default_value, :customized => nil)
v.custom_field.is_required = false
errors.add(:default_value, :activerecord_error_invalid) unless v.valid?
errors.add(:default_value, :invalid) unless v.valid?
end
# Makes possible_values accept a multiline string
def possible_values=(arg)
if arg.is_a?(Array)
write_attribute(:possible_values, arg.compact.collect(&:strip).select {|v| !v.blank?})
else
self.possible_values = arg.to_s.split(/[\n\r]+/)
end
end
def cast_value(value)
casted = nil
unless value.blank?
case field_format
when 'string', 'text', 'list'
casted = value
when 'date'
casted = begin; value.to_date; rescue; nil end
when 'bool'
casted = (value == '1' ? true : false)
when 'int'
casted = value.to_i
when 'float'
casted = value.to_f
end
end
casted
end
# Returns a ORDER BY clause that can used to sort customized
# objects by their value of the custom field.
# Returns false, if the custom field can not be used for sorting.
def order_statement
case field_format
when 'string', 'text', 'list', 'date', 'bool'
# COALESCE is here to make sure that blank and NULL values are sorted equally
"COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
" WHERE cv_sort.customized_type='#{self.class.customized_class.name}'" +
" AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
" AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
when 'int', 'float'
# Make the database cast values into numeric
# Postgresql will raise an error if a value can not be casted!
# CustomValue validations should ensure that it doesn't occur
"(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" +
" WHERE cv_sort.customized_type='#{self.class.customized_class.name}'" +
" AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
" AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)"
else
nil
end
end
def <=>(field)
position <=> field.position
end
def self.customized_class
self.name =~ /^(.+)CustomField$/
begin; $1.constantize; rescue nil; end
end
# to move in project_custom_field
def self.for_all
find(:all, :conditions => ["is_for_all=?", true], :order => 'position')

View File

@@ -20,7 +20,7 @@ class CustomValue < ActiveRecord::Base
belongs_to :customized, :polymorphic => true
def after_initialize
if custom_field && new_record? && (customized_type.blank? || (customized && customized.new_record?))
if new_record? && custom_field && (customized_type.blank? || (customized && customized.new_record?))
self.value ||= custom_field.default_value
end
end
@@ -30,25 +30,37 @@ class CustomValue < ActiveRecord::Base
self.value == '1'
end
def editable?
custom_field.editable?
end
def required?
custom_field.is_required?
end
def to_s
value.to_s
end
protected
def validate
if value.blank?
errors.add(:value, :activerecord_error_blank) if custom_field.is_required? and value.blank?
errors.add(:value, :blank) if custom_field.is_required? and value.blank?
else
errors.add(:value, :activerecord_error_invalid) unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp)
errors.add(:value, :activerecord_error_too_short) if custom_field.min_length > 0 and value.length < custom_field.min_length
errors.add(:value, :activerecord_error_too_long) if custom_field.max_length > 0 and value.length > custom_field.max_length
errors.add(:value, :invalid) unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp)
errors.add(:value, :too_short, :count => custom_field.min_length) if custom_field.min_length > 0 and value.length < custom_field.min_length
errors.add(:value, :too_long, :count => custom_field.max_length) if custom_field.max_length > 0 and value.length > custom_field.max_length
# Format specific validations
case custom_field.field_format
when 'int'
errors.add(:value, :activerecord_error_not_a_number) unless value =~ /^[+-]?\d+$/
errors.add(:value, :not_a_number) unless value =~ /^[+-]?\d+$/
when 'float'
begin; Kernel.Float(value); rescue; errors.add(:value, :activerecord_error_invalid) end
begin; Kernel.Float(value); rescue; errors.add(:value, :invalid) end
when 'date'
errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/
errors.add(:value, :not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/
when 'list'
errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.include?(value)
errors.add(:value, :inclusion) unless custom_field.possible_values.include?(value)
end
end
end

View File

@@ -17,7 +17,7 @@
class Document < ActiveRecord::Base
belongs_to :project
belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id"
acts_as_attachable :delete_permission => :manage_documents
acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
@@ -29,9 +29,28 @@ class Document < ActiveRecord::Base
validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_documents, project)
end
def after_initialize
if new_record?
self.category ||= Enumeration.default('DCAT')
self.category ||= DocumentCategory.default
end
end
def updated_on
unless @updated_on
a = attachments.find(:first, :order => 'created_on DESC')
@updated_on = (a && a.created_on) || created_on
end
@updated_on
end
# Returns the mail adresses of users that should be notified
def recipients
notified = project.notified_users
notified.reject! {|user| !visible?(user)}
notified.collect(&:mail)
end
end

View File

@@ -0,0 +1,34 @@
# 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 DocumentCategory < Enumeration
has_many :documents, :foreign_key => 'category_id'
OptionName = :enumeration_doc_categories
def option_name
OptionName
end
def objects_count
documents.count
end
def transfer_relations(to)
documents.update_all("category_id = #{to.id}")
end
end

View File

@@ -0,0 +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 DocumentCategoryCustomField < CustomField
def type_name
:enumeration_doc_categories
end
end

View File

@@ -15,19 +15,8 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AWSProjectWithRepository < ActionWebService::Struct
member :id, :int
member :identifier, :string
member :name, :string
member :is_public, :bool
member :repository, Repository
end
class SysApi < ActionWebService::API::Base
api_method :projects_with_repository_enabled,
:expects => [],
:returns => [[AWSProjectWithRepository]]
api_method :repository_created,
:expects => [:string, :string, :string],
:returns => [:int]
class DocumentObserver < ActiveRecord::Observer
def after_create(document)
Mailer.deliver_document_added(document) if Setting.notified_events.include?('document_added')
end
end

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -20,4 +20,19 @@ class EnabledModule < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name, :scope => :project_id
after_create :module_enabled
private
# after_create callback used to do things when a module is enabled
def module_enabled
case name
when 'wiki'
# Create a wiki with a default start page
if project && project.wiki.nil?
Wiki.create(:project => project, :start_page => 'Wiki')
end
end
end
end

View File

@@ -16,46 +16,59 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Enumeration < ActiveRecord::Base
acts_as_list :scope => 'opt = \'#{opt}\''
default_scope :order => "#{Enumeration.table_name}.position ASC"
belongs_to :project
acts_as_list :scope => 'type = \'#{type}\''
acts_as_customizable
acts_as_tree :order => 'position ASC'
before_destroy :check_integrity
validates_presence_of :opt, :name
validates_uniqueness_of :name, :scope => [:opt]
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:type, :project_id]
validates_length_of :name, :maximum => 30
# Single table inheritance would be an option
OPTIONS = {
"IPRI" => {:label => :enumeration_issue_priorities, :model => Issue, :foreign_key => :priority_id},
"DCAT" => {:label => :enumeration_doc_categories, :model => Document, :foreign_key => :category_id},
"ACTI" => {:label => :enumeration_activities, :model => TimeEntry, :foreign_key => :activity_id}
}.freeze
def self.get_values(option)
find(:all, :conditions => {:opt => option}, :order => 'position')
end
def self.default(option)
find(:first, :conditions => {:opt => option, :is_default => true}, :order => 'position')
end
named_scope :shared, :conditions => { :project_id => nil }
named_scope :active, :conditions => { :active => true }
def self.default
# Creates a fake default scope so Enumeration.default will check
# it's type. STI subclasses will automatically add their own
# types to the finder.
if self.descends_from_active_record?
find(:first, :conditions => { :is_default => true, :type => 'Enumeration' })
else
# STI classes are
find(:first, :conditions => { :is_default => true })
end
end
# Overloaded on concrete classes
def option_name
OPTIONS[self.opt][:label]
nil
end
def before_save
if is_default? && is_default_changed?
Enumeration.update_all("is_default = #{connection.quoted_false}", {:opt => opt})
Enumeration.update_all("is_default = #{connection.quoted_false}", {:type => type})
end
end
# Overloaded on concrete classes
def objects_count
OPTIONS[self.opt][:model].count(:conditions => "#{OPTIONS[self.opt][:foreign_key]} = #{id}")
0
end
def in_use?
self.objects_count != 0
end
# Is this enumeration overiding a system level enumeration?
def is_override?
!self.parent.nil?
end
alias :destroy_without_reassign :destroy
@@ -63,7 +76,7 @@ class Enumeration < ActiveRecord::Base
# If a enumeration is specified, objects are reassigned
def destroy(reassign_to = nil)
if reassign_to && reassign_to.is_a?(Enumeration)
OPTIONS[self.opt][:model].update_all("#{OPTIONS[self.opt][:foreign_key]} = #{reassign_to.id}", "#{OPTIONS[self.opt][:foreign_key]} = #{id}")
self.transfer_relations(reassign_to)
end
destroy_without_reassign
end
@@ -73,9 +86,49 @@ class Enumeration < ActiveRecord::Base
end
def to_s; name end
# Returns the Subclasses of Enumeration. Each Subclass needs to be
# required in development mode.
#
# Note: subclasses is protected in ActiveRecord
def self.get_subclasses
@@subclasses[Enumeration]
end
# Does the +new+ Hash override the previous Enumeration?
def self.overridding_change?(new, previous)
if (same_active_state?(new['active'], previous.active)) && same_custom_values?(new,previous)
return false
else
return true
end
end
# Does the +new+ Hash have the same custom values as the previous Enumeration?
def self.same_custom_values?(new, previous)
previous.custom_field_values.each do |custom_value|
if custom_value.value != new["custom_field_values"][custom_value.custom_field_id.to_s]
return false
end
end
return true
end
# Are the new and previous fields equal?
def self.same_active_state?(new, previous)
new = (new == "1" ? true : false)
return new == previous
end
private
def check_integrity
raise "Can't delete enumeration" if self.in_use?
end
end
# Force load the subclasses in development mode
require_dependency 'time_entry_activity'
require_dependency 'document_category'
require_dependency 'issue_priority'

48
app/models/group.rb Normal file
View File

@@ -0,0 +1,48 @@
# Redmine - project management software
# Copyright (C) 2006-2009 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 Group < Principal
has_and_belongs_to_many :users, :after_add => :user_added,
:after_remove => :user_removed
acts_as_customizable
validates_presence_of :lastname
validates_uniqueness_of :lastname, :case_sensitive => false
validates_length_of :lastname, :maximum => 30
def to_s
lastname.to_s
end
def user_added(user)
members.each do |member|
user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
member.member_roles.each do |member_role|
user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
end
user_member.save!
end
end
def user_removed(user)
members.each do |member|
MemberRole.find(:all, :include => :member,
:conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy)
end
end
end

View File

@@ -0,0 +1,22 @@
# Redmine - project management software
# Copyright (C) 2006-2009 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 GroupCustomField < CustomField
def type_name
:label_group_plural
end
end

View File

@@ -22,7 +22,7 @@ class Issue < ActiveRecord::Base
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 :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
has_many :journals, :as => :journalized, :dependent => :destroy
@@ -39,22 +39,38 @@ class Issue < ActiveRecord::Base
:include => [:project, :journals],
# sort by id so that limited eager loading doesn't break with postgresql
:order_column => "#{table_name}.id"
acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
:url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}
acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
:url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
:type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
:author_key => :author_id
DONE_RATIO_OPTIONS = %w(issue_field issue_status)
validates_presence_of :subject, :priority, :project, :tracker, :author, :status
validates_length_of :subject, :maximum => 255
validates_inclusion_of :done_ratio, :in => 0..100
validates_numericality_of :estimated_hours, :allow_nil => true
named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
before_save :update_done_ratio_from_issue_status
after_save :create_journal
# Returns true if usr or current user is allowed to view the issue
def visible?(usr=nil)
(usr || User.current).allowed_to?(:view_issues, self.project)
end
def after_initialize
if new_record?
# set default values for new records only
self.status ||= IssueStatus.default
self.priority ||= Enumeration.default('IPRI')
self.priority ||= IssuePriority.default
end
end
@@ -65,66 +81,133 @@ class Issue < ActiveRecord::Base
def copy_from(arg)
issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
self.attributes = issue.attributes.dup
self.attributes = issue.attributes.dup.except("id", "created_on", "updated_on")
self.custom_values = issue.custom_values.collect {|v| v.clone}
self.status = issue.status
self
end
# Move an issue to a new project and tracker
def move_to(new_project, new_tracker = nil)
# Moves/copies an issue to a new project and tracker
# Returns the moved/copied issue on success, false on failure
def move_to(new_project, new_tracker = nil, options = {})
options ||= {}
issue = options[:copy] ? self.clone : self
transaction do
if new_project && project_id != new_project.id
if new_project && issue.project_id != new_project.id
# delete issue relations
unless Setting.cross_project_issue_relations?
self.relations_from.clear
self.relations_to.clear
issue.relations_from.clear
issue.relations_to.clear
end
# issue is moved to another project
# reassign to the category with same name if any
new_category = category.nil? ? nil : new_project.issue_categories.find_by_name(category.name)
self.category = new_category
self.fixed_version = nil
self.project = new_project
new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
issue.category = new_category
# Keep the fixed_version if it's still valid in the new_project
unless new_project.shared_versions.include?(issue.fixed_version)
issue.fixed_version = nil
end
issue.project = new_project
end
if new_tracker
self.tracker = new_tracker
issue.tracker = new_tracker
end
if save
# Manually update project_id on related time entries
TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
if options[:copy]
issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
issue.status = if options[:attributes] && options[:attributes][:status_id]
IssueStatus.find_by_id(options[:attributes][:status_id])
else
self.status
end
end
# Allow bulk setting of attributes on the issue
if options[:attributes]
issue.attributes = options[:attributes]
end
if issue.save
unless options[:copy]
# Manually update project_id on related time entries
TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
end
else
rollback_db_transaction
Issue.connection.rollback_db_transaction
return false
end
end
return true
return issue
end
def priority_id=(pid)
self.priority = nil
write_attribute(:priority_id, pid)
end
def tracker_id=(tid)
self.tracker = nil
write_attribute(:tracker_id, tid)
result = write_attribute(:tracker_id, tid)
@custom_field_values = nil
result
end
# Overrides attributes= so that tracker_id gets assigned first
def attributes_with_tracker_first=(new_attributes, *args)
return if new_attributes.nil?
new_tracker_id = new_attributes['tracker_id'] || new_attributes[:tracker_id]
if new_tracker_id
self.tracker_id = new_tracker_id
end
send :attributes_without_tracker_first=, new_attributes, *args
end
alias_method_chain :attributes=, :tracker_first
def estimated_hours=(h)
write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
end
def done_ratio
if Issue.use_status_for_done_ratio? && status && status.default_done_ratio?
status.default_done_ratio
else
read_attribute(:done_ratio)
end
end
def self.use_status_for_done_ratio?
Setting.issue_done_ratio == 'issue_status'
end
def self.use_field_for_done_ratio?
Setting.issue_done_ratio == 'issue_field'
end
def validate
if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
errors.add :due_date, :activerecord_error_not_a_date
errors.add :due_date, :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
errors.add :due_date, :greater_than_start_date
end
if start_date && soonest_start && start_date < soonest_start
errors.add :start_date, :activerecord_error_invalid
errors.add :start_date, :invalid
end
if fixed_version
if !assignable_versions.include?(fixed_version)
errors.add :fixed_version_id, :inclusion
elsif reopened? && fixed_version.closed?
errors.add_to_base I18n.t(:error_can_not_reopen_issue_on_closed_version)
end
end
# Checks that the issue can not be added/moved to a disabled tracker
if project && (tracker_id_changed? || project_id_changed?)
unless project.trackers.include?(tracker)
errors.add :tracker_id, :inclusion
end
end
end
def validate_on_create
errors.add :tracker_id, :activerecord_error_invalid unless project.trackers.include?(tracker)
end
def before_create
@@ -134,28 +217,12 @@ class Issue < ActiveRecord::Base
end
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|
next if (@custom_values_before_change[c.custom_field_id]==c.value ||
(@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
@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)
}
@current_journal.save
# Set the done_ratio using the status if that setting is set. This will keep the done_ratios
# even if the user turns off the setting later
def update_done_ratio_from_issue_status
if Issue.use_status_for_done_ratio? && status && status.default_done_ratio?
self.done_ratio = status.default_done_ratio
end
# Save the issue even if the journal is not saved (because empty)
true
end
def after_save
@@ -195,9 +262,21 @@ class Issue < ActiveRecord::Base
self.status.is_closed?
end
# Return true if the issue is being reopened
def reopened?
if !new_record? && status_id_changed?
status_was = IssueStatus.find_by_id(status_id_was)
status_new = IssueStatus.find_by_id(status_id)
if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
return true
end
end
false
end
# Returns true if the issue is overdue
def overdue?
!due_date.nil? && (due_date < Date.today)
!due_date.nil? && (due_date < Date.today) && !status.is_closed?
end
# Users the issue can be assigned to
@@ -205,22 +284,41 @@ class Issue < ActiveRecord::Base
project.assignable_users
end
# Versions that the issue can be assigned to
def assignable_versions
@assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
end
# Returns true if this issue is blocked by another issue that is still open
def blocked?
!relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
end
# Returns an array of status that user is able to apply
def new_statuses_allowed_to(user)
statuses = status.find_new_statuses_allowed_to(user.role_for_project(project), tracker)
statuses = status.find_new_statuses_allowed_to(user.roles_for_project(project), tracker)
statuses << status unless statuses.empty?
statuses.uniq.sort
statuses = statuses.uniq.sort
blocked? ? statuses.reject {|s| s.is_closed?} : statuses
end
# Returns the mail adresses of users that should be notified for the issue
# Returns the mail adresses of users that should be notified
def recipients
recipients = project.recipients
notified = project.notified_users
# Author and assignee are always notified unless they have been locked
recipients << author.mail if author && author.active?
recipients << assigned_to.mail if assigned_to && assigned_to.active?
recipients.compact.uniq
notified << author if author && author.active?
notified << assigned_to if assigned_to && assigned_to.active?
notified.uniq!
# Remove users that can not view the issue
notified.reject! {|user| !visible?(user)}
notified.collect(&:mail)
end
# Returns the total number of hours spent on this issue.
#
# Example:
# spent_hours => 0
# spent_hours => 50
def spent_hours
@spent_hours ||= time_entries.sum(:hours) || 0
end
@@ -249,6 +347,11 @@ class Issue < ActiveRecord::Base
due_date || (fixed_version ? fixed_version.effective_date : nil)
end
# Returns the time scheduled for this issue.
#
# Example:
# Start Date: 2/26/09, End Date: 3/04/09
# duration => 6
def duration
(start_date && due_date) ? due_date - start_date : 0
end
@@ -257,18 +360,56 @@ class Issue < ActiveRecord::Base
@soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
end
def self.visible_by(usr)
with_scope(:find => { :conditions => Project.visible_by(usr) }) do
yield
end
end
def to_s
"#{tracker} ##{id}: #{subject}"
end
# Returns a string of css classes that apply to the issue
def css_classes
s = "issue status-#{status.position} priority-#{priority.position}"
s << ' closed' if closed?
s << ' overdue' if overdue?
s << ' created-by-me' if User.current.logged? && author_id == User.current.id
s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
s
end
# Unassigns issues from +version+ if it's no longer shared with issue's project
def self.update_versions_from_sharing_change(version)
# Update issues assigned to the version
update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
end
# Unassigns issues from versions that are no longer shared
# after +project+ was moved
def self.update_versions_from_hierarchy_change(project)
moved_project_ids = project.self_and_descendants.reload.collect(&:id)
# Update issues of the moved projects and issues assigned to a version of a moved project
Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
end
private
# Update issues so their versions are not pointing to a
# fixed_version that is not shared with the issue's project
def self.update_versions(conditions=nil)
# Only need to update issues with a fixed_version from
# a different project and that is not systemwide shared
Issue.all(:conditions => merge_conditions("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
" AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
" AND #{Version.table_name}.sharing <> 'system'",
conditions),
:include => [:project, :fixed_version]
).each do |issue|
next if issue.project.nil? || issue.fixed_version.nil?
unless issue.project.shared_versions.include?(issue.fixed_version)
issue.init_journal(User.current)
issue.fixed_version = nil
issue.save
end
end
end
# Callback on attachment deletion
def attachment_removed(obj)
journal = init_journal(User.current)
@@ -277,4 +418,28 @@ class Issue < ActiveRecord::Base
:old_value => obj.filename)
journal.save
end
# Saves the changes in a Journal
# Called after_save
def create_journal
if @current_journal
# attributes changes
(Issue.column_names - %w(id description lock_version created_on updated_on)).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|
next if (@custom_values_before_change[c.custom_field_id]==c.value ||
(@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
@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)
}
@current_journal.save
end
end
end

View File

@@ -0,0 +1,22 @@
# 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 IssueObserver < ActiveRecord::Observer
def after_create(issue)
Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
end
end

View File

@@ -0,0 +1,34 @@
# 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 IssuePriority < Enumeration
has_many :issues, :foreign_key => 'priority_id'
OptionName = :enumeration_issue_priorities
def option_name
OptionName
end
def objects_count
issues.count
end
def transfer_relations(to)
issues.update_all("priority_id = #{to.id}")
end
end

View File

@@ -0,0 +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 IssuePriorityCustomField < CustomField
def type_name
:enumeration_issue_priorities
end
end

View File

@@ -23,11 +23,13 @@ class IssueRelation < ActiveRecord::Base
TYPE_DUPLICATES = "duplicates"
TYPE_BLOCKS = "blocks"
TYPE_PRECEDES = "precedes"
TYPE_FOLLOWS = "follows"
TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 },
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 },
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 },
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 },
TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes, :order => 5 }
}.freeze
validates_presence_of :issue_from, :issue_to, :relation_type
@@ -35,11 +37,13 @@ class IssueRelation < ActiveRecord::Base
validates_numericality_of :delay, :allow_nil => true
validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
attr_protected :issue_from_id, :issue_to_id
def validate
if issue_from && issue_to
errors.add :issue_to_id, :activerecord_error_invalid if issue_from_id == issue_to_id
errors.add :issue_to_id, :activerecord_error_not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations?
errors.add_to_base :activerecord_error_circular_dependency if issue_to.all_dependent_issues.include? issue_from
errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations?
errors.add_to_base :circular_dependency if issue_to.all_dependent_issues.include? issue_from
end
end
@@ -52,6 +56,8 @@ class IssueRelation < ActiveRecord::Base
end
def before_save
reverse_if_needed
if TYPE_PRECEDES == relation_type
self.delay ||= 0
else
@@ -76,4 +82,15 @@ class IssueRelation < ActiveRecord::Base
def <=>(relation)
TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
end
private
def reverse_if_needed
if (TYPE_FOLLOWS == relation_type)
issue_tmp = issue_to
self.issue_to = issue_from
self.issue_from = issue_tmp
self.relation_type = TYPE_PRECEDES
end
end
end

View File

@@ -24,6 +24,7 @@ class IssueStatus < ActiveRecord::Base
validates_uniqueness_of :name
validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
def after_save
IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default?
@@ -33,27 +34,49 @@ class IssueStatus < ActiveRecord::Base
def self.default
find(:first, :conditions =>["is_default=?", true])
end
# Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+
def self.update_issue_done_ratios
if Issue.use_status_for_done_ratio?
IssueStatus.find(:all, :conditions => ["default_done_ratio >= 0"]).each do |status|
Issue.update_all(["done_ratio = ?", status.default_done_ratio],
["status_id = ?", status.id])
end
end
return Issue.use_status_for_done_ratio?
end
# Returns an array of all statuses the given role can switch to
# Uses association cache when called more than one time
def new_statuses_allowed_to(role, tracker)
new_statuses = workflows.select {|w| w.role_id == role.id && w.tracker_id == tracker.id}.collect{|w| w.new_status} if role && tracker
new_statuses ? new_statuses.compact.sort{|x, y| x.position <=> y.position } : []
def new_statuses_allowed_to(roles, tracker)
if roles && tracker
role_ids = roles.collect(&:id)
new_statuses = workflows.select {|w| role_ids.include?(w.role_id) && w.tracker_id == tracker.id}.collect{|w| w.new_status}.compact.sort
else
[]
end
end
# Same thing as above but uses a database query
# More efficient than the previous method if called just once
def find_new_statuses_allowed_to(role, tracker)
new_statuses = workflows.find(:all,
:include => :new_status,
:conditions => ["role_id=? and tracker_id=?", role.id, tracker.id]).collect{ |w| w.new_status }.compact if role && tracker
new_statuses ? new_statuses.sort{|x, y| x.position <=> y.position } : []
def find_new_statuses_allowed_to(roles, tracker)
if roles && tracker
workflows.find(:all,
:include => :new_status,
:conditions => { :role_id => roles.collect(&:id),
:tracker_id => tracker.id}).collect{ |w| w.new_status }.compact.sort
else
[]
end
end
def new_status_allowed_to?(status, role, tracker)
status && role && tracker ?
!workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => role.id, :tracker_id => tracker.id}).nil? :
def new_status_allowed_to?(status, roles, tracker)
if status && roles && tracker
!workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => roles.collect(&:id), :tracker_id => tracker.id}).nil?
else
false
end
end
def <=>(status)

View File

@@ -0,0 +1,22 @@
# 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 JournalObserver < ActiveRecord::Observer
def after_create(journal)
Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
end
end

View File

@@ -34,30 +34,70 @@ class MailHandler < ActionMailer::Base
@@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
# Status overridable by default
@@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
@@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false)
super email
end
# Processes incoming emails
# Returns the created object (eg. an issue, a message) or false
def receive(email)
@email = email
@user = User.active.find(:first, :conditions => ["LOWER(mail) = ?", email.from.to_a.first.to_s.strip.downcase])
unless @user
# Unknown user => the email is ignored
# TODO: ability to create the user's account
logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
sender_email = email.from.to_a.first.to_s.strip
# Ignore emails received from the application emission address to avoid hell cycles
if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" if logger && logger.info
return false
end
@user = User.find_by_mail(sender_email)
if @user && !@user.active?
logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" if logger && logger.info
return false
end
if @user.nil?
# Email was submitted by an unknown user
case @@handler_options[:unknown_user]
when 'accept'
@user = User.anonymous
when 'create'
@user = MailHandler.create_user_from_email(email)
if @user
logger.info "MailHandler: [#{@user.login}] account created" if logger && logger.info
Mailer.deliver_account_information(@user, @user.password)
else
logger.error "MailHandler: could not create account for [#{sender_email}]" if logger && logger.error
return false
end
else
# Default behaviour, emails from unknown users are ignored
logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info
return false
end
end
User.current = @user
dispatch
end
private
ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
def dispatch
if m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
receive_issue_update(m[1].to_i)
headers = [email.in_reply_to, email.references].flatten.compact
if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
klass, object_id = $1, $2.to_i
method_name = "receive_#{klass}_reply"
if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
send method_name, object_id
else
# ignoring it
end
elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
receive_issue_reply(m[1].to_i)
elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
receive_message_reply(m[1].to_i)
else
receive_issue
end
@@ -78,18 +118,23 @@ class MailHandler < ActionMailer::Base
project = target_project
tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
priority = (get_keyword(:priority) && IssuePriority.find_by_name(get_keyword(:priority)))
status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
# check permission
raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
unless @@handler_options[:no_permission_check]
raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
end
issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
# check workflow
if status && issue.new_statuses_allowed_to(user).include?(status)
issue.status = status
end
issue.subject = email.subject.chomp.toutf8
issue.description = plain_text_body
issue.subject = email.subject.chomp
if issue.subject.blank?
issue.subject = '(no subject)'
end
# custom fields
issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
if value = get_keyword(c.name, :override => true)
@@ -97,13 +142,12 @@ class MailHandler < ActionMailer::Base
end
h
end
issue.description = cleaned_up_text_body
# add To and Cc as watchers before saving so the watchers can reply to Redmine
add_watchers(issue)
issue.save!
add_attachments(issue)
logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
# add To and Cc as watchers
add_watchers(issue)
# send notification after adding watchers so that they can reply to Redmine
Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
issue
end
@@ -117,17 +161,19 @@ class MailHandler < ActionMailer::Base
end
# Adds a note to an existing issue
def receive_issue_update(issue_id)
def receive_issue_reply(issue_id)
status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
issue = Issue.find_by_id(issue_id)
return unless issue
# check permission
raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
unless @@handler_options[:no_permission_check]
raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
end
# add the note
journal = issue.init_journal(user, plain_text_body)
journal = issue.init_journal(user, cleaned_up_text_body)
add_attachments(issue)
# check workflow
if status && issue.new_statuses_allowed_to(user).include?(status)
@@ -135,10 +181,41 @@ class MailHandler < ActionMailer::Base
end
issue.save!
logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
journal
end
# Reply will be added to the issue
def receive_journal_reply(journal_id)
journal = Journal.find_by_id(journal_id)
if journal && journal.journalized_type == 'Issue'
receive_issue_reply(journal.journalized_id)
end
end
# Receives a reply to a forum message
def receive_message_reply(message_id)
message = Message.find_by_id(message_id)
if message
message = message.root
unless @@handler_options[:no_permission_check]
raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
end
if !message.locked?
reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
:content => cleaned_up_text_body)
reply.author = user
reply.board = message.board
message.children << reply
add_attachments(reply)
reply
else
logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" if logger && logger.info
end
end
end
def add_attachments(obj)
if email.has_attachments?
email.attachments.each do |attachment|
@@ -168,7 +245,7 @@ class MailHandler < ActionMailer::Base
@keywords[attr]
else
@keywords[attr] = begin
if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}:[ \t]*(.+)\s*$/i, '')
if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}[ \t]*:[ \t]*(.+)\s*$/i, '')
$1.strip
elsif !@@handler_options[:issue][attr].blank?
@@handler_options[:issue][attr]
@@ -197,4 +274,43 @@ class MailHandler < ActionMailer::Base
@plain_text_body.strip!
@plain_text_body
end
def cleaned_up_text_body
cleanup_body(plain_text_body)
end
def self.full_sanitizer
@full_sanitizer ||= HTML::FullSanitizer.new
end
# Creates a user account for the +email+ sender
def self.create_user_from_email(email)
addr = email.from_addrs.to_a.first
if addr && !addr.spec.blank?
user = User.new
user.mail = addr.spec
names = addr.name.blank? ? addr.spec.gsub(/@.*$/, '').split('.') : addr.name.split
user.firstname = names.shift
user.lastname = names.join(' ')
user.lastname = '-' if user.lastname.blank?
user.login = user.mail
user.password = ActiveSupport::SecureRandom.hex(5)
user.language = Setting.default_language
user.save ? user : nil
end
end
private
# Removes the email body of text after the truncation configurations.
def cleanup_body(body)
delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
unless delimiters.empty?
regex = Regexp.new("^(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
body = body.gsub(regex, '')
end
body.strip
end
end

View File

@@ -16,11 +16,13 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Mailer < ActionMailer::Base
layout 'mailer'
helper :application
helper :issues
helper :custom_fields
include ActionController::UrlWriter
include Redmine::I18n
def self.default_url_options
h = Setting.host_name
@@ -28,24 +30,38 @@ class Mailer < ActionMailer::Base
{ :host => h, :protocol => Setting.protocol }
end
# Builds a tmail object used to email recipients of the added issue.
#
# Example:
# issue_add(issue) => tmail object
# Mailer.deliver_issue_add(issue) => sends an email to issue recipients
def issue_add(issue)
redmine_headers 'Project' => issue.project.identifier,
'Issue-Id' => issue.id,
'Issue-Author' => issue.author.login
redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
message_id issue
recipients issue.recipients
cc(issue.watcher_recipients - @recipients)
subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
body :issue => issue,
:issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
render_multipart('issue_add', body)
end
# Builds a tmail object used to email recipients of the edited issue.
#
# Example:
# issue_edit(journal) => tmail object
# Mailer.deliver_issue_edit(journal) => sends an email to issue recipients
def issue_edit(journal)
issue = journal.journalized
issue = journal.journalized.reload
redmine_headers 'Project' => issue.project.identifier,
'Issue-Id' => issue.id,
'Issue-Author' => issue.author.login
redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
message_id journal
references issue
@author = journal.user
recipients issue.recipients
# Watchers in cc
@@ -57,6 +73,8 @@ class Mailer < ActionMailer::Base
body :issue => issue,
:journal => journal,
:issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
render_multipart('issue_edit', body)
end
def reminder(user, issues, days)
@@ -66,16 +84,28 @@ class Mailer < ActionMailer::Base
body :issues => issues,
:days => days,
:issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
render_multipart('reminder', body)
end
# Builds a tmail object used to email users belonging to the added document's project.
#
# Example:
# document_added(document) => tmail object
# Mailer.deliver_document_added(document) => sends an email to the document's project recipients
def document_added(document)
redmine_headers 'Project' => document.project.identifier
recipients document.project.recipients
recipients document.recipients
subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
body :document => document,
:document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
render_multipart('document_added', body)
end
# Builds a tmail object used to email recipients of a project when an attachements are added.
#
# Example:
# attachments_added(attachments) => tmail object
# Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients
def attachments_added(attachments)
container = attachments.first.container
added_to = ''
@@ -84,38 +114,97 @@ class Mailer < ActionMailer::Base
when 'Project'
added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
added_to = "#{l(:label_project)}: #{container}"
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
when 'Version'
added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
added_to = "#{l(:label_version)}: #{container.name}"
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
when 'Document'
added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
added_to = "#{l(:label_document)}: #{container.title}"
recipients container.recipients
end
redmine_headers 'Project' => container.project.identifier
recipients container.project.recipients
subject "[#{container.project.name}] #{l(:label_attachment_new)}"
body :attachments => attachments,
:added_to => added_to,
:added_to_url => added_to_url
render_multipart('attachments_added', body)
end
# Builds a tmail object used to email recipients of a news' project when a news item is added.
#
# Example:
# news_added(news) => tmail object
# Mailer.deliver_news_added(news) => sends an email to the news' project recipients
def news_added(news)
redmine_headers 'Project' => news.project.identifier
recipients news.project.recipients
message_id news
recipients news.recipients
subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
body :news => news,
:news_url => url_for(:controller => 'news', :action => 'show', :id => news)
render_multipart('news_added', body)
end
def message_posted(message, recipients)
# Builds a tmail object used to email the recipients of the specified message that was posted.
#
# Example:
# message_posted(message) => tmail object
# Mailer.deliver_message_posted(message) => sends an email to the recipients
def message_posted(message)
redmine_headers 'Project' => message.project.identifier,
'Topic-Id' => (message.parent_id || message.id)
recipients(recipients)
subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}"
message_id message
references message.parent unless message.parent.nil?
recipients(message.recipients)
cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
body :message => message,
:message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
render_multipart('message_posted', body)
end
# Builds a tmail object used to email the recipients of a project of the specified wiki content was added.
#
# Example:
# wiki_content_added(wiki_content) => tmail object
# Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients
def wiki_content_added(wiki_content)
redmine_headers 'Project' => wiki_content.project.identifier,
'Wiki-Page-Id' => wiki_content.page.id
message_id wiki_content
recipients wiki_content.recipients
cc(wiki_content.page.wiki.watcher_recipients - recipients)
subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :page => wiki_content.page.pretty_title)}"
body :wiki_content => wiki_content,
:wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title)
render_multipart('wiki_content_added', body)
end
# Builds a tmail object used to email the recipients of a project of the specified wiki content was updated.
#
# Example:
# wiki_content_updated(wiki_content) => tmail object
# Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients
def wiki_content_updated(wiki_content)
redmine_headers 'Project' => wiki_content.project.identifier,
'Wiki-Page-Id' => wiki_content.page.id
message_id wiki_content
recipients wiki_content.recipients
cc(wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients)
subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :page => wiki_content.page.pretty_title)}"
body :wiki_content => wiki_content,
:wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title),
:wiki_diff_url => url_for(:controller => 'wiki', :action => 'diff', :id => wiki_content.project, :page => wiki_content.page.title, :version => wiki_content.version)
render_multipart('wiki_content_updated', body)
end
# Builds a tmail object used to email the specified user their account information.
#
# Example:
# account_information(user, password) => tmail object
# Mailer.deliver_account_information(user, password) => sends account information to the user
def account_information(user, password)
set_language_if_valid user.language
recipients user.mail
@@ -123,23 +212,35 @@ class Mailer < ActionMailer::Base
body :user => user,
:password => password,
:login_url => url_for(:controller => 'account', :action => 'login')
render_multipart('account_information', body)
end
# Builds a tmail object used to email all active administrators of an account activation request.
#
# Example:
# account_activation_request(user) => tmail object
# Mailer.deliver_account_activation_request(user)=> sends an email to all active administrators
def account_activation_request(user)
# Send the email to all active administrators
recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
subject l(:mail_subject_account_activation_request, Setting.app_title)
body :user => user,
:url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
render_multipart('account_activation_request', body)
end
# A registered user's account was activated by an administrator
# Builds a tmail object used to email the specified user that their account was activated by an administrator.
#
# Example:
# account_activated(user) => tmail object
# Mailer.deliver_account_activated(user) => sends an email to the registered user
def account_activated(user)
set_language_if_valid user.language
recipients user.mail
subject l(:mail_subject_register, Setting.app_title)
body :user => user,
:login_url => url_for(:controller => 'account', :action => 'login')
render_multipart('account_activated', body)
end
def lost_password(token)
@@ -148,6 +249,7 @@ class Mailer < ActionMailer::Base
subject l(:mail_subject_lost_password, Setting.app_title)
body :token => token,
:url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
render_multipart('lost_password', body)
end
def register(token)
@@ -156,6 +258,7 @@ class Mailer < ActionMailer::Base
subject l(:mail_subject_register, Setting.app_title)
body :token => token,
:url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
render_multipart('register', body)
end
def test(user)
@@ -163,6 +266,7 @@ class Mailer < ActionMailer::Base
recipients user.mail
subject 'Redmine test'
body :url => url_for(:controller => 'welcome')
render_multipart('test', body)
end
# Overrides default deliver! method to prevent from sending an email
@@ -171,7 +275,15 @@ class Mailer < ActionMailer::Base
return false if (recipients.nil? || recipients.empty?) &&
(cc.nil? || cc.empty?) &&
(bcc.nil? || bcc.empty?)
super
# Set Message-Id and References
if @message_id_object
mail.message_id = self.class.message_id_for(@message_id_object)
end
if @references_objects
mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
end
super(mail)
end
# Sends reminders to issue assignees
@@ -235,30 +347,57 @@ class Mailer < ActionMailer::Base
super
end
# Renders a message with the corresponding layout
def render_message(method_name, body)
layout = method_name.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
body[:content_for_layout] = render(:file => method_name, :body => body)
ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true)
end
# for the case of plain text only
def body(*params)
value = super(*params)
# Rails 2.3 has problems rendering implicit multipart messages with
# layouts so this method will wrap an multipart messages with
# explicit parts.
#
# https://rails.lighthouseapp.com/projects/8994/tickets/2338-actionmailer-mailer-views-and-content-type
# https://rails.lighthouseapp.com/projects/8994/tickets/1799-actionmailer-doesnt-set-template_format-when-rendering-layouts
def render_multipart(method_name, body)
if Setting.plain_text_mail?
templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}")
unless String === @body or templates.empty?
template = File.basename(templates.first)
@body[:content_for_layout] = render(:file => template, :body => @body)
@body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true)
return @body
end
content_type "text/plain"
body render(:file => "#{method_name}.text.plain.rhtml", :body => body, :layout => 'mailer.text.plain.erb')
else
content_type "multipart/alternative"
part :content_type => "text/plain", :body => render(:file => "#{method_name}.text.plain.rhtml", :body => body, :layout => 'mailer.text.plain.erb')
part :content_type => "text/html", :body => render_message("#{method_name}.text.html.rhtml", body)
end
return value
end
# Makes partial rendering work with Rails 1.2 (retro-compatibility)
def self.controller_path
''
end unless respond_to?('controller_path')
# Returns a predictable Message-Id for the given object
def self.message_id_for(object)
# id + timestamp should reduce the odds of a collision
# as far as we don't send multiple emails for the same object
timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on)
hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}"
host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
host = "#{::Socket.gethostname}.redmine" if host.empty?
"<#{hash}@#{host}>"
end
private
def message_id(object)
@message_id_object = object
end
def references(object)
@references_objects ||= []
@references_objects << object
end
end
# Patch TMail so that message_id is not overwritten
module TMail
class Mail
def add_message_id( fqdn = nil )
self.message_id ||= ::TMail::new_message_id(fqdn)
end
end
end

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -17,26 +17,73 @@
class Member < ActiveRecord::Base
belongs_to :user
belongs_to :role
belongs_to :principal, :foreign_key => 'user_id'
has_many :member_roles, :dependent => :destroy
has_many :roles, :through => :member_roles
belongs_to :project
validates_presence_of :role, :user, :project
validates_presence_of :principal, :project
validates_uniqueness_of :user_id, :scope => :project_id
def validate
errors.add :role_id, :activerecord_error_invalid if role && !role.member?
end
after_destroy :unwatch_from_permission_change
def name
self.user.name
end
alias :base_role_ids= :role_ids=
def role_ids=(arg)
ids = (arg || []).collect(&:to_i) - [0]
# Keep inherited roles
ids += member_roles.select {|mr| !mr.inherited_from.nil?}.collect(&:role_id)
new_role_ids = ids - role_ids
# Add new roles
new_role_ids.each {|id| member_roles << MemberRole.new(:role_id => id) }
# Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy)
member_roles_to_destroy = member_roles.select {|mr| !ids.include?(mr.role_id)}
if member_roles_to_destroy.any?
member_roles_to_destroy.each(&:destroy)
unwatch_from_permission_change
end
end
def <=>(member)
role == member.role ? (user <=> member.user) : (role <=> member.role)
a, b = roles.sort.first, member.roles.sort.first
a == b ? (principal <=> member.principal) : (a <=> b)
end
def deletable?
member_roles.detect {|mr| mr.inherited_from}.nil?
end
def include?(user)
if principal.is_a?(Group)
!user.nil? && user.groups.include?(principal)
else
self.user == user
end
end
def before_destroy
# remove category based auto assignments for this member
IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project.id, user.id]
if user
# remove category based auto assignments for this member
IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project.id, user.id]
end
end
protected
def validate
errors.add_to_base "Role can't be blank" if member_roles.empty? && roles.empty?
end
private
# Unwatch things that the user is no longer allowed to view inside project
def unwatch_from_permission_change
if user
Watcher.prune(:user => user, :project => project)
end
end
end

63
app/models/member_role.rb Normal file
View File

@@ -0,0 +1,63 @@
# Redmine - project management software
# Copyright (C) 2006-2009 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 MemberRole < ActiveRecord::Base
belongs_to :member
belongs_to :role
after_destroy :remove_member_if_empty
after_create :add_role_to_group_users
after_destroy :remove_role_from_group_users
validates_presence_of :role
def validate
errors.add :role_id, :invalid if role && !role.member?
end
def inherited?
!inherited_from.nil?
end
private
def remove_member_if_empty
if member.roles.empty?
member.destroy
end
end
def add_role_to_group_users
if member.principal.is_a?(Group)
member.principal.users.each do |user|
user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
user_member.member_roles << MemberRole.new(:role => role, :inherited_from => id)
user_member.save!
end
end
end
def remove_role_from_group_users
MemberRole.find(:all, :conditions => { :inherited_from => id }).group_by(&:member).each do |member, member_roles|
member_roles.each(&:destroy)
if member && member.user
Watcher.prune(:user => member.user, :project => member.project)
end
end
end
end

View File

@@ -37,32 +37,41 @@ class Message < ActiveRecord::Base
acts_as_watchable
attr_protected :locked, :sticky
validates_presence_of :subject, :content
validates_presence_of :board, :subject, :content
validates_length_of :subject, :maximum => 255
after_create :add_author_as_watcher
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_messages, project)
end
def validate_on_create
# Can not reply to a locked topic
errors.add_to_base 'Topic is locked' if root.locked? && self != root
end
def after_create
board.update_attribute(:last_message_id, self.id)
board.increment! :messages_count
if parent
parent.reload.update_attribute(:last_reply_id, self.id)
else
board.increment! :topics_count
end
board.reset_counters!
end
def after_update
if board_id_changed?
Message.update_all("board_id = #{board_id}", ["id = ? OR parent_id = ?", root.id, root.id])
Board.reset_counters!(board_id_was)
Board.reset_counters!(board_id)
end
end
def after_destroy
# The following line is required so that the previous counter
# updates (due to children removal) are not overwritten
board.reload
board.decrement! :messages_count
board.decrement! :topics_count unless parent
board.reset_counters!
end
def sticky=(arg)
write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0)
end
def sticky?
@@ -81,6 +90,13 @@ class Message < ActiveRecord::Base
usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project)))
end
# Returns the mail adresses of users that should be notified
def recipients
notified = project.notified_users
notified.reject! {|user| !visible?(user)}
notified.collect(&:mail)
end
private
def add_author_as_watcher

View File

@@ -17,14 +17,6 @@
class MessageObserver < ActiveRecord::Observer
def after_create(message)
recipients = []
# send notification to the topic watchers
recipients += message.root.watcher_recipients
# send notification to the board watchers
recipients += message.board.watcher_recipients
# send notification to project members who want to be notified
recipients += message.board.project.recipients
recipients = recipients.compact.uniq
Mailer.deliver_message_posted(message, recipients) if !recipients.empty? && Setting.notified_events.include?('message_posted')
Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted')
end
end

View File

@@ -29,6 +29,17 @@ class News < ActiveRecord::Base
acts_as_activity_provider :find_options => {:include => [:project, :author]},
:author_key => :author_id
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_news, project)
end
# Returns the mail adresses of users that should be notified
def recipients
notified = project.notified_users
notified.reject! {|user| !visible?(user)}
notified.collect(&:mail)
end
# returns latest news for projects visible by user
def self.latest(user = User.current, count = 5)
find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")

View File

@@ -0,0 +1,22 @@
# 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 NewsObserver < ActiveRecord::Observer
def after_create(news)
Mailer.deliver_news_added(news) if Setting.notified_events.include?('news_added')
end
end

43
app/models/principal.rb Normal file
View File

@@ -0,0 +1,43 @@
# Redmine - project management software
# Copyright (C) 2006-2009 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 Principal < ActiveRecord::Base
set_table_name 'users'
has_many :members, :foreign_key => 'user_id', :dependent => :destroy
has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
has_many :projects, :through => :memberships
# Groups and active users
named_scope :active, :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status = 1)"
named_scope :like, lambda {|q|
s = "%#{q.to_s.strip.downcase}%"
{:conditions => ["LOWER(login) LIKE :s OR LOWER(firstname) LIKE :s OR LOWER(lastname) LIKE :s OR LOWER(mail) LIKE :s", {:s => s}],
:order => 'type, login, lastname, firstname, mail'
}
}
def <=>(principal)
if self.class.name == principal.class.name
self.to_s.downcase <=> principal.to_s.downcase
else
# groups after users
principal.class.name <=> self.class.name
end
end
end

View File

@@ -20,8 +20,16 @@ class Project < ActiveRecord::Base
STATUS_ACTIVE = 1
STATUS_ARCHIVED = 9
has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
# Specific overidden Activities
has_many :time_entry_activities
has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
has_many :memberships, :class_name => 'Member'
has_many :member_principals, :class_name => 'Member',
:include => :principal,
:conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
has_many :users, :through => :members
has_many :principals, :through => :member_principals, :source => :principal
has_many :enabled_modules, :dependent => :delete_all
has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
@@ -43,7 +51,7 @@ class Project < ActiveRecord::Base
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
:association_foreign_key => 'custom_field_id'
acts_as_tree :order => "name", :counter_cache => true
acts_as_nested_set :order => 'name', :dependent => :destroy
acts_as_attachable :view_permission => :view_files,
:delete_permission => :manage_files
@@ -61,11 +69,17 @@ class Project < ActiveRecord::Base
validates_length_of :name, :maximum => 30
validates_length_of :homepage, :maximum => 255
validates_length_of :identifier, :in => 1..20
validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
# donwcase letters, digits, dashes but not digits only
validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
# reserved words
validates_exclusion_of :identifier, :in => %w( new )
before_destroy :delete_all_members
named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
named_scope :all_public, { :conditions => { :is_public => true } }
named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
def identifier=(identifier)
super unless identifier_frozen?
@@ -74,21 +88,6 @@ class Project < ActiveRecord::Base
def identifier_frozen?
errors[:identifier].nil? && !(new_record? || identifier.blank?)
end
def issues_with_subprojects(include_subprojects=false)
conditions = nil
if include_subprojects
ids = [id] + child_ids
conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
end
conditions ||= ["#{Project.table_name}.id = ?", id]
# Quick and dirty fix for Rails 2 compatibility
Issue.send(:with_scope, :find => { :conditions => conditions }) do
Version.send(:with_scope, :find => { :conditions => conditions }) do
yield
end
end
end
# returns latest created projects
# non public projects will be returned only if user is a member of those
@@ -96,6 +95,11 @@ class Project < ActiveRecord::Base
find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
end
# Returns a SQL :conditions string used to find all active projects for the specified user.
#
# Examples:
# Projects.visible_by(admin) => "projects.status = 1"
# Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
def self.visible_by(user=nil)
user ||= User.current
if user && user.admin?
@@ -113,12 +117,12 @@ class Project < ActiveRecord::Base
if perm = Redmine::AccessControl.permission(permission)
unless perm.project_module.nil?
# If the permission belongs to a project module, make sure the module is enabled
base_statement << " AND EXISTS (SELECT em.id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}' AND em.project_id=#{Project.table_name}.id)"
base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
end
end
if options[:project]
project_statement = "#{Project.table_name}.id = #{options[:project].id}"
project_statement << " OR #{Project.table_name}.parent_id = #{options[:project].id}" if options[:with_subprojects]
project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
base_statement = "(#{project_statement}) AND (#{base_statement})"
end
if user.admin?
@@ -126,22 +130,74 @@ class Project < ActiveRecord::Base
else
statements << "1=0"
if user.logged?
statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id}
if Role.non_member.allowed_to?(permission) && !options[:member]
statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
end
allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
elsif Role.anonymous.allowed_to?(permission)
# anonymous user allowed on public project
statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
else
# anonymous user is not authorized
if Role.anonymous.allowed_to?(permission) && !options[:member]
# anonymous user allowed on public project
statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
end
end
end
statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
end
# Returns the Systemwide and project specific activities
def activities(include_inactive=false)
if include_inactive
return all_activities
else
return active_activities
end
end
# Will create a new Project specific Activity or update an existing one
#
# This will raise a ActiveRecord::Rollback if the TimeEntryActivity
# does not successfully save.
def update_or_create_time_entry_activity(id, activity_hash)
if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
self.create_time_entry_activity_if_needed(activity_hash)
else
activity = project.time_entry_activities.find_by_id(id.to_i)
activity.update_attributes(activity_hash) if activity
end
end
# Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
#
# This will raise a ActiveRecord::Rollback if the TimeEntryActivity
# does not successfully save.
def create_time_entry_activity_if_needed(activity)
if activity['parent_id']
parent_activity = TimeEntryActivity.find(activity['parent_id'])
activity['name'] = parent_activity.name
activity['position'] = parent_activity.position
if Enumeration.overridding_change?(activity, parent_activity)
project_activity = self.time_entry_activities.create(activity)
if project_activity.new_record?
raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
else
self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
end
end
end
end
# Returns a :conditions SQL string that can be used to find the issues associated with this project.
#
# Examples:
# project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
# project.project_condition(false) => "projects.id = 1"
def project_condition(with_subprojects)
cond = "#{Project.table_name}.id = #{id}"
cond = "(#{cond} OR #{Project.table_name}.parent_id = #{id})" if with_subprojects
cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
cond
end
@@ -164,40 +220,157 @@ class Project < ActiveRecord::Base
self.status == STATUS_ACTIVE
end
# Archives the project and its descendants
def archive
# Archive subprojects if any
children.each do |subproject|
subproject.archive
# Check that there is no issue of a non descendant project that is assigned
# to one of the project or descendant versions
v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
if v_ids.any? && Issue.find(:first, :include => :project,
:conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" +
" AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
return false
end
update_attribute :status, STATUS_ARCHIVED
Project.transaction do
archive!
end
true
end
# Unarchives the project
# All its ancestors must be active
def unarchive
return false if parent && !parent.active?
return false if ancestors.detect {|a| !a.active?}
update_attribute :status, STATUS_ACTIVE
end
def active_children
children.select {|child| child.active?}
# Returns an array of projects the project can be moved to
# by the current user
def allowed_parents
return @allowed_parents if @allowed_parents
@allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
@allowed_parents = @allowed_parents - self_and_descendants
if User.current.allowed_to?(:add_project, nil, :global => true)
@allowed_parents << nil
end
unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
@allowed_parents << parent
end
@allowed_parents
end
# Returns an array of the trackers used by the project and its sub projects
# Sets the parent of the project with authorization check
def set_allowed_parent!(p)
unless p.nil? || p.is_a?(Project)
if p.to_s.blank?
p = nil
else
p = Project.find_by_id(p)
return false unless p
end
end
if p.nil?
if !new_record? && allowed_parents.empty?
return false
end
elsif !allowed_parents.include?(p)
return false
end
set_parent!(p)
end
# Sets the parent of the project
# Argument can be either a Project, a String, a Fixnum or nil
def set_parent!(p)
unless p.nil? || p.is_a?(Project)
if p.to_s.blank?
p = nil
else
p = Project.find_by_id(p)
return false unless p
end
end
if p == parent && !p.nil?
# Nothing to do
true
elsif p.nil? || (p.active? && move_possible?(p))
# Insert the project so that target's children or root projects stay alphabetically sorted
sibs = (p.nil? ? self.class.roots : p.children)
to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
if to_be_inserted_before
move_to_left_of(to_be_inserted_before)
elsif p.nil?
if sibs.empty?
# move_to_root adds the project in first (ie. left) position
move_to_root
else
move_to_right_of(sibs.last) unless self == sibs.last
end
else
# move_to_child_of adds the project in last (ie.right) position
move_to_child_of(p)
end
Issue.update_versions_from_hierarchy_change(self)
true
else
# Can not move to the given target
false
end
end
# Returns an array of the trackers used by the project and its active sub projects
def rolled_up_trackers
@rolled_up_trackers ||=
Tracker.find(:all, :include => :projects,
:select => "DISTINCT #{Tracker.table_name}.*",
:conditions => ["#{Project.table_name}.id = ? OR #{Project.table_name}.parent_id = ?", id, id],
:conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
:order => "#{Tracker.table_name}.position")
end
# Closes open and locked project versions that are completed
def close_completed_versions
Version.transaction do
versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
if version.completed?
version.update_attribute(:status, 'closed')
end
end
end
end
# Returns a scope of the Versions used by the project
def shared_versions
@shared_versions ||=
Version.scoped(:include => :project,
:conditions => "#{Project.table_name}.id = #{id}" +
" OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
" #{Version.table_name}.sharing = 'system'" +
" OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" +
" OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
" OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
"))")
end
# Returns a hash of project users grouped by role
def users_by_role
members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
m.roles.each do |r|
h[r] ||= []
h[r] << m.user
end
h
end
end
# Deletes all project's members
def delete_all_members
me, mr = Member.table_name, MemberRole.table_name
connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
Member.delete_all(['project_id = ?', id])
end
# Users issues can be assigned to
def assignable_users
members.select {|m| m.role.assignable?}.collect {|m| m.user}.sort
members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
end
# Returns the mail adresses of users that should be always notified on project events
@@ -205,6 +378,11 @@ class Project < ActiveRecord::Base
members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
end
# Returns the users that should be notified on project events
def notified_users
members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user}
end
# Returns an array of all custom fields enabled for project issues
# (explictly associated custom fields and custom fields enabled for all projects)
def all_issue_custom_fields
@@ -225,9 +403,13 @@ class Project < ActiveRecord::Base
# Returns a short description of the projects (first lines)
def short_description(length = 255)
description.gsub(/^(.{#{length}}[^\n]*).*$/m, '\1').strip if description
description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
end
# Return true if this project is allowed to do the specified action.
# action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project)
def allows_to?(action)
if action.is_a? Hash
allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
@@ -242,10 +424,14 @@ class Project < ActiveRecord::Base
end
def enabled_module_names=(module_names)
enabled_modules.clear
module_names = [] unless module_names && module_names.is_a?(Array)
module_names.each do |name|
enabled_modules << EnabledModule.new(:name => name.to_s)
if module_names && module_names.is_a?(Array)
module_names = module_names.collect(&:to_s)
# remove disabled modules
enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
# add new modules
module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
else
enabled_modules.clear
end
end
@@ -255,14 +441,183 @@ class Project < ActiveRecord::Base
p.nil? ? nil : p.identifier.to_s.succ
end
protected
def validate
errors.add(parent_id, " must be a root project") if parent and parent.parent
errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
errors.add(:identifier, :activerecord_error_invalid) if !identifier.blank? && identifier.match(/^\d*$/)
# Copies and saves the Project instance based on the +project+.
# Duplicates the source project's:
# * Wiki
# * Versions
# * Categories
# * Issues
# * Members
# * Queries
#
# Accepts an +options+ argument to specify what to copy
#
# Examples:
# project.copy(1) # => copies everything
# project.copy(1, :only => 'members') # => copies members only
# project.copy(1, :only => ['members', 'versions']) # => copies members and versions
def copy(project, options={})
project = project.is_a?(Project) ? project : Project.find(project)
to_be_copied = %w(wiki versions issue_categories issues members queries boards)
to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
Project.transaction do
if save
reload
to_be_copied.each do |name|
send "copy_#{name}", project
end
Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
save
end
end
end
# Copies +project+ and returns the new instance. This will not save
# the copy
def self.copy_from(project)
begin
project = project.is_a?(Project) ? project : Project.find(project)
if project
# clear unique attributes
attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
copy = Project.new(attributes)
copy.enabled_modules = project.enabled_modules
copy.trackers = project.trackers
copy.custom_values = project.custom_values.collect {|v| v.clone}
copy.issue_custom_fields = project.issue_custom_fields
return copy
else
return nil
end
rescue ActiveRecord::RecordNotFound
return nil
end
end
private
# Copies wiki from +project+
def copy_wiki(project)
# Check that the source project has a wiki first
unless project.wiki.nil?
self.wiki ||= Wiki.new
wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
project.wiki.pages.each do |page|
new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
new_wiki_page.content = new_wiki_content
wiki.pages << new_wiki_page
end
end
end
# Copies versions from +project+
def copy_versions(project)
project.versions.each do |version|
new_version = Version.new
new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
self.versions << new_version
end
end
# Copies issue categories from +project+
def copy_issue_categories(project)
project.issue_categories.each do |issue_category|
new_issue_category = IssueCategory.new
new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
self.issue_categories << new_issue_category
end
end
# Copies issues from +project+
def copy_issues(project)
# Stores the source issue id as a key and the copied issues as the
# value. Used to map the two togeather for issue relations.
issues_map = {}
project.issues.each do |issue|
new_issue = Issue.new
new_issue.copy_from(issue)
# Reassign fixed_versions by name, since names are unique per
# project and the versions for self are not yet saved
if issue.fixed_version
new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
end
# Reassign the category by name, since names are unique per
# project and the categories for self are not yet saved
if issue.category
new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
end
self.issues << new_issue
issues_map[issue.id] = new_issue
end
# Relations after in case issues related each other
project.issues.each do |issue|
new_issue = issues_map[issue.id]
# Relations
issue.relations_from.each do |source_relation|
new_issue_relation = IssueRelation.new
new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
new_issue_relation.issue_to = source_relation.issue_to
end
new_issue.relations_from << new_issue_relation
end
issue.relations_to.each do |source_relation|
new_issue_relation = IssueRelation.new
new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
new_issue_relation.issue_from = source_relation.issue_from
end
new_issue.relations_to << new_issue_relation
end
end
end
# Copies members from +project+
def copy_members(project)
project.memberships.each do |member|
new_member = Member.new
new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
# only copy non inherited roles
# inherited roles will be added when copying the group membership
role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
next if role_ids.empty?
new_member.role_ids = role_ids
new_member.project = self
self.members << new_member
end
end
# Copies queries from +project+
def copy_queries(project)
project.queries.each do |query|
new_query = Query.new
new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
new_query.sort_criteria = query.sort_criteria if query.sort_criteria
new_query.project = self
self.queries << new_query
end
end
# Copies boards from +project+
def copy_boards(project)
project.boards.each do |board|
new_board = Board.new
new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
new_board.project = self
self.boards << new_board
end
end
private
def allowed_permissions
@allowed_permissions ||= begin
module_names = enabled_modules.collect {|m| m.name}
@@ -273,4 +628,50 @@ private
def allowed_actions
@actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
end
# Returns all the active Systemwide and project specific activities
def active_activities
overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
if overridden_activity_ids.empty?
return TimeEntryActivity.shared.active
else
return system_activities_and_project_overrides
end
end
# Returns all the Systemwide and project specific activities
# (inactive and active)
def all_activities
overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
if overridden_activity_ids.empty?
return TimeEntryActivity.shared
else
return system_activities_and_project_overrides(true)
end
end
# Returns the systemwide active activities merged with the project specific overrides
def system_activities_and_project_overrides(include_inactive=false)
if include_inactive
return TimeEntryActivity.shared.
find(:all,
:conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
self.time_entry_activities
else
return TimeEntryActivity.shared.active.
find(:all,
:conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
self.time_entry_activities.active
end
end
# Archives subprojects recursively
def archive!
children.each do |subproject|
subproject.send :archive!
end
update_attribute :status, STATUS_ARCHIVED
end
end

View File

@@ -16,26 +16,42 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class QueryColumn
attr_accessor :name, :sortable, :default_order
include GLoc
attr_accessor :name, :sortable, :groupable, :default_order
include Redmine::I18n
def initialize(name, options={})
self.name = name
self.sortable = options[:sortable]
self.groupable = options[:groupable] || false
if groupable == true
self.groupable = name.to_s
end
self.default_order = options[:default_order]
end
def caption
set_language_if_valid(User.current.language)
l("field_#{name}")
end
# Returns true if the column is sortable, otherwise false
def sortable?
!sortable.nil?
end
def value(issue)
issue.send name
end
end
class QueryCustomFieldColumn < QueryColumn
def initialize(custom_field)
self.name = "cf_#{custom_field.id}".to_sym
self.sortable = false
self.sortable = custom_field.order_statement || false
if %w(list date bool int).include?(custom_field.field_format)
self.groupable = custom_field.order_statement
end
self.groupable ||= false
@cf = custom_field
end
@@ -46,13 +62,22 @@ class QueryCustomFieldColumn < QueryColumn
def custom_field
@cf
end
def value(issue)
cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
cv && @cf.cast_value(cv.value)
end
end
class Query < ActiveRecord::Base
class StatementInvalid < ::ActiveRecord::StatementInvalid
end
belongs_to :project
belongs_to :user
serialize :filters
serialize :column_names
serialize :sort_criteria, Array
attr_protected :project_id, :user_id
@@ -65,8 +90,8 @@ class Query < ActiveRecord::Base
"c" => :label_closed_issues,
"!*" => :label_none,
"*" => :label_all,
">=" => '>=',
"<=" => '<=',
">=" => :label_greater_or_equal,
"<=" => :label_less_or_equal,
"<t+" => :label_in_less_than,
">t+" => :label_in_more_than,
"t+" => :label_in,
@@ -93,20 +118,20 @@ class Query < ActiveRecord::Base
cattr_reader :operators_by_filter_type
@@available_columns = [
QueryColumn.new(:project, :sortable => "#{Project.table_name}.name"),
QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc'),
QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
QueryColumn.new(:author),
QueryColumn.new(:assigned_to, :sortable => "#{User.table_name}.lastname"),
QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
QueryColumn.new(:fixed_version, :sortable => "#{Version.table_name}.effective_date", :default_order => 'desc'),
QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
]
cattr_reader :available_columns
@@ -114,7 +139,6 @@ class Query < ActiveRecord::Base
def initialize(attributes = nil)
super attributes
self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
set_language_if_valid(User.current.language)
end
def after_initialize
@@ -124,7 +148,7 @@ class Query < ActiveRecord::Base
def validate
filters.each_key do |field|
errors.add label_for(field), :activerecord_error_blank unless
errors.add label_for(field), :blank unless
# filter requires one or more values
(values_for(field) and !values_for(field).first.blank?) or
# filter doesn't require any value
@@ -147,7 +171,7 @@ class Query < ActiveRecord::Base
@available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
"tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
"priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI'], :order => 'position').collect{|s| [s.name, s.id.to_s] } },
"priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
"subject" => { :type => :text, :order => 8 },
"created_on" => { :type => :date_past, :order => 9 },
"updated_on" => { :type => :date_past, :order => 10 },
@@ -162,21 +186,26 @@ class Query < ActiveRecord::Base
user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
else
# members of the user's projects
# OPTIMIZE: Is selecting from users per project (N+1)
user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
end
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
@available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
if User.current.logged?
@available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
end
if project
# project specific filters
unless @project.issue_categories.empty?
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
end
unless @project.versions.empty?
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
unless @project.shared_versions.empty?
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
end
unless @project.active_children.empty?
@available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
unless @project.descendants.active.empty?
@available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
end
add_custom_fields_filters(@project.all_issue_custom_fields)
else
@@ -203,7 +232,7 @@ class Query < ActiveRecord::Base
def add_short_filter(field, expression)
return unless expression
parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
add_filter field, (parms[0] || "="), [parms[1] || ""]
end
@@ -229,10 +258,15 @@ class Query < ActiveRecord::Base
@available_columns = Query.available_columns
@available_columns += (project ?
project.all_issue_custom_fields :
IssueCustomField.find(:all, :conditions => {:is_for_all => true})
IssueCustomField.find(:all)
).collect {|cf| QueryCustomFieldColumn.new(cf) }
end
# Returns an array of columns that can be used to group the results
def groupable_columns
available_columns.select {|c| c.groupable}
end
def columns
if has_default_columns?
available_columns.select do |c|
@@ -246,8 +280,14 @@ class Query < ActiveRecord::Base
end
def column_names=(names)
names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
if names
names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
# Set column_names to nil if default columns
if names.map(&:to_s) == Setting.issue_list_default_columns
names = nil
end
end
write_attribute(:column_names, names)
end
@@ -259,9 +299,52 @@ class Query < ActiveRecord::Base
column_names.nil? || column_names.empty?
end
def sort_criteria=(arg)
c = []
if arg.is_a?(Hash)
arg = arg.keys.sort.collect {|k| arg[k]}
end
c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
write_attribute(:sort_criteria, c)
end
def sort_criteria
read_attribute(:sort_criteria) || []
end
def sort_criteria_key(arg)
sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
end
def sort_criteria_order(arg)
sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
end
# Returns the SQL sort order that should be prepended for grouping
def group_by_sort_order
if grouped? && (column = group_by_column)
column.sortable.is_a?(Array) ?
column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
"#{column.sortable} #{column.default_order}"
end
end
# Returns true if the query is a grouped query
def grouped?
!group_by.blank?
end
def group_by_column
groupable_columns.detect {|c| c.name.to_s == group_by}
end
def group_by_statement
group_by_column.groupable
end
def project_statement
project_clauses = []
if project && !@project.active_children.empty?
if project && !@project.descendants.active.empty?
ids = [project.id]
if has_filter?("subproject_id")
case operator_for("subproject_id")
@@ -272,10 +355,10 @@ class Query < ActiveRecord::Base
# main project only
else
# all subprojects
ids += project.child_ids
ids += project.descendants.collect(&:id)
end
elsif Setting.display_subprojects_issues?
ids += project.child_ids
ids += project.descendants.collect(&:id)
end
project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
elsif project
@@ -292,42 +375,108 @@ class Query < ActiveRecord::Base
next if field == "subproject_id"
v = values_for(field).clone
next unless v and !v.empty?
operator = operator_for(field)
# "me" value subsitution
if %w(assigned_to_id author_id watcher_id).include?(field)
v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
end
sql = ''
is_custom_filter = false
if field =~ /^cf_(\d+)$/
# custom field
db_table = CustomValue.table_name
db_field = 'value'
is_custom_filter = true
sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
elsif field == 'watcher_id'
db_table = Watcher.table_name
db_field = 'user_id'
sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
else
# regular field
db_table = Issue.table_name
db_field = field
sql << '('
sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
end
# "me" value subsitution
if %w(assigned_to_id author_id).include?(field)
v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
end
sql = sql + sql_for_field(field, v, db_table, db_field, is_custom_filter)
sql << ')'
filters_clauses << sql
end if filters and valid?
(filters_clauses << project_statement).join(' AND ')
end
# Returns the issue count
def issue_count
Issue.count(:include => [:status, :project], :conditions => statement)
rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
end
# Returns the issue count by group or nil if query is not grouped
def issue_count_by_group
r = nil
if grouped?
begin
# Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
rescue ActiveRecord::RecordNotFound
r = {nil => issue_count}
end
c = group_by_column
if c.is_a?(QueryCustomFieldColumn)
r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
end
end
r
rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
end
# Returns the issues
# Valid options are :order, :offset, :limit, :include, :conditions
def issues(options={})
order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
order_option = nil if order_option.blank?
Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
:conditions => Query.merge_conditions(statement, options[:conditions]),
:order => order_option,
:limit => options[:limit],
:offset => options[:offset]
rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
end
# Returns the journals
# Valid options are :order, :offset, :limit
def journals(options={})
Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
:conditions => statement,
:order => options[:order],
:limit => options[:limit],
:offset => options[:offset]
rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
end
# Returns the versions
# Valid options are :conditions
def versions(options={})
Version.find :all, :include => :project,
:conditions => Query.merge_conditions(project_statement, options[:conditions])
rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
end
private
# Helper method to generate the WHERE sql for a +field+ with a +value+
def sql_for_field(field, value, db_table, db_field, is_custom_filter)
# Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
sql = ''
case operator_for field
case operator
when "="
sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
when "!"
@@ -368,9 +517,9 @@ class Query < ActiveRecord::Base
Time.now.at_beginning_of_week
sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
when "~"
sql = "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(value.first)}%'"
sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
when "!~"
sql = "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(value.first)}%'"
sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
end
return sql

View File

@@ -25,7 +25,7 @@ class Repository < ActiveRecord::Base
before_destroy :clear_changesets
# Checks if the SCM is enabled when creating a repository
validate_on_create { |r| r.errors.add(:type, :activerecord_error_invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
# Removes leading and trailing whitespace
def url=(arg)
@@ -62,6 +62,18 @@ class Repository < ActiveRecord::Base
def entries(path=nil, identifier=nil)
scm.entries(path, identifier)
end
def branches
scm.branches
end
def tags
scm.tags
end
def default_branch
scm.default_branch
end
def properties(path, identifier=nil)
scm.properties(path, identifier)
@@ -75,27 +87,39 @@ class Repository < ActiveRecord::Base
scm.diff(path, rev, rev_to)
end
# Default behaviour: we search in cached changesets
def changesets_for_path(path)
path = "/#{path}" unless path.starts_with?('/')
Change.find(:all, :include => {:changeset => :user},
:conditions => ["repository_id = ? AND path = ?", id, path],
:order => "committed_on DESC, #{Changeset.table_name}.id DESC").collect(&:changeset)
end
# Returns a path relative to the url of the repository
def relative_path(path)
path
end
# Finds and returns a revision with a number or the beginning of a hash
def find_changeset_by_name(name)
changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
end
def latest_changeset
@latest_changeset ||= changesets.find(:first)
end
# Returns the latest changesets for +path+
# Default behaviour is to search in cached changesets
def latest_changesets(path, rev, limit=10)
if path.blank?
changesets.find(:all, :include => :user,
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
:limit => limit)
else
changes.find(:all, :include => {:changeset => :user},
:conditions => ["path = ?", path.with_leading_slash],
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
:limit => limit).collect(&:changeset)
end
end
def scan_changesets_for_issue_ids
self.changesets.each(&:scan_comment_for_issue_ids)
end
# Returns an array of committers usernames and associated user_id
def committers
@committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
@@ -172,8 +196,9 @@ class Repository < ActiveRecord::Base
end
def clear_changesets
connection.delete("DELETE FROM changes WHERE changes.changeset_id IN (SELECT changesets.id FROM changesets WHERE changesets.repository_id = #{id})")
connection.delete("DELETE FROM changesets_issues WHERE changesets_issues.changeset_id IN (SELECT changesets.id FROM changesets WHERE changesets.repository_id = #{id})")
connection.delete("DELETE FROM changesets WHERE changesets.repository_id = #{id}")
cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
end
end

View File

@@ -29,42 +29,50 @@ class Repository::Git < Repository
'Git'
end
def changesets_for_path(path)
Change.find(:all, :include => {:changeset => :user},
:conditions => ["repository_id = ? AND path = ?", id, path],
:order => "committed_on DESC, #{Changeset.table_name}.revision DESC").collect(&:changeset)
def branches
scm.branches
end
def fetch_changesets
scm_info = scm.info
if scm_info
# latest revision found in database
db_revision = latest_changeset ? latest_changeset.revision : nil
# latest revision in the repository
scm_revision = scm_info.lastrev.scmid
def tags
scm.tags
end
unless changesets.find_by_scmid(scm_revision)
scm.revisions('', db_revision, nil, :reverse => true) do |revision|
if changesets.find_by_scmid(revision.scmid.to_s).nil?
transaction do
changeset = Changeset.create!(:repository => self,
:revision => revision.identifier,
:scmid => revision.scmid,
:committer => revision.author,
:committed_on => revision.time,
:comments => revision.message)
revision.paths.each do |change|
Change.create!(:changeset => changeset,
:action => change[:action],
:path => change[:path],
:from_path => change[:from_path],
:from_revision => change[:from_revision])
end
end
end
end
end
end
# With SCM's that have a sequential commit numbering, redmine is able to be
# clever and only fetch changesets going forward from the most recent one
# it knows about. However, with git, you never know if people have merged
# commits into the middle of the repository history, so we always have to
# parse the entire log.
def fetch_changesets
# Save ourselves an expensive operation if we're already up to date
return if scm.num_revisions == changesets.count
revisions = scm.revisions('', nil, nil, :all => true)
return if revisions.nil? || revisions.empty?
# Find revisions that redmine knows about already
existing_revisions = changesets.find(:all).map!{|c| c.scmid}
# Clean out revisions that are no longer in git
Changeset.delete_all(["scmid NOT IN (?) AND repository_id = (?)", revisions.map{|r| r.scmid}, self.id])
# Subtract revisions that redmine already knows about
revisions.reject!{|r| existing_revisions.include?(r.scmid)}
# Save the remaining ones to the database
revisions.each{|r| r.save(self)} unless revisions.nil?
end
def latest_changesets(path,rev,limit=10)
revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
return [] if revisions.nil? || revisions.empty?
changesets.find(
:all,
:conditions => [
"scmid IN (?)",
revisions.map!{|c| c.scmid}
],
:order => 'committed_on DESC'
)
end
end

View File

@@ -20,7 +20,7 @@ require 'redmine/scm/adapters/subversion_adapter'
class Repository::Subversion < Repository
attr_protected :root_url
validates_presence_of :url
validates_format_of :url, :with => /^(http|https|svn|svn\+ssh|file):\/\/.+/i
validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i
def scm_adapter
Redmine::Scm::Adapters::SubversionAdapter
@@ -30,8 +30,8 @@ class Repository::Subversion < Repository
'Subversion'
end
def changesets_for_path(path)
revisions = scm.revisions(path)
def latest_changesets(path, rev, limit=10)
revisions = scm.revisions(path, rev, nil, :limit => limit)
revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : []
end
@@ -84,6 +84,6 @@ class Repository::Subversion < Repository
# url = file:///var/svn/foo/bar
# => returns /bar
def relative_url
@relative_url ||= url.gsub(Regexp.new("^#{Regexp.escape(root_url)}"), '')
@relative_url ||= url.gsub(Regexp.new("^#{Regexp.escape(root_url || scm.root_url)}", Regexp::IGNORECASE), '')
end
end

View File

@@ -20,6 +20,7 @@ class Role < ActiveRecord::Base
BUILTIN_NON_MEMBER = 1
BUILTIN_ANONYMOUS = 2
named_scope :givable, { :conditions => "builtin = 0", :order => 'position' }
named_scope :builtin, lambda { |*args|
compare = 'not' if args.first == true
{ :conditions => "#{compare} builtin = 0" }
@@ -27,18 +28,13 @@ class Role < ActiveRecord::Base
before_destroy :check_deletable
has_many :workflows, :dependent => :delete_all do
def copy(role)
raise "Can not copy workflow from a #{role.class}" unless role.is_a?(Role)
raise "Can not copy workflow from/to an unsaved role" if proxy_owner.new_record? || role.new_record?
clear
connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, old_status_id, new_status_id, role_id)" +
" SELECT tracker_id, old_status_id, new_status_id, #{proxy_owner.id}" +
" FROM #{Workflow.table_name}" +
" WHERE role_id = #{role.id}"
def copy(source_role)
Workflow.copy(nil, source_role, nil, proxy_owner)
end
end
has_many :members
has_many :member_roles, :dependent => :destroy
has_many :members, :through => :member_roles
acts_as_list
serialize :permissions, Array
@@ -82,7 +78,11 @@ class Role < ActiveRecord::Base
end
def <=>(role)
position <=> role.position
role ? position <=> role.position : -1
end
def to_s
name
end
# Return true if the role is a builtin role

View File

@@ -140,6 +140,10 @@ class Setting < ActiveRecord::Base
per_page_options.split(%r{[\s,]}).collect(&:to_i).select {|n| n > 0}.sort
end
def self.openid?
Object.const_defined?(:OpenID) && self[:openid].to_i > 0
end
# Checks if settings have changed since the values were read
# and clears the cache hash if it's the case
# Called once per request

View File

@@ -21,25 +21,30 @@ class TimeEntry < ActiveRecord::Base
belongs_to :project
belongs_to :issue
belongs_to :user
belongs_to :activity, :class_name => 'Enumeration', :foreign_key => :activity_id
belongs_to :activity, :class_name => 'TimeEntryActivity', :foreign_key => 'activity_id'
attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
acts_as_customizable
acts_as_event :title => Proc.new {|o| "#{o.user}: #{lwr(:label_f_hour, o.hours)} (#{(o.issue || o.project).event_title})"},
:url => Proc.new {|o| {:controller => 'timelog', :action => 'details', :project_id => o.project}},
acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
:url => Proc.new {|o| {:controller => 'timelog', :action => 'details', :project_id => o.project, :issue_id => o.issue}},
:author => :user,
:description => :comments
acts_as_activity_provider :timestamp => "#{table_name}.created_on",
:author_key => :user_id,
:find_options => {:include => :project}
validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
validates_numericality_of :hours, :allow_nil => true, :message => :activerecord_error_invalid
validates_numericality_of :hours, :allow_nil => true, :message => :invalid
validates_length_of :comments, :maximum => 255, :allow_nil => true
def after_initialize
if new_record? && self.activity.nil?
if default_activity = Enumeration.default('ACTI')
if default_activity = TimeEntryActivity.default
self.activity_id = default_activity.id
end
self.hours = nil if hours == 0
end
end
@@ -48,9 +53,9 @@ class TimeEntry < ActiveRecord::Base
end
def validate
errors.add :hours, :activerecord_error_invalid if hours && (hours < 0 || hours >= 1000)
errors.add :project_id, :activerecord_error_invalid if project.nil?
errors.add :issue_id, :activerecord_error_invalid if (issue_id && !issue) || (issue && project!=issue.project)
errors.add :hours, :invalid if hours && (hours < 0 || hours >= 1000)
errors.add :project_id, :invalid if project.nil?
errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project)
end
def hours=(h)

View File

@@ -0,0 +1,34 @@
# 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 TimeEntryActivity < Enumeration
has_many :time_entries, :foreign_key => 'activity_id'
OptionName = :enumeration_activities
def option_name
OptionName
end
def objects_count
time_entries.count
end
def transfer_relations(to)
time_entries.update_all("activity_id = #{to.id}")
end
end

View File

@@ -0,0 +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 TimeEntryActivityCustomField < CustomField
def type_name
:enumeration_activities
end
end

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -17,6 +17,9 @@
class Token < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :value
before_create :delete_previous_tokens
@@validity_time = 1.day
@@ -36,9 +39,13 @@ class Token < ActiveRecord::Base
private
def self.generate_token_value
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
token_value = ''
40.times { |i| token_value << chars[rand(chars.size-1)] }
token_value
ActiveSupport::SecureRandom.hex(20)
end
# Removes obsolete tokens (same user and action)
def delete_previous_tokens
if user
Token.delete_all(['user_id = ? AND action = ?', user.id, action])
end
end
end

View File

@@ -19,14 +19,8 @@ class Tracker < ActiveRecord::Base
before_destroy :check_integrity
has_many :issues
has_many :workflows, :dependent => :delete_all do
def copy(tracker)
raise "Can not copy workflow from a #{tracker.class}" unless tracker.is_a?(Tracker)
raise "Can not copy workflow from/to an unsaved tracker" if proxy_owner.new_record? || tracker.new_record?
clear
connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, old_status_id, new_status_id, role_id)" +
" SELECT #{proxy_owner.id}, old_status_id, new_status_id, role_id" +
" FROM #{Workflow.table_name}" +
" WHERE tracker_id = #{tracker.id}"
def copy(source_tracker)
Workflow.copy(source_tracker, nil, proxy_owner, nil)
end
end
@@ -49,6 +43,23 @@ class Tracker < ActiveRecord::Base
find(:all, :order => 'position')
end
# Returns an array of IssueStatus that are used
# in the tracker's workflows
def issue_statuses
if @issue_statuses
return @issue_statuses
elsif new_record?
return []
end
ids = Workflow.
connection.select_rows("SELECT DISTINCT old_status_id, new_status_id FROM #{Workflow.table_name} WHERE tracker_id = #{id}").
flatten.
uniq
@issue_statuses = IssueStatus.find_all_by_id(ids).sort
end
private
def check_integrity
raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -17,7 +17,7 @@
require "digest/sha1"
class User < ActiveRecord::Base
class User < Principal
# Account statuses
STATUS_ANONYMOUS = 0
@@ -33,13 +33,13 @@ class User < ActiveRecord::Base
:username => '#{login}'
}
has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
has_many :members, :dependent => :delete_all
has_many :projects, :through => :memberships
has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
:after_remove => Proc.new {|user, group| group.user_removed(user)}
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
has_many :changesets, :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
belongs_to :auth_source
# Active non-anonymous users scope
@@ -50,11 +50,11 @@ class User < ActiveRecord::Base
attr_accessor :password, :password_confirmation
attr_accessor :last_before_login_on
# Prevents unauthorized assignments
attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }
validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
# Login must contain lettres, numbers, underscores only
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
validates_length_of :login, :maximum => 30
@@ -62,7 +62,6 @@ class User < ActiveRecord::Base
validates_length_of :firstname, :lastname, :maximum => 30
validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
validates_length_of :mail, :maximum => 60, :allow_nil => true
validates_length_of :password, :minimum => 4, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true
def before_create
@@ -80,6 +79,19 @@ class User < ActiveRecord::Base
super
end
def identity_url=(url)
if url.blank?
write_attribute(:identity_url, '')
else
begin
write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
rescue OpenIdAuthentication::InvalidOpenId
# Invlaid url, don't save
end
end
self.read_attribute(:identity_url)
end
# Returns the user that matches provided login and password, or nil
def self.try_to_login(login, password)
# Make sure no one can sign in with an empty password
@@ -113,6 +125,19 @@ class User < ActiveRecord::Base
rescue => text
raise text
end
# Returns the user who matches the given autologin +key+ or nil
def self.try_to_autologin(key)
tokens = Token.find_all_by_action_and_value('autologin', key)
# Make sure there's only 1 token that matches the key
if tokens.size == 1
token = tokens.first
if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
token.user.update_attribute(:last_login_on, Time.now)
token.user
end
end
end
# Return user's full name for display
def name(formatter = nil)
@@ -138,6 +163,18 @@ class User < ActiveRecord::Base
def check_password?(clear_password)
User.hash_password(clear_password) == self.hashed_password
end
# Generate and set a random password. Useful for automated user creation
# Based on Token#generate_token_value
#
def random_password
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
password = ''
40.times { |i| password << chars[rand(chars.size-1)] }
self.password = password
self.password_confirmation = password
self
end
def pref
self.preference ||= UserPreference.new(:user => self)
@@ -156,6 +193,12 @@ class User < ActiveRecord::Base
token = self.rss_token || Token.create(:user => self, :action => 'feeds')
token.value
end
# Return user's API key (a 40 chars long string), used to access the API
def api_key
token = self.api_token || self.create_api_token(:action => 'api')
token.value
end
# Return an array of project ids for which the user has explicitly turned mail notifications on
def notified_projects_ids
@@ -174,25 +217,29 @@ class User < ActiveRecord::Base
token && token.user.active? ? token.user : nil
end
def self.find_by_autologin_key(key)
token = Token.find_by_action_and_value('autologin', key)
token && (token.created_on > Setting.autologin.to_i.day.ago) && token.user.active? ? token.user : nil
def self.find_by_api_key(key)
token = Token.find_by_action_and_value('api', key)
token && token.user.active? ? token.user : nil
end
# Makes find_by_mail case-insensitive
def self.find_by_mail(mail)
find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
end
# Sort users by their display names
def <=>(user)
self.to_s.downcase <=> user.to_s.downcase
end
def to_s
name
end
# Returns the current day according to user's time zone
def today
if time_zone.nil?
Date.today
else
Time.now.in_time_zone(time_zone).to_date
end
end
def logged?
true
end
@@ -201,26 +248,30 @@ class User < ActiveRecord::Base
!logged?
end
# Return user's role for project
def role_for_project(project)
# Return user's roles for project
def roles_for_project(project)
roles = []
# No role on archived projects
return nil unless project && project.active?
return roles unless project && project.active?
if logged?
# Find project membership
membership = memberships.detect {|m| m.project_id == project.id}
if membership
membership.role
roles = membership.roles
else
@role_non_member ||= Role.non_member
roles << @role_non_member
end
else
@role_anonymous ||= Role.anonymous
roles << @role_anonymous
end
roles
end
# Return true if the user is a member of project
def member_of?(project)
role_for_project(project).member?
!roles_for_project(project).detect {|role| role.member?}.nil?
end
# Return true if the user is allowed to do the specified action on project
@@ -236,13 +287,16 @@ class User < ActiveRecord::Base
# Admin users are authorized for anything else
return true if admin?
role = role_for_project(project)
return false unless role
role.allowed_to?(action) && (project.is_public? || role.member?)
roles = roles_for_project(project)
return false unless roles
roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
elsif options[:global]
# Admin users are always authorized
return true if admin?
# authorize if user has at least one role that has this permission
roles = memberships.collect {|m| m.role}.uniq
roles = memberships.collect {|m| m.roles}.flatten.uniq
roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
else
false
@@ -257,6 +311,8 @@ class User < ActiveRecord::Base
@current_user ||= User.anonymous
end
# Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
# one anonymous user per database.
def self.anonymous
anonymous_user = AnonymousUser.find(:first)
if anonymous_user.nil?
@@ -266,7 +322,17 @@ class User < ActiveRecord::Base
anonymous_user
end
private
protected
def validate
# Password length validation based on setting
if !password.nil? && password.size < Setting.password_min_length.to_i
errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
end
end
private
# Return password digest
def self.hash_password(clear_password)
Digest::SHA1.hexdigest(clear_password || "")
@@ -287,7 +353,7 @@ class AnonymousUser < User
# Overrides a few properties
def logged?; false end
def admin; false end
def name; 'Anonymous' end
def name(*args); I18n.t(:label_user_anonymous) end
def mail; nil end
def time_zone; nil end
def rss_key; nil end

View File

@@ -17,15 +17,31 @@
class Version < ActiveRecord::Base
before_destroy :check_integrity
after_update :update_issues_from_sharing_change
belongs_to :project
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
acts_as_customizable
acts_as_attachable :view_permission => :view_files,
:delete_permission => :manage_files
VERSION_STATUSES = %w(open locked closed)
VERSION_SHARINGS = %w(none descendants hierarchy tree system)
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 60
validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => 'activerecord_error_not_a_date', :allow_nil => true
validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
validates_inclusion_of :status, :in => VERSION_STATUSES
validates_inclusion_of :sharing, :in => VERSION_SHARINGS
named_scope :open, :conditions => {:status => 'open'}
named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
# Returns true if +user+ or current user is allowed to view the version
def visible?(user=User.current)
user.allowed_to?(:view_issues, self.project)
end
def start_date
effective_date
@@ -45,26 +61,37 @@ class Version < ActiveRecord::Base
@spent_hours ||= TimeEntry.sum(:hours, :include => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
end
def closed?
status == 'closed'
end
def open?
status == 'open'
end
# Returns true if the version is completed: due date reached and no open issues
def completed?
effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
end
# Returns the completion percentage of this version based on the amount of open/closed issues
# and the time spent on the open issues.
def completed_pourcent
if fixed_issues.count == 0
if issues_count == 0
0
elsif open_issues_count == 0
100
else
(closed_issues_count * 100 + Issue.sum('done_ratio', :include => 'status', :conditions => ["fixed_version_id = ? AND is_closed = ?", id, false]).to_f) / fixed_issues.count
issues_progress(false) + issues_progress(true)
end
end
# Returns the percentage of issues that have been marked as 'closed'.
def closed_pourcent
if fixed_issues.count == 0
if issues_count == 0
0
else
closed_issues_count * 100.0 / fixed_issues.count
issues_progress(false)
end
end
@@ -73,10 +100,17 @@ class Version < ActiveRecord::Base
effective_date && (effective_date < Date.today) && (open_issues_count > 0)
end
# Returns assigned issues count
def issues_count
@issue_count ||= fixed_issues.count
end
# Returns the total amount of open issues for this version.
def open_issues_count
@open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
end
# Returns the total amount of closed issues for this version.
def closed_issues_count
@closed_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, true], :include => :status)
end
@@ -100,8 +134,76 @@ class Version < ActiveRecord::Base
end
end
# Returns the sharings that +user+ can set the version to
def allowed_sharings(user = User.current)
VERSION_SHARINGS.select do |s|
if sharing == s
true
else
case s
when 'system'
# Only admin users can set a systemwide sharing
user.admin?
when 'hierarchy', 'tree'
# Only users allowed to manage versions of the root project can
# set sharing to hierarchy or tree
project.nil? || user.allowed_to?(:manage_versions, project.root)
else
true
end
end
end
end
private
def check_integrity
raise "Can't delete version" if self.fixed_issues.find(:first)
end
# Update the issue's fixed versions. Used if a version's sharing changes.
def update_issues_from_sharing_change
if sharing_changed?
if VERSION_SHARINGS.index(sharing_was).nil? ||
VERSION_SHARINGS.index(sharing).nil? ||
VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
Issue.update_versions_from_sharing_change self
end
end
end
# Returns the average estimated time of assigned issues
# or 1 if no issue has an estimated time
# Used to weigth unestimated issues in progress calculation
def estimated_average
if @estimated_average.nil?
average = fixed_issues.average(:estimated_hours).to_f
if average == 0
average = 1
end
@estimated_average = average
end
@estimated_average
end
# Returns the total progress of open or closed issues. The returned percentage takes into account
# the amount of estimated time set for this version.
#
# Examples:
# issues_progress(true) => returns the progress percentage for open issues.
# issues_progress(false) => returns the progress percentage for closed issues.
def issues_progress(open)
@issues_progress ||= {}
@issues_progress[open] ||= begin
progress = 0
if issues_count > 0
ratio = open ? 'done_ratio' : 100
done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
:include => :status,
:conditions => ["is_closed = ?", !open]).to_f
progress = done / (estimated_average * issues_count)
end
progress
end
end
end

View File

@@ -0,0 +1,22 @@
# Redmine - project management software
# Copyright (C) 2006-2009 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 VersionCustomField < CustomField
def type_name
:label_version_plural
end
end

View File

@@ -21,10 +21,45 @@ class Watcher < ActiveRecord::Base
validates_presence_of :user
validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id]
# Unwatch things that users are no longer allowed to view
def self.prune(options={})
if options.has_key?(:user)
prune_single_user(options[:user], options)
else
pruned = 0
User.find(:all, :conditions => "id IN (SELECT DISTINCT user_id FROM #{table_name})").each do |user|
pruned += prune_single_user(user, options)
end
pruned
end
end
protected
def validate
errors.add :user_id, :activerecord_error_invalid unless user.nil? || user.active?
errors.add :user_id, :invalid unless user.nil? || user.active?
end
private
def self.prune_single_user(user, options={})
return unless user.is_a?(User)
pruned = 0
find(:all, :conditions => {:user_id => user.id}).each do |watcher|
next if watcher.watchable.nil?
if options.has_key?(:project)
next unless watcher.watchable.respond_to?(:project) && watcher.watchable.project == options[:project]
end
if watcher.watchable.respond_to?(:visible?)
unless watcher.watchable.visible?(user)
watcher.destroy
pruned += 1
end
end
end
pruned
end
end

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 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
@@ -20,9 +20,15 @@ class Wiki < ActiveRecord::Base
has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title'
has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all
acts_as_watchable
validates_presence_of :start_page
validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_wiki_pages, project)
end
# find the page with the given title
# if page doesn't exist, return a new page
def find_or_new_page(title)

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