Compare commits

...
396 Commits
Author SHA1 Message Date
Jean-Philippe Lang 366d46c701 tagged version 0.5.1
git-svn-id: http://redmine.rubyforge.org/svn/tags/0.5.1@605 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-08-12 10:02:32 +00:00
Jean-Philippe Lang 7363428703 Fixed mailer (error when no assignee)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@592 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-15 12:01:53 +00:00
Jean-Philippe Lang 25f8c64f92 Doc update for 0.5.1 release.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@591 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-15 11:44:52 +00:00
Jean-Philippe Lang fd2839c833 Now, when a user turns mail notifications off, he will still receive notifications about issue changes if he is the author or the assignee of the issue.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@590 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-15 11:12:08 +00:00
Jean-Philippe Lang 1cb5fc747f Slight documentation update.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@589 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-15 10:11:25 +00:00
Jean-Philippe Lang 4670426e4e Hours displayed using %.2f on time report.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@588 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-14 19:07:35 +00:00
Jean-Philippe Lang 05c1888703 Fix: SVN commit dates are now stored as local time
git-svn-id: http://redmine.rubyforge.org/svn/trunk@587 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-14 14:53:18 +00:00
Jean-Philippe Lang e94e88e3ad Added the ability to log time when changing an issue status.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@586 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-14 14:37:22 +00:00
Jean-Philippe Lang 9c38458f8b Added an option to choose the date format: language based (as defined in each lang file) or ISO 8601 (YYYY-MM-DD).
This option can be set in Admin -> Settings.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@585 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-14 13:29:20 +00:00
Jean-Philippe Lang 98cf33070f Fixed: Error when editing the wokflow after deleting a status
git-svn-id: http://redmine.rubyforge.org/svn/trunk@584 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-14 11:31:43 +00:00
Jean-Philippe Lang 5e20417e6d Added wiki diff.
Diff can be viewed from the page history, or directly from the project activity page for each edit.
Uses Lars Christensen's diff library.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@583 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-14 11:25:03 +00:00
Jean-Philippe Lang bf74efcd11 Added a margin for inline images in wiki text
git-svn-id: http://redmine.rubyforge.org/svn/trunk@582 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-02 17:23:31 +00:00
Jean-Philippe Lang 384aa50be3 Fixed: issue_id not nullified on time entries when deleting the issue
git-svn-id: http://redmine.rubyforge.org/svn/trunk@581 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-02 15:59:35 +00:00
Jean-Philippe Lang 8687401d65 Fixed: trash icon not visible on files list.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@580 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-07-02 15:32:42 +00:00
Jean-Philippe Lang 7eba6786d3 Changesets stored in the database are now displayed on the repository page even if the repository can not be reached (eg. svnserve down).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@579 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-29 18:50:48 +00:00
Jean-Philippe Lang 65be9d3c86 Removed User#display_name (replaced by User#name).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@578 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-29 17:27:27 +00:00
Jean-Philippe Lang 87bad767c6 Each category can now be associated to a user, so that new issues in that category are automatically assigned to that user.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@577 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-29 17:21:37 +00:00
Jean-Philippe Lang f816d4f378 Fixed: Long text custom fields displayed without line breaks
Added wiki toolbar for issue notes

git-svn-id: http://redmine.rubyforge.org/svn/trunk@576 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-27 19:12:52 +00:00
Jean-Philippe Lang 184be9d0e3 Fixed: time report doesn't take account of the project for time calculations
git-svn-id: http://redmine.rubyforge.org/svn/trunk@575 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-27 18:59:36 +00:00
Jean-Philippe Lang 7eb3b186c4 Fixed an error when trying to delete a project (versions/issues dependency)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@574 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-25 19:17:58 +00:00
Jean-Philippe Lang ba1b857197 Added Darcs basic support.
Files in the repository can not be viewed or downloaded since Darcs doesn't support cat-like command.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@573 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-24 19:30:38 +00:00
Jean-Philippe Lang faa3d984ab Added time report.
Report can be generated by member/activity/tracker/version and year/month/week for the selected period.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@572 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-24 16:07:06 +00:00
Jean-Philippe Lang 6d7a855ca2 Added pagination on wiki page history.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@571 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-23 18:53:45 +00:00
Jean-Philippe Lang 600018d5ad Fixed Iconv::IllegalSequence errors in csv exports
git-svn-id: http://redmine.rubyforge.org/svn/trunk@570 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-23 16:55:17 +00:00
Jean-Philippe Lang ec44c94c12 Fixed an error on welcome screen for users with no membership
git-svn-id: http://redmine.rubyforge.org/svn/trunk@569 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-23 16:36:01 +00:00
Jean-Philippe Lang 48c2a690c2 Don't show "Projects" label if projects list is empty.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@568 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-23 14:10:13 +00:00
Jean-Philippe Lang 1a2aee84b2 Fixed confidentiality issue on account/show.
Only public projects or private projects that the logged in user belongs to are displayed.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@567 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-23 14:06:21 +00:00
Jean-Philippe Lang 1c44600c62 Added per user custom queries.
Any logged in user can now save queries (they are not visible to the other users).
Only users with explicit permission can manage queries that are visible to anyone.
The queries list is removed from the "Reports" view. It can now be accessed from the issues list.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@566 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-23 13:49:29 +00:00
Jean-Philippe Lang 5332c4362c Added page association on versioned WikiContent
git-svn-id: http://redmine.rubyforge.org/svn/trunk@565 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-17 19:32:58 +00:00
Jean-Philippe Lang f2acb56041 A wiki page can now be attached to each version.
For that, edit the version and set the wiki page name (project wiki must be enabled).
The wiki page content is displayed for each version on the roadmap view.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@564 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-14 18:26:27 +00:00
Jean-Philippe Lang ff1343882a Swedish mail templates added (Thomas Habets)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@563 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-13 16:56:37 +00:00
Jean-Philippe Lang 2dbb397818 Fixed version field on issue view page now links to the corresponding version in the roadmap.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@562 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-12 23:07:00 +00:00
Jean-Philippe Lang 136a2a614b Applied this fix http://dev.rubyonrails.org/ticket/4967 to solve namespaced models dependencies problem.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@561 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-12 23:03:38 +00:00
Jean-Philippe Lang a681d8bf4d Added an error message when trying to create an issue with no default status defined.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@560 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-12 22:31:05 +00:00
Jean-Philippe Lang 438161ad1f Added basic support for CVS and Mercurial SCMs.
Browsing, changesets fetching and diff viewing are implemented.
Only tested with local repositories.

Thanks to Ralph Vater for CVS specific code.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@559 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-06-12 20:12:05 +00:00
Jean-Philippe Lang 4dddb606a6 Added :dependent => :delete_all on IssueStatus Workflow association.
Also added compact in find_new_statuses_allowed_to to remove nil statuses.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@558 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-31 17:19:01 +00:00
Jean-Philippe Lang 22c7419a70 Japanese translation updated (Satoru KURASHIKI)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@557 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-30 17:52:24 +00:00
Jean-Philippe Lang 7fb03b1ca3 Fixed: error on csv/pdf export and feeds (oracle)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@556 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-29 22:20:43 +00:00
Jean-Philippe Lang 97f6315bd0 Fixed: SQL error when a non-admin user displays the project list
git-svn-id: http://redmine.rubyforge.org/svn/trunk@555 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-29 19:48:50 +00:00
Jean-Philippe Lang e7ff47cff5 Fixed date query filters (wrong results and sql error with postgresql)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@554 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-29 19:40:48 +00:00
Jean-Philippe Lang 4a20ece43e Added Swedish translation supplied by Thomas Habets.
English email templates suffix removed so that they are used when translated template is not available.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@553 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-29 17:57:10 +00:00
Jean-Philippe Lang b2d4666bf1 Attachments can be displayed inline in Documents
git-svn-id: http://redmine.rubyforge.org/svn/trunk@552 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-28 12:15:19 +00:00
Jean-Philippe Lang 515caa8f37 Fixed: open/closed issue counts are always 0 on reports view (postgresql)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@551 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-28 09:09:37 +00:00
Jean-Philippe Lang 4cf1b68969 Added filename header when sending an image inline
git-svn-id: http://redmine.rubyforge.org/svn/trunk@550 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-27 18:45:48 +00:00
Jean-Philippe Lang 413247ee5b Added the ability to archive projects:
* Only administrators can archive/unarchive projects.
* Once archived, the project is visible on the admin project listing only. It doesn't show up anywhere else in the app. Subprojects are also archived.
* Archive/unarchive preserve everything on the project (issues, members, ...).
* A subproject can not be unarchived if its parent project is archived.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@549 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-27 17:42:04 +00:00
Jean-Philippe Lang 70374d084e Slight views refactoring
git-svn-id: http://redmine.rubyforge.org/svn/trunk@548 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-27 12:38:29 +00:00
Jean-Philippe Lang f335e943ad Password fields hidden on users/add form when selecting an external authentication mode.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@547 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-27 11:57:49 +00:00
Jean-Philippe Lang f04225321c Account information can now be sent to the user when creating an account.
ActionMailer logger set to nil for production environment to disable email contents output in production.log

git-svn-id: http://redmine.rubyforge.org/svn/trunk@546 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-27 10:57:13 +00:00
Jean-Philippe Lang f12315075f Optimistic locking added for wiki edits.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@545 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-26 17:22:27 +00:00
Jean-Philippe Lang c99da15445 Show a 404 error page if attachment can not be read.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@544 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-26 16:57:25 +00:00
Jean-Philippe Lang ef39db234b Added issue count details for versions on Reports view.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@543 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-26 16:06:02 +00:00
Jean-Philippe Lang 8af6d24a36 Fixed "Projects" drop-down menu for IE.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@542 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-26 15:53:15 +00:00
Jean-Philippe Lang f8ef65e8f6 Attachments can now be added to wiki pages (original patch by Pavol Murin). Only authorized users can add/delete attachments.
Attached images can be displayed inline, using textile image tag (for wiki pages, issue descriptions and forum messages).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@541 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-26 15:42:37 +00:00
Jean-Philippe Lang 6446c312be Added the ability to destroy wiki pages (content and its history are deleted from the database).
This permission has to be explicitly given (Roles & Permissions -> Wiki pages/Delete).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@540 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-25 16:44:50 +00:00
Jean-Philippe Lang 4a524ff911 Dutch translation added (Linda van den Brink)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@539 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-24 17:29:11 +00:00
Jean-Philippe Lang 9fe0dd051d User's projects alphabetically sorted in the Projects drop down menu.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@538 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-23 17:18:21 +00:00
Jean-Philippe Lang 3782501275 Fixed a couple of spelling errors (JT Zemp)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@537 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-20 18:11:49 +00:00
Jean-Philippe Lang d34ea9a569 Versions can now be created with no date.
Versions with no date appear at the end of the roadmap, sorted by name.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@536 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-20 17:46:02 +00:00
Jean-Philippe Lang bb1fccb7b7 Fixed: performance issue on RepositoriesController#revisions when a changeset has a great number of changes (eg. 100,000).
Also added pagination for changes on changeset details view.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@535 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-15 21:32:36 +00:00
Jean-Philippe Lang 777c9acae8 "My projects" are now listed under the drop-down "Projects" top menu item (20 projects max.).
The left menu section for "My projects" is removed.
Patch by Damien McKenna (slightly edited).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@534 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-15 17:39:01 +00:00
Jean-Philippe Lang 7b13b58a2f Text search added on messages.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@533 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-14 17:46:58 +00:00
Jean-Philippe Lang b11dd1f213 Reply form on the message view is displayed only if user is logged in.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@532 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-14 17:07:40 +00:00
Jean-Philippe Lang 7ca7e4bad5 Added mail notification when a new message is posted in the forums.
Only users who "watch" the board receive notifications. To watch a board, go to the board and click on the "Watch" link.

Notifications are sent by MessageObserver observer.
GLoc was modified to use the mail template without language suffix when translated template (with language suffix) doesn't exist.
Usefull when there's no hard coded text in the mail tempate.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@531 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-14 17:03:59 +00:00
Jean-Philippe Lang bca5bd9c62 Added watchers for message boards (watchers controller modified to support any watchable model).
No notification yet when a new message is posted.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@530 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-13 19:43:35 +00:00
Jean-Philippe Lang b90e84b9fe Per project forums added.
Permissions for forums management can be set in "Admin -> Roles & Permissions".
Forums can be created on the project settings screen ("Forums" tab).
Once a project has a forum, a "Forums" link appears in the project menu.
For now, posting messages in forums requires to be logged in. Files can be attached to messages.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@529 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-13 17:09:56 +00:00
Jean-Philippe Lang 75582f80f8 Fixed: error when viewing a file diff from a revision view (only if repository url doesn't point to the root of the repository).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@528 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-10 18:39:49 +00:00
Jean-Philippe Lang 270e3a4345 Added "assigned to" field in mail notifications.
Also fixed mail_handler unit test (wrong fixture filename).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@527 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-10 17:46:16 +00:00
Jean-Philippe Lang 0fab627a3c Added some javascript to prevent from selecting the same from/to revision for the diff view.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@526 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-10 17:36:45 +00:00
Jean-Philippe Lang 599e49e4d0 Added the ability to view a file diff with free to/from revision selection.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@525 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-10 17:28:18 +00:00
Jean-Philippe Lang cff3950e6b Added a test for the mail handler.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@524 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-09 19:24:07 +00:00
Jean-Philippe Lang 42193960f2 Added a simple mail handler.
It lets users add notes to an existing issue by replying to the initial notification email.
Permissions are checked in the same way as in the application (the user is identified by its mail address).

Information about configuring the application so that it receives emails can be found here:
http://wiki.rubyonrails.com/rails/pages/HowToReceiveEmailsWithActionMailer

RedMine mail hander is MailHandler#receive

git-svn-id: http://redmine.rubyforge.org/svn/trunk@523 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-09 18:46:43 +00:00
Jean-Philippe Lang 98d08439dc Fixed: files with an apostrophe in their names can't be accessed in the repository
git-svn-id: http://redmine.rubyforge.org/svn/trunk@522 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-09 17:22:15 +00:00
Jean-Philippe Lang 8614c00c8a Text files can now be viewed online when browsing the repository.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@521 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-08 15:49:20 +00:00
Jean-Philippe Lang df0a49ff14 "me" value is now available in queries for "assigned to" and "author" filters.
When executing the query, it is replaced by the currently logged in user.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@520 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-08 12:46:15 +00:00
Jean-Philippe Lang 50429d0819 Fixed the order of the japanese day names
git-svn-id: http://redmine.rubyforge.org/svn/trunk@519 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-08 11:51:20 +00:00
Jean-Philippe Lang 5c88c1f50b Changed the length of 'language' column in users table from 2 to 5, to allow long language codes like pt-br.
Updated portuguese translation (Arthur Zapparoli).
Current pt translation moved to pt-br.


git-svn-id: http://redmine.rubyforge.org/svn/trunk@518 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-08 09:26:57 +00:00
Jean-Philippe Lang d85f5518d9 Removed "Wiki edits" option in the activity view if the project has no wiki.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@517 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-07 16:57:42 +00:00
Jean-Philippe Lang 8e24c6458d Added an option to see all versions in the roadmap view (including completed ones).
On calendar and gantt, versions are now clickable and link to the corresponding entry in the roadmap.

Since calendar and gantt are now cached, don't forget to empty your cache before restarting the application.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@516 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-07 16:54:26 +00:00
Jean-Philippe Lang b748455d96 Added fragment caching for calendar and gantt views
git-svn-id: http://redmine.rubyforge.org/svn/trunk@515 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-06 16:40:33 +00:00
Jean-Philippe Lang 7eb6471559 Added autologin feature (disabled by default).
To enable this feature, go to administration settings and choose a duration for autologin.
When enabled, a checkbox on the login form lets users activate autologin.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@514 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-06 12:49:32 +00:00
Jean-Philippe Lang 56513a8c66 Added a "clear" link when displaying a saved query. It clears the query filter to show all issues.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@513 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-05 17:52:08 +00:00
Jean-Philippe Lang 8b1455c945 Filter values hidden by default to avoid show/hide flashes when loading screen.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@512 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-05 16:10:45 +00:00
Jean-Philippe Lang b9be1d1268 Ajaxified issue filter reset link
git-svn-id: http://redmine.rubyforge.org/svn/trunk@511 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-05 15:59:06 +00:00
Jean-Philippe Lang 71e6f22670 Issue#long_id no more used and removed
git-svn-id: http://redmine.rubyforge.org/svn/trunk@510 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-05 15:55:50 +00:00
Jean-Philippe Lang 0dbbf776c6 Slight modifications on project settings views
git-svn-id: http://redmine.rubyforge.org/svn/trunk@509 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-05 15:35:28 +00:00
Jean-Philippe Lang 3eed7e622c Members management in project settings is now AJAXified
git-svn-id: http://redmine.rubyforge.org/svn/trunk@508 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-05 15:21:18 +00:00
Jean-Philippe Lang a6a181c70c Issue filter no more reseted when clicking 'Issues' menu link
git-svn-id: http://redmine.rubyforge.org/svn/trunk@507 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-05 13:35:33 +00:00
Jean-Philippe Lang 92b02014d2 Issue relations first commit (not thoroughly tested). 4 kinds of relation are available:
* relates to: do nothing special. Just to know that the 2 issues are related...
* duplicates: will close the related issue with the same status when closing the issue (not implemented yet)
* blocks: will require to close the blocking issue before closing the blocked issue (not implemented yet)
* precedes (end to start relation): start date of the related issue depends on the due date of the preceding issue (implemented). A delay can be set so that the related issue can only start n days after the end of the preceding issue. When setting dates for an issue, dates of all downstream issues are set according to these relations.

To set a relation, the 2 issues have to belong to the same project (may change in the future). So if an issue is moved to another project, all its relations are removed.
Circular dependencies are checked when creating a relation.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@506 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-05 13:22:27 +00:00
Jean-Philippe Lang 987e843cd1 Removed alpha transparency from time icon
git-svn-id: http://redmine.rubyforge.org/svn/trunk@505 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-05 10:28:49 +00:00
Jean-Philippe Lang 5050586a9e Changed delete icon
git-svn-id: http://redmine.rubyforge.org/svn/trunk@504 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-05 10:06:13 +00:00
Jean-Philippe Lang da4fd6b71a Login field automatically focused on login form
git-svn-id: http://redmine.rubyforge.org/svn/trunk@503 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-05 09:23:56 +00:00
Jean-Philippe Lang e07c44543f Bulgarian translation added (Nikolay Solakov)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@502 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-02 19:36:00 +00:00
Jean-Philippe Lang 78b5e57a4a Fixed an unicode problem on gantt (first letter of the day name)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@501 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-02 19:13:38 +00:00
Jean-Philippe Lang 10a49d476d HTML select tags are no longer hidden with IE7 when showing navigation drop-down menu.
They are still hidden for IE6 (no z-index support for select tags).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@500 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-02 17:31:42 +00:00
Jean-Philippe Lang 0759212a44 Added fragment caching for svn diffs.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@499 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-01 20:56:19 +00:00
Jean-Philippe Lang 7eda64e464 Issue subjects column width set to 330 on gantt chart
git-svn-id: http://redmine.rubyforge.org/svn/trunk@498 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-01 08:48:24 +00:00
Jean-Philippe Lang 124cca3af0 Text formatting drop down disabled if RedCloth is not available (system settings).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@497 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-05-01 08:37:07 +00:00
Jean-Philippe Lang 7faf77804d Subproject name added in csv and pdf exports.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@496 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-30 20:18:28 +00:00
Jean-Philippe Lang d94bcd285a A 403 error page is now displayed (instead of a blank page) when trying to access a protected page.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@495 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-30 19:47:28 +00:00
Jean-Philippe Lang 9af49e07f3 Added last commit message for each entry in repository browser.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@494 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-30 18:20:22 +00:00
Jean-Philippe Lang 0f0ab74560 Fixed: last day of the month not included in project activity
git-svn-id: http://redmine.rubyforge.org/svn/trunk@493 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-30 18:03:42 +00:00
Jean-Philippe Lang 5288b8550b Removed hard coded 'Search' string in base layout.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@492 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-30 14:17:20 +00:00
Jean-Philippe Lang 66d789229d Added a DropOut effect when removing a block on my page layout.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@491 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-30 09:09:45 +00:00
Jean-Philippe Lang bba9be6a4d Removed an used li element in navigation menu.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@490 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-30 09:08:13 +00:00
Jean-Philippe Lang ebe10fa645 Added a quick search form in page header. Search functionality moved to a dedicated controller.
When used:
* outside of a project: searches projects
* inside a project: searches issues, changesets, news, documents and wiki pages of the current project

If an issue number is given, user is redirected to the corresponding issue.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@489 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-30 08:52:39 +00:00
Jean-Philippe Lang 833c5035a6 Fixed comments header label on time log details
git-svn-id: http://redmine.rubyforge.org/svn/trunk@488 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-28 17:32:11 +00:00
Jean-Philippe Lang aa3445bdb6 Moved functional tests for MyController
git-svn-id: http://redmine.rubyforge.org/svn/trunk@487 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-28 17:30:17 +00:00
Jean-Philippe Lang 66fe12db71 Fixed a TemplateError nil:NilClass (oracle specific) on _news.rhtml
git-svn-id: http://redmine.rubyforge.org/svn/trunk@486 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-27 19:34:31 +00:00
Jean-Philippe Lang 5db3396c07 Added an ajax indicator for all ajax calls. Also removed highlight effects on my page layout edition.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@485 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-27 19:32:57 +00:00
Jean-Philippe Lang c1a18a2889 Subproject name displayed on issue list, calendar and gantt (only for issues that belong to a subproject).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@484 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-27 16:21:53 +00:00
Jean-Philippe Lang 89db185726 Added a link on revision screen to see the entire diff for the revision.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@483 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-26 18:46:55 +00:00
Jean-Philippe Lang a301d0eb4e Updated migration 41 to support table name prefix/sufix.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@482 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-25 16:48:01 +00:00
Jean-Philippe Lang 076655cdd8 Updated migration 41 to support table name prefix/sufix.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@481 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-25 16:47:29 +00:00
Jean-Philippe Lang 8e4a5996b0 Fixed: 9268 These files should be moved out of revision control
Renamed to *.example:
* config/database.yml
* public/dispatch.cgi
* public/dispatch.fcgi
* public/dispatch.rb

git-svn-id: http://redmine.rubyforge.org/svn/trunk@480 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-25 16:21:23 +00:00
Jean-Philippe Lang 52547466f0 Fixed: 10342 Creation of Schema in Oracle
Comment is a reserved keyword for Oracle. The five 'Comment' columns are renamed to 'Commments'.
Migration scripts were modified to let oracle users create the database. For the others, migration 41 will rename the columns (only if columns have the 'old' name).
Fixed also a few oracle specific issues.

Note: currently (in Rails 1.2.3), there's bug in Rails oracle adapter. See: http://dev.rubyonrails.org/ticket/7344
Attached patch is required for redMine to work properly.


git-svn-id: http://redmine.rubyforge.org/svn/trunk@479 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-25 15:06:20 +00:00
Jean-Philippe Lang 4967fa8733 Journal details truncated only if values are strings
git-svn-id: http://redmine.rubyforge.org/svn/trunk@478 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-24 18:26:42 +00:00
Jean-Philippe Lang 8065001c0d Fixed 10335 Error in journalizing an issue with longtext custom fields (Postgresql)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@477 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-24 16:17:57 +00:00
Jean-Philippe Lang 72a5839931 Removed an old, no longer used, model.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@476 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-24 15:53:00 +00:00
Jean-Philippe Lang c995be94d9 Fixed: default status not showing in new issue
git-svn-id: http://redmine.rubyforge.org/svn/trunk@475 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-24 15:19:49 +00:00
Jean-Philippe Lang 018b81a46a Fixed 10337 Regression: Admin raises issue in project not assigned to
git-svn-id: http://redmine.rubyforge.org/svn/trunk@474 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-24 14:49:05 +00:00
Jean-Philippe Lang 941a240535 Commit messages are now scanned for referenced or fixed issue IDs.
Keywords and the status to apply to fixed issues can be defined in Admin -> Settings.

Default keywords:
- for referencing issues: refs, references, IssueID
- for fixing issues: fixes,closes
There's no default status defined for fixed issue. You'll have to specify it if you want to enable auto closure of issues.

Example of a working commit message: "This commit references #1, #2 and fixes #3"

git-svn-id: http://redmine.rubyforge.org/svn/trunk@473 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-24 13:57:27 +00:00
Jean-Philippe Lang ed5b5d0559 Missing fav_off icon.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@472 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-23 19:56:13 +00:00
Jean-Philippe Lang 104b7d457d Removed some parentheses before arguments.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@471 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-23 16:46:04 +00:00
Jean-Philippe Lang feb973b970 The ability to change the issue status to the same status is no longer forced.
This behaviour can be defined in workflow setup.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@470 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-22 22:20:09 +00:00
Jean-Philippe Lang e02068eb2a Removed some parentheses before arguments.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@469 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-22 21:19:16 +00:00
Jean-Philippe Lang eea1878a3b Italian translation update (Alessio Spadaro)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@468 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-22 20:29:00 +00:00
Jean-Philippe Lang 4499c7a030 Grey scale icon for issue not watched.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@467 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-22 20:03:08 +00:00
Jean-Philippe Lang 2f1d8630f1 Fixed: error on history atom feed when there's no notes on an issue change
git-svn-id: http://redmine.rubyforge.org/svn/trunk@466 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-22 12:50:26 +00:00
Jean-Philippe Lang cf59496957 Fixed 10058: issue URL in repository comment break
git-svn-id: http://redmine.rubyforge.org/svn/trunk@465 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-22 10:58:15 +00:00
Jean-Philippe Lang 32de29ea35 Fixed 10061 problem with textilize and :hard_breaks (Pavol Murin)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@464 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-22 10:10:56 +00:00
Jean-Philippe Lang 8d7de50ca8 Fixed 10211 Wiki names can't have periods in them.
Same validations as WikiPage model now applied to wiki start page.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@463 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-22 09:50:16 +00:00
Jean-Philippe Lang 3d685f7bec Fixed: blank lines in left menu with IE
git-svn-id: http://redmine.rubyforge.org/svn/trunk@462 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-21 18:06:01 +00:00
Jean-Philippe Lang 05b08ae38f Right overflow of the left menu now hidden.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@461 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-21 17:11:30 +00:00
Jean-Philippe Lang 623d2f25b1 Projects menu item now shows the list of public projects and projects for which the user is a member (marked with a star).
If current user is admin, private projects are also listed.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@460 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-21 16:40:56 +00:00
Jean-Philippe Lang 634d3557f2 Online help japanese translation added (Ken Date)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@459 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-21 16:11:12 +00:00
Jean-Philippe Lang 6957eceee0 Added portuguese translation (Joao Carlos Clementoni).
Email templates copied from english.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@458 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-21 16:02:28 +00:00
Jean-Philippe Lang 5097f6394d Fixed: error when using a custom query feed with custom field filter.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@457 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-21 13:16:04 +00:00
Jean-Philippe Lang 77cfc1cc59 Added a new block available for my page: "Watched issues"
git-svn-id: http://redmine.rubyforge.org/svn/trunk@456 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-21 12:28:22 +00:00
Jean-Philippe Lang 5d8200b9fc Added missing parentheses on news/show and removed useless call to auto_link
git-svn-id: http://redmine.rubyforge.org/svn/trunk@455 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-21 12:10:50 +00:00
Jean-Philippe Lang 18255881ca Added create_watchers migration
git-svn-id: http://redmine.rubyforge.org/svn/trunk@454 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-21 12:09:07 +00:00
Jean-Philippe Lang 2fb84af3e9 Added "Watch" functionality on issues. It allows users to receive mail notifications about issue changes.
For now, it's only usefull for users who are not members of the project, since members receive notifications for each issue (this behaviour will change).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@453 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-21 12:08:31 +00:00
Jean-Philippe Lang 907f906ec6 Fixed: error when exporting to PDF an issue list using a custom field filter.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@452 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-21 08:21:23 +00:00
Jean-Philippe Lang acfb87a07b News box on project/show is hidden if there is no news to display.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@451 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-20 10:26:57 +00:00
Jean-Philippe Lang c10541feb7 Removed some spaces before argument parentheses.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@450 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-20 10:23:25 +00:00
Jean-Philippe Lang e72778ea84 Removed some spaces before argument parentheses.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@449 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-20 09:53:44 +00:00
Jean-Philippe Lang a824017d8c Checkboxes on the workflow screen diagonal no longer disabled
git-svn-id: http://redmine.rubyforge.org/svn/trunk@448 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-17 11:02:46 +00:00
Jean-Philippe Lang d570bc5cc5 Custom fields for issues can now be used as filters on issue list.
To use a custom field as a filter, check "Used as a filter" on the custom field edit screen.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@447 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-17 10:53:20 +00:00
Jean-Philippe Lang 559b2069ac User#<=> modified to sort on firstname if same lastname
git-svn-id: http://redmine.rubyforge.org/svn/trunk@446 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-12 16:49:02 +00:00
Jean-Philippe Lang 6c5e89ede0 Redmine::VERSION updated
git-svn-id: http://redmine.rubyforge.org/svn/trunk@445 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-11 16:38:19 +00:00
Jean-Philippe Lang 6a8ddc954f Doc files updated for 0.5.0 release
git-svn-id: http://redmine.rubyforge.org/svn/trunk@443 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-11 15:33:27 +00:00
Jean-Philippe Lang 8f1c0eb2dd Closed issues no longer displayed on "my page" -> "issues assigned to me".
git-svn-id: http://redmine.rubyforge.org/svn/trunk@442 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-11 15:18:36 +00:00
Jean-Philippe Lang 8c01275ee9 Japanese lang file updated (Satoru Kurashiki)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@441 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-11 15:14:44 +00:00
Jean-Philippe Lang d6f4208a15 Fixed 10031 revision links and auto linking URLs conflict
git-svn-id: http://redmine.rubyforge.org/svn/trunk@440 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-11 15:10:02 +00:00
Jean-Philippe Lang 2e78bc1bc4 Fixed pagination links on admin project listing
git-svn-id: http://redmine.rubyforge.org/svn/trunk@439 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-10 17:08:59 +00:00
Jean-Philippe Lang 20feb44564 Changesets comments truncated at 500 characters on activity view.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@438 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-09 18:18:23 +00:00
Jean-Philippe Lang 1506d3ddab Form to add an attachment on document view is now togglable.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@437 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-09 17:59:31 +00:00
Jean-Philippe Lang 5cb64fff91 Replaced deprecated content_for_layout in the layout.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@436 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-08 20:43:57 +00:00
Jean-Philippe Lang 1adf56664d Replaced hard coded help link.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@435 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-08 18:16:45 +00:00
Jean-Philippe Lang 183ede84fd Various changes on views. On project summary, members are now grouped by role and subprojects are listed inline.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@434 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-08 17:44:54 +00:00
Jean-Philippe Lang bd9a44c225 Added permissions report (link available on roles list view).
git-svn-id: http://redmine.rubyforge.org/svn/trunk@433 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-07 20:27:19 +00:00
Jean-Philippe Lang 30734ba8e8 Added toggle_link helper.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@432 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-07 18:56:12 +00:00
Jean-Philippe Lang cf4651b6bb Added link_to_issue helper.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@431 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-07 18:30:01 +00:00
Jean-Philippe Lang 14314b77ee Added a warning message on tracker list for trackers that don't have any workflow defined.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@430 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-07 17:33:07 +00:00
Jean-Philippe Lang ef71edd331 Added versions due dates on calendar.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@429 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-07 12:16:09 +00:00
Jean-Philippe Lang 708c3c9ec6 Added versions due dates on gantt chart.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@428 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-07 12:09:01 +00:00
Jean-Philippe Lang c1a5c5b9f8 Removed repository creation callback
git-svn-id: http://redmine.rubyforge.org/svn/trunk@427 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-07 09:49:36 +00:00
Jean-Philippe Lang 1cc7006555 Modified the change_status view to use the form builder.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@426 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-05 18:02:56 +00:00
Jean-Philippe Lang 3f87f3c47a Feature 9867 Allow file upload in comment and add to issue history
git-svn-id: http://redmine.rubyforge.org/svn/trunk@425 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-05 17:20:04 +00:00
Jean-Philippe Lang f3a1b4e38c Added the ability to easily rollback to a previous version of a wiki page.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@424 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-05 14:45:44 +00:00
Jean-Philippe Lang 7eb4abae80 Removed RedCloth :hard_break option that broke textile rendering.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@423 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-05 14:34:11 +00:00
Jean-Philippe Lang 0ea1b712be "Author" column replaced by "Assigned to" in issue list.
"Assigned to" column added in the csv export.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@422 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-05 07:33:51 +00:00
Jean-Philippe Lang 9cf96580bf RedCloth attribute :hard_break set to true to keep line breaks
git-svn-id: http://redmine.rubyforge.org/svn/trunk@421 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-04 22:16:17 +00:00
Jean-Philippe Lang 46d67cb349 Fixed a regression (crash when an admin with no role on the project try to create an issue)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@420 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-04 21:52:53 +00:00
Jean-Philippe Lang f7d5141251 Added svn:eol-style native property for various files
git-svn-id: http://redmine.rubyforge.org/svn/trunk@419 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-04 18:20:45 +00:00
Jean-Philippe Lang 8e11dac375 Added gantt and calendar url in robots file, to avoid recursive indexing by bots.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@418 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-04 18:09:28 +00:00
Jean-Philippe Lang adee6de140 Added a unit test on User#role_for_project
git-svn-id: http://redmine.rubyforge.org/svn/trunk@417 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-04 17:52:07 +00:00
Jean-Philippe Lang 4ed89dc2d3 Fixed User role_for_project method
git-svn-id: http://redmine.rubyforge.org/svn/trunk@416 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-04 17:46:51 +00:00
Jean-Philippe Lang 8e6d575d4e Modified logged_in_user_membership controller method so that it returns a role
git-svn-id: http://redmine.rubyforge.org/svn/trunk@415 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-04 17:32:23 +00:00
Jean-Philippe Lang e227b92972 Various code cleaning, mainly on User, Permission and IssueStatus models.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@414 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-04 17:26:05 +00:00
Jean-Philippe Lang a9a082f05c Patch 9725 Email templates of chinese simplified (Andy Wu)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@413 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-04 16:22:00 +00:00
Jean-Philippe Lang be54cb9531 Feature #9808 Allow user to "change" issue status to the same status
git-svn-id: http://redmine.rubyforge.org/svn/trunk@412 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-04 16:14:26 +00:00
Jean-Philippe Lang 18a6b8e101 Fixed 9793 Preview fail for first wiki page of a project
git-svn-id: http://redmine.rubyforge.org/svn/trunk@411 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-03 22:24:59 +00:00
Jean-Philippe Lang 52ced19e2e Added subprojects issue count on project "Reports" page
git-svn-id: http://redmine.rubyforge.org/svn/trunk@410 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-03 19:48:49 +00:00
Jean-Philippe Lang 1c0971b12c Fixed pagination on users/list (status filter lost)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@409 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-03 19:22:56 +00:00
Jean-Philippe Lang 2db9bdf7a2 Slight modifications on revisions table
git-svn-id: http://redmine.rubyforge.org/svn/trunk@408 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-03 19:16:09 +00:00
Jean-Philippe Lang a09b2a23b9 Feature 9784 Set status when raising issue.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@407 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-03 18:34:22 +00:00
Jean-Philippe Lang 6d2ecefca0 Quick fix for:
* 9750 sort field is lost, when displaying next part of issues list
* 9782 show issues in page is wrong

Still some problems, eg. status criteria is lost when paginating or sorting on users/list

git-svn-id: http://redmine.rubyforge.org/svn/trunk@406 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-03 17:46:05 +00:00
Jean-Philippe Lang 568c22016a Colored square added on "my page" issue list
git-svn-id: http://redmine.rubyforge.org/svn/trunk@405 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-02 20:24:02 +00:00
Jean-Philippe Lang 6e1b9326e4 Added parent project name (if it exists) on project list and project overview.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@404 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-02 20:17:02 +00:00
Jean-Philippe Lang 3c8a38212e Applied the same column width in different similar views.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@403 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-02 19:27:05 +00:00
Jean-Philippe Lang eba4af7a9f added svn-executable property on perl scripts
git-svn-id: http://redmine.rubyforge.org/svn/trunk@402 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-02 19:01:31 +00:00
Jean-Philippe Lang f8a5725214 Renamed the repository management script and removed hard coded parameters.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@401 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-02 19:00:22 +00:00
Jean-Philippe Lang 791077c240 Added some attributes length validations.
Also added some information about attribute length constraints on forms.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@400 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-02 18:44:35 +00:00
Jean-Philippe Lang bd8de9a6f8 Added the ability to copy an existing workflow when creating a new tracker.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@399 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-02 17:45:21 +00:00
Jean-Philippe Lang 0a82489ddc Added the ability to include subprojects issues on calendar & gantt (options box)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@398 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-02 17:12:02 +00:00
Jean-Philippe Lang 006b8716ed Migration to add identifier attribute on project.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@397 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-02 06:29:09 +00:00
Jean-Philippe Lang 4ff8386e3d Initial commit for svn repository management and access control:
* Identifier attribute added on Project model. Used as the unix group name for the project
* Web services (disabled by default) and scripts for repository management on a remote svn host

git-svn-id: http://redmine.rubyforge.org/svn/trunk@396 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-04-01 19:43:59 +00:00
Jean-Philippe Lang cb6c8bee47 Patch #9705 Tracker filtering on Main Calendar (Jeffrey Jones)
Slightly edited.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@395 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-31 17:15:30 +00:00
Jean-Philippe Lang 3a35883250 Added calendar-zh.js for chinese translation (Andy Wu)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@394 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-31 15:53:56 +00:00
Jean-Philippe Lang 2a00565bc4 Added chinese simplified translation (Andy Wu)
Fixed rfpdf chinese.rb

git-svn-id: http://redmine.rubyforge.org/svn/trunk@393 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-31 15:51:44 +00:00
Jean-Philippe Lang 5e0e951867 Japanese lang file updated (Satoru KURASHIKI)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@392 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-30 23:02:35 +00:00
Jean-Philippe Lang 4c75bd404b removed hard coded strings in views
git-svn-id: http://redmine.rubyforge.org/svn/trunk@391 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-29 19:02:48 +00:00
Jean-Philippe Lang 2b70760594 patch #9627 Add Side by Side in Diff view (Cyril Mougel)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@390 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-29 18:14:59 +00:00
Jean-Philippe Lang d8ce97f766 german lang file updated (Thomas Löber)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@389 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-28 17:52:20 +00:00
Jean-Philippe Lang cf221c8f75 removed filter_html on RedCloth textile conversions
git-svn-id: http://redmine.rubyforge.org/svn/trunk@388 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-27 17:49:52 +00:00
Jean-Philippe Lang b938c60396 slight svg rendering modifications
git-svn-id: http://redmine.rubyforge.org/svn/trunk@387 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-26 18:13:42 +00:00
Jean-Philippe Lang 3a28fa01b0 added info about textile availabality on admin/info
git-svn-id: http://redmine.rubyforge.org/svn/trunk@386 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-26 18:03:25 +00:00
Jean-Philippe Lang 8a84884d50 added changes counts on the "commits per author" svn stat graph
git-svn-id: http://redmine.rubyforge.org/svn/trunk@385 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-26 17:14:06 +00:00
Jean-Philippe Lang b103052ff0 replaced an hard coded label
git-svn-id: http://redmine.rubyforge.org/svn/trunk@384 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-26 16:47:35 +00:00
Jean-Philippe Lang 71b6dd232e fixed #9542 sqlite3 exception in db:migrate on AddChangesetCommitDate
git-svn-id: http://redmine.rubyforge.org/svn/trunk@383 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-26 16:41:54 +00:00
Jean-Philippe Lang 1f7863e323 fixed bug 9537 fetch_changesets broken
git-svn-id: http://redmine.rubyforge.org/svn/trunk@382 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-25 22:33:40 +00:00
Jean-Philippe Lang e951209d68 added pagination on revisions list
git-svn-id: http://redmine.rubyforge.org/svn/trunk@381 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-25 18:18:29 +00:00
Jean-Philippe Lang 0ef114e006 added simple svn statistics graphs, rendered using SVG::Graph
git-svn-id: http://redmine.rubyforge.org/svn/trunk@380 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-25 17:11:46 +00:00
Jean-Philippe Lang 887f11435b Patch #9530 Progress bars for roadmap versions (Nick Read)
Slightly edited and links to issues list added

git-svn-id: http://redmine.rubyforge.org/svn/trunk@379 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-25 12:59:48 +00:00
Jean-Philippe Lang d7f1b873c8 show anonymous on activity view when a commit has no author
git-svn-id: http://redmine.rubyforge.org/svn/trunk@378 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-25 12:24:07 +00:00
Jean-Philippe Lang 16f9f50f50 SVN commits are now stored in the database, and added to the activity view and the search engine.
New commits are automatically retrieved and stored when consulting the repository in the app. This behaviour can be disabled by unchecking 'Autofecth commits' in configuration settings.
Commits can be fetched offline by running (recommanded at least for the initial import of past commits): 
ruby script/runner "Repository.fetch_changesets"
It will load commits for all of the repositories.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@377 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-25 12:12:15 +00:00
Jean-Philippe Lang 6f1dcdc08f updated INSTALL doc with rake db:migrate instead of the deprecated rake migrate
git-svn-id: http://redmine.rubyforge.org/svn/trunk@376 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-24 20:09:07 +00:00
Jean-Philippe Lang 3cd50da3b0 text formatting setting cached at controller level
git-svn-id: http://redmine.rubyforge.org/svn/trunk@375 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-24 09:16:33 +00:00
Jean-Philippe Lang 0125ec3b48 removed td padding
git-svn-id: http://redmine.rubyforge.org/svn/trunk@374 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-23 23:50:12 +00:00
Jean-Philippe Lang 7611f52faf added some parenthesis
git-svn-id: http://redmine.rubyforge.org/svn/trunk@373 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-23 19:03:22 +00:00
Jean-Philippe Lang 669b2342ff added some parenthesis in views
git-svn-id: http://redmine.rubyforge.org/svn/trunk@372 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-23 19:00:48 +00:00
Jean-Philippe Lang 1493c2a9b4 added roadmap and activity calls in projects functional tests
git-svn-id: http://redmine.rubyforge.org/svn/trunk@371 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-23 15:58:53 +00:00
Jean-Philippe Lang 724ca74f51 patch #9429 Display Wiki edits in activity log (Nick Read)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@370 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-23 15:58:02 +00:00
Jean-Philippe Lang 76d4ac17a2 added 999 as a maximum for time entry hours
git-svn-id: http://redmine.rubyforge.org/svn/trunk@369 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-23 13:11:47 +00:00
Jean-Philippe Lang 8d54d97007 Simple time tracking functionality added. Time can be logged at issue or project level.
There's no aggregation reports for now, it's just possible to see all time entries for a project or an issue.
A new "activities" enumeration is added.
Permission for a role to log time must be set (new "Time tracking" section in role permissions screen).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@368 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-23 12:22:31 +00:00
Jean-Philippe Lang 7cf2d889d8 native eol property set on lang files
git-svn-id: http://redmine.rubyforge.org/svn/trunk@363 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-23 08:15:22 +00:00
Jean-Philippe Lang 59a2d1c974 closed issues are now striked on roadmap
git-svn-id: http://redmine.rubyforge.org/svn/trunk@362 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-23 08:13:59 +00:00
Jean-Philippe Lang 5108a9c6fe german translation improved (thanks to Daniel Weinand)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@361 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-23 08:13:33 +00:00
Jean-Philippe Lang 6e98df0e28 fixed #9397 Roadmap - Distinguishing open and closed issues
(closed) is added at the end of closed issue subjects

git-svn-id: http://redmine.rubyforge.org/svn/trunk@360 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-21 18:56:47 +00:00
Jean-Philippe Lang f3f75f557a added some diagnostic information on admin/info
git-svn-id: http://redmine.rubyforge.org/svn/trunk@359 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-20 20:03:27 +00:00
Jean-Philippe Lang 8bf5759d26 fixed #9398 RSS feed on custom reports not displaying subproject changes
git-svn-id: http://redmine.rubyforge.org/svn/trunk@358 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-20 18:55:33 +00:00
Jean-Philippe Lang cbf797ed95 removed a non matching closing select tag
git-svn-id: http://redmine.rubyforge.org/svn/trunk@357 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-20 18:37:53 +00:00
Jean-Philippe Lang 17c4d733d0 fixed #9403 Include "Fixed version" when creating a new issue
git-svn-id: http://redmine.rubyforge.org/svn/trunk@356 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-20 18:37:21 +00:00
Jean-Philippe Lang dd52fb7e2e accidentally committed a database name change...
git-svn-id: http://redmine.rubyforge.org/svn/trunk@355 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-20 18:34:07 +00:00
Jean-Philippe Lang b1f5103c75 fixed #9401 New Wiki, cannot create new page (good one)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@354 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-20 18:14:23 +00:00
Jean-Philippe Lang f19e8af33b fixed #9404 Issue and revision textile links in issue notes
git-svn-id: http://redmine.rubyforge.org/svn/trunk@353 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-20 18:09:14 +00:00
Jean-Philippe Lang a559c0cfc2 fixed #9401 New Wiki, cannot create new page.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@352 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-20 17:41:04 +00:00
Jean-Philippe Lang 5132252daa added "return false;" for onclick event on unclickable menu items
git-svn-id: http://redmine.rubyforge.org/svn/trunk@351 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-20 17:38:34 +00:00
Jean-Philippe Lang dfb81dd89c added back "subproject" filter on issue list
git-svn-id: http://redmine.rubyforge.org/svn/trunk@350 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-19 20:31:02 +00:00
Jean-Philippe Lang 59e3802c74 fixed migration scripts to work with mysql 5 running in strict mode
git-svn-id: http://redmine.rubyforge.org/svn/trunk@349 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-19 18:12:35 +00:00
Jean-Philippe Lang a1d2acd574 fixed #9374 Edit issue doesn't have blank category
git-svn-id: http://redmine.rubyforge.org/svn/trunk@348 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-19 17:46:02 +00:00
Jean-Philippe Lang 8bb60b6474 added a status filter on users/list
git-svn-id: http://redmine.rubyforge.org/svn/trunk@347 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-18 20:44:33 +00:00
Jean-Philippe Lang 143be7ee02 added a setting option to set the feeds content limit
git-svn-id: http://redmine.rubyforge.org/svn/trunk@346 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-18 18:32:59 +00:00
Jean-Philippe Lang d967bb4a54 added back the anchor for each page on the wiki full export
git-svn-id: http://redmine.rubyforge.org/svn/trunk@345 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-18 16:52:25 +00:00
Jean-Philippe Lang fd37f37ee4 fixed problems when svn path doesn't point to the root directory of the repository.
a root_url property has been added on the repository. it's automatically retrieved the first time the repository is browsed

git-svn-id: http://redmine.rubyforge.org/svn/trunk@344 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-18 15:48:05 +00:00
Jean-Philippe Lang db3e8616c8 better rendering time for projects/calendar
git-svn-id: http://redmine.rubyforge.org/svn/trunk@343 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-18 10:17:59 +00:00
Jean-Philippe Lang 8fdef1f820 admin menu modification
git-svn-id: http://redmine.rubyforge.org/svn/trunk@342 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-18 10:17:21 +00:00
Jean-Philippe Lang b481e76ee2 members are now sorted according to roles order on projects/show
removed the useless "members" item in the project menu

git-svn-id: http://redmine.rubyforge.org/svn/trunk@341 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-17 17:44:29 +00:00
Jean-Philippe Lang 3477f9bfe0 added feed icon
git-svn-id: http://redmine.rubyforge.org/svn/trunk@340 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-17 15:51:26 +00:00
Jean-Philippe Lang 10cf1ccc1a added rss/atom feeds at project levels for:
* news
* new issues reported
* details of issue changes

issue cutom queries can be used as feeds

git-svn-id: http://redmine.rubyforge.org/svn/trunk@339 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-17 15:18:50 +00:00
Jean-Philippe Lang 22b42dc90a fixed self.down in AddSearchPermission migration
git-svn-id: http://redmine.rubyforge.org/svn/trunk@338 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-17 14:50:08 +00:00
Jean-Philippe Lang e6f58e4dcc fixed #9308 table_name pre/suffix support
git-svn-id: http://redmine.rubyforge.org/svn/trunk@337 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-15 22:11:02 +00:00
Jean-Philippe Lang 192b9008bd fixed: error on feeds/news when no news exist
git-svn-id: http://redmine.rubyforge.org/svn/trunk@336 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-15 19:20:49 +00:00
Jean-Philippe Lang 56d0af472d removed project_id presence validation on wiki model which was failing when creating a project with a wiki
git-svn-id: http://redmine.rubyforge.org/svn/trunk@335 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-14 17:23:38 +00:00
Jean-Philippe Lang 85f32b956f added italian translation [Alessio Spadaro]
git-svn-id: http://redmine.rubyforge.org/svn/trunk@334 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-12 18:34:25 +00:00
Jean-Philippe Lang 027bf93849 added svn:eol-style native property on /app files
git-svn-id: http://redmine.rubyforge.org/svn/trunk@333 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-12 17:59:02 +00:00
Jean-Philippe Lang 071f8e18d0 fixed #9207 Can't Save Changes to Project
git-svn-id: http://redmine.rubyforge.org/svn/trunk@332 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-12 17:33:20 +00:00
Jean-Philippe Lang 19d962c3aa added svn:executable property on the scripts
git-svn-id: http://redmine.rubyforge.org/svn/trunk@331 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-12 17:29:04 +00:00
Jean-Philippe Lang dae2d4686e width of all wiki content textareas set to 99.5%
git-svn-id: http://redmine.rubyforge.org/svn/trunk@329 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-11 14:22:53 +00:00
Jean-Philippe Lang fc3ee67b1f send a 404 when trying to access an unexisting repository
git-svn-id: http://redmine.rubyforge.org/svn/trunk@328 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-11 14:06:05 +00:00
Jean-Philippe Lang a54a15523b wiki:
* added "Heading" and "Inline quote" buttons to the toolbar
* changed "Link" button behaviour (now produces [[Link]])
* added a simple help on text formatting

git-svn-id: http://redmine.rubyforge.org/svn/trunk@327 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-11 14:00:15 +00:00
Jean-Philippe Lang 5b135c07d0 fixed the "preview" link on wiki/edit
git-svn-id: http://redmine.rubyforge.org/svn/trunk@326 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-10 15:46:34 +00:00
Jean-Philippe Lang dd83445fd7 extended the search to wiki content
git-svn-id: http://redmine.rubyforge.org/svn/trunk@325 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-10 15:34:38 +00:00
Jean-Philippe Lang 290485b144 added missing calendar-ja.js, partially translated
git-svn-id: http://redmine.rubyforge.org/svn/trunk@324 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-10 15:20:37 +00:00
Jean-Philippe Lang c514316a2e wiki branch merged into trunk
git-svn-id: http://redmine.rubyforge.org/svn/trunk@323 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-10 15:09:49 +00:00
Jean-Philippe Lang 8b98ceb92c improved search engine
* it's now possible to search for multiple words ("all words" or "one of the words" options)
* added a fixed limit for result count

git-svn-id: http://redmine.rubyforge.org/svn/trunk@321 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-10 13:32:04 +00:00
Jean-Philippe Lang 7a20a4d32b feature #9137 Password-protected SVN repositories
* added login and password attributes on repositories
* fixed svn calls (svn waiting for user input)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@319 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-09 18:03:31 +00:00
Jean-Philippe Lang 8cbb78bb1d patch #9104 Disable cleartext logging of passwords in log files
git-svn-id: http://redmine.rubyforge.org/svn/trunk@310 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-07 17:59:41 +00:00
Jean-Philippe Lang ef84339003 fixed #9097: Minor rendering problem within Safari at admin/mail_options and roles permissions
git-svn-id: http://redmine.rubyforge.org/svn/trunk@308 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-06 22:46:58 +00:00
Jean-Philippe Lang 909954ac34 fixed dotted hr for IE6
git-svn-id: http://redmine.rubyforge.org/svn/trunk@305 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-06 18:45:08 +00:00
Jean-Philippe Lang d996643540 adjusted the attachment date column width on pdf issue export to avoid overlapping in japanese
git-svn-id: http://redmine.rubyforge.org/svn/trunk@302 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-05 19:00:16 +00:00
Jean-Philippe Lang 074a4a6e0e fixed #9005: administration-menu doesn't work with prefix-url
git-svn-id: http://redmine.rubyforge.org/svn/trunk@289 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-03 17:35:11 +00:00
Jean-Philippe Lang a6248cba19 added missing field_is_in_roadmap key in lang files
git-svn-id: http://redmine.rubyforge.org/svn/trunk@288 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-03 10:22:38 +00:00
Jean-Philippe Lang 6a875eb691 fix for #8973: Export feature(to csv/pdf) doesn't work in Japanese
csv and pdf encoding are know defined for each language (general_csv_encoding and general_pdf_encoding keys in lang files)
added SJIS font for japanese pdf exports

git-svn-id: http://redmine.rubyforge.org/svn/trunk@287 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-03-01 19:49:04 +00:00
Jean-Philippe Lang 83c96d4f3e search engine improvements and simple test
git-svn-id: http://redmine.rubyforge.org/svn/trunk@285 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-28 22:35:22 +00:00
Jean-Philippe Lang 1fcc9e977f updated supported language count in test (now 5)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@284 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-28 22:32:45 +00:00
Jean-Philippe Lang c56b95ed68 patch #8936: Properties changes in a diff are showing as modifications to files
git-svn-id: http://redmine.rubyforge.org/svn/trunk@283 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-28 19:33:07 +00:00
Jean-Philippe Lang bb3a5bcfd7 patch #8937: Show filename above diff to aid reading multi-file diffs
git-svn-id: http://redmine.rubyforge.org/svn/trunk@282 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-28 19:26:53 +00:00
Jean-Philippe Lang 35678a1c09 brackets removed around "View all changes" on issues/show
git-svn-id: http://redmine.rubyforge.org/svn/trunk@281 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-27 22:07:42 +00:00
Jean-Philippe Lang c622ad0b50 #8911 Textile formatting for issue notes
git-svn-id: http://redmine.rubyforge.org/svn/trunk@280 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-27 22:07:01 +00:00
Jean-Philippe Lang 75324e4859 added a simple search engine. left to do:
- full content search (only subject/titles for now)
- pagination
- results presentation improvement

git-svn-id: http://redmine.rubyforge.org/svn/trunk@279 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-27 21:03:15 +00:00
Jean-Philippe Lang 93f07c78d6 japanese translation added (thanks to Satoru Kurashiki)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@278 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-27 17:58:59 +00:00
Jean-Philippe Lang 59a619ebbb fixed #8915: crash when adding a user with a wrong length password
git-svn-id: http://redmine.rubyforge.org/svn/trunk@277 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-27 17:26:25 +00:00
Jean-Philippe Lang 77938cc11e fixed: no diff display with one line files [#8883]
git-svn-id: http://redmine.rubyforge.org/svn/trunk@276 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-26 21:32:16 +00:00
Jean-Philippe Lang ae998984a0 fixed: unable to update repository url
git-svn-id: http://redmine.rubyforge.org/svn/trunk@275 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-26 19:20:18 +00:00
Jean-Philippe Lang 2e31311e3c "#id" patterns are turned into links to issues in descriptions and commit messages
git-svn-id: http://redmine.rubyforge.org/svn/trunk@274 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-25 19:40:57 +00:00
Jean-Philippe Lang 32782a7f9f added back database adapter name on admin/info
git-svn-id: http://redmine.rubyforge.org/svn/trunk@262 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-20 20:17:34 +00:00
Jean-Philippe Lang 4f2d74e25f fixed: error when clicking "add" with no block selected on my/page_layout
git-svn-id: http://redmine.rubyforge.org/svn/trunk@261 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 20:20:17 +00:00
Jean-Philippe Lang c9ed3fdae3 updated CHANGELOG for 0.4.2 release
git-svn-id: http://redmine.rubyforge.org/svn/trunk@259 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 15:08:26 +00:00
Jean-Philippe Lang 7b164b5cd4 fixed: crash on my/page_layout (trying to modify a frozen hash)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@258 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 14:59:06 +00:00
Jean-Philippe Lang dd54b1aacb fixed: hide mail preference not saved (my/account)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@257 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 13:56:35 +00:00
Jean-Philippe Lang 8fa96feb1f show "no data" label when there's nothing to display on project member list
git-svn-id: http://redmine.rubyforge.org/svn/trunk@256 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 13:45:15 +00:00
Jean-Philippe Lang ddd0796e40 show "no data" label when there's nothing to display on changelog and roadmap
git-svn-id: http://redmine.rubyforge.org/svn/trunk@255 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 13:37:13 +00:00
Jean-Philippe Lang 4ef86b965b updated UPGRADING doc file
git-svn-id: http://redmine.rubyforge.org/svn/trunk@254 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 12:26:06 +00:00
Jean-Philippe Lang 1cc6dd871b updated documentation
git-svn-id: http://redmine.rubyforge.org/svn/trunk@253 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 12:13:24 +00:00
Jean-Philippe Lang 4564519a56 missing parenthesis added in filter.rb (net-ldap)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@252 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 11:45:55 +00:00
Jean-Philippe Lang 5a241fd12e file upload test now uses ActionController::TestUploadedFile
git-svn-id: http://redmine.rubyforge.org/svn/trunk@251 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 11:41:39 +00:00
Jean-Philippe Lang 840fcf2fa9 file upload test now uses ActionController::TestUploadedFile
git-svn-id: http://redmine.rubyforge.org/svn/trunk@250 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 11:41:36 +00:00
Jean-Philippe Lang 75e3ab737c replaced deprecated server_settings with smtp_settings
git-svn-id: http://redmine.rubyforge.org/svn/trunk@249 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 11:40:48 +00:00
Jean-Philippe Lang 516cedaa47 syntax error in rfpdf/makefont.rb
git-svn-id: http://redmine.rubyforge.org/svn/trunk@248 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 10:33:22 +00:00
Jean-Philippe Lang 3881ade96d removed some line breaks in application.css
git-svn-id: http://redmine.rubyforge.org/svn/trunk@247 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-05 20:18:22 +00:00
Jean-Philippe Lang f4842d6eed added icons on my/page_layout top right links
git-svn-id: http://redmine.rubyforge.org/svn/trunk@246 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-05 19:48:01 +00:00
Jean-Philippe Lang 10e77d865b wrapped javascript with //<![CDATA[ in my/page_layout
git-svn-id: http://redmine.rubyforge.org/svn/trunk@245 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-05 19:40:26 +00:00
Jean-Philippe Lang 92725e5803 check that block is defined before rendering it (my/page_layout)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@244 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-05 19:38:19 +00:00
Jean-Philippe Lang 73510949ba check that block is defined before rendering it (my/page)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@243 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-05 19:36:21 +00:00
Jean-Philippe Lang 08ff4dfb21 removed underscores in block names (problem with scriptaculous sortables)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@242 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-05 19:33:43 +00:00
Jean-Philippe Lang 2e72f2eca8 updated prototype (1.50) and script.aculo.us javascripts
git-svn-id: http://redmine.rubyforge.org/svn/trunk@241 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-05 18:31:27 +00:00
Jean-Philippe Lang 9845bbddce mail notification removed for projects/roadmap in migration
git-svn-id: http://redmine.rubyforge.org/svn/trunk@240 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-05 18:20:39 +00:00
Jean-Philippe Lang 81b3ad5b87 updated documentation with new setting options
git-svn-id: http://redmine.rubyforge.org/svn/trunk@239 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 17:20:36 +00:00
Jean-Philippe Lang 6c4f196f6e removed strong tag for version name in settings view
git-svn-id: http://redmine.rubyforge.org/svn/trunk@238 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 17:18:16 +00:00
Jean-Philippe Lang 022b4ae57c rescued to_date exception when displaying a custom value
git-svn-id: http://redmine.rubyforge.org/svn/trunk@237 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 16:35:42 +00:00
Jean-Philippe Lang d185e2f9e0 lost_password option checking in account controller
git-svn-id: http://redmine.rubyforge.org/svn/trunk@236 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 15:55:29 +00:00
Jean-Philippe Lang 2b7c31feb5 setting password_lost renamed to lost_password
git-svn-id: http://redmine.rubyforge.org/svn/trunk@235 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 15:54:56 +00:00
Jean-Philippe Lang 7dd312b1ac added a setting option to set max number of issues in export
git-svn-id: http://redmine.rubyforge.org/svn/trunk@234 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 15:45:38 +00:00
Jean-Philippe Lang 4ff7b395dd associations loading correction in projects/export_issues_pdf
git-svn-id: http://redmine.rubyforge.org/svn/trunk@233 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 12:27:41 +00:00
Jean-Philippe Lang 66044c620b issue list pdf export modified
git-svn-id: http://redmine.rubyforge.org/svn/trunk@232 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 12:24:16 +00:00
Jean-Philippe Lang 52c1718ee8 added a setting option to disable "password lost" functionality
git-svn-id: http://redmine.rubyforge.org/svn/trunk@231 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 09:37:28 +00:00
Jean-Philippe Lang 8b180aa606 removed an unused localization string
git-svn-id: http://redmine.rubyforge.org/svn/trunk@230 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 09:23:28 +00:00
Jean-Philippe Lang ccaf6c8df0 removed unused development_* environments
git-svn-id: http://redmine.rubyforge.org/svn/trunk@229 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 09:20:07 +00:00
Jean-Philippe Lang 879a614cde added roadmap view
git-svn-id: http://redmine.rubyforge.org/svn/trunk@228 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 09:18:40 +00:00
Jean-Philippe Lang 8c3dc34359 rightbox class set to display:none for printing
git-svn-id: http://redmine.rubyforge.org/svn/trunk@227 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-04 09:08:49 +00:00
Jean-Philippe Lang f82654f158 Title property added for pdf export of an issue
git-svn-id: http://redmine.rubyforge.org/svn/trunk@226 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-03 14:12:28 +00:00
Jean-Philippe Lang c365614548 Title property added for generated pdf gantt
git-svn-id: http://redmine.rubyforge.org/svn/trunk@225 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-03 14:08:47 +00:00
Jean-Philippe Lang a3c2c4a48a Creator property added for generated pdf files
git-svn-id: http://redmine.rubyforge.org/svn/trunk@224 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-03 14:00:22 +00:00
Jean-Philippe Lang 8316e78667 custom_field.possible_values is now serialized (no longer pipe separated)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@223 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-03 12:57:33 +00:00
Jean-Philippe Lang 4a988b0f92 removed translation of error messages inside ActiveRecord module
git-svn-id: http://redmine.rubyforge.org/svn/trunk@222 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-03 12:42:06 +00:00
Jean-Philippe Lang b5e80bf90f filter is now cleared when clicking on 'Issues' menu item
git-svn-id: http://redmine.rubyforge.org/svn/trunk@221 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-02 16:13:45 +00:00
Jean-Philippe Lang da426fa652 added a limit of 500 issues when exporting to csv or pdf
git-svn-id: http://redmine.rubyforge.org/svn/trunk@220 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-02 16:05:54 +00:00
Jean-Philippe Lang 39e65a41a2 added tests for settings
git-svn-id: http://redmine.rubyforge.org/svn/trunk@219 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-02 15:38:34 +00:00
Jean-Philippe Lang 1eb17b08f9 added parentheses to get rid of warning
git-svn-id: http://redmine.rubyforge.org/svn/trunk@218 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-02 15:22:07 +00:00
Jean-Philippe Lang 318c8717b8 empty tokens table
git-svn-id: http://redmine.rubyforge.org/svn/trunk@217 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-02 15:20:49 +00:00
Jean-Philippe Lang 7184632b84 fixtures loading correction
git-svn-id: http://redmine.rubyforge.org/svn/trunk@216 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-02 15:16:30 +00:00
Jean-Philippe Lang 1e4a7d55e0 added unit tests for mailer
git-svn-id: http://redmine.rubyforge.org/svn/trunk@215 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-02 14:47:40 +00:00
Jean-Philippe Lang 5101c55e10 added lang files validition test
git-svn-id: http://redmine.rubyforge.org/svn/trunk@214 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-02 14:47:13 +00:00
Jean-Philippe Lang c1537b5291 * fixed a variable name error in Setting
* setting values to_s


git-svn-id: http://redmine.rubyforge.org/svn/trunk@213 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-02 14:28:54 +00:00
Jean-Philippe Lang 4c767d636a removed broken route for sorting links
git-svn-id: http://redmine.rubyforge.org/svn/trunk@212 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-02 13:57:36 +00:00
Jean-Philippe Lang aa91ca0a8d issue list: added 'Priority' column, removed 'Created on' column
git-svn-id: http://redmine.rubyforge.org/svn/trunk@211 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-02 13:56:49 +00:00
Jean-Philippe Lang d3b831bf7b post method verification for trackers/move
git-svn-id: http://redmine.rubyforge.org/svn/trunk@210 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-31 21:20:49 +00:00
Jean-Philippe Lang fa688d48e6 added the ability to set the sort order for trackers
git-svn-id: http://redmine.rubyforge.org/svn/trunk@209 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-31 21:18:43 +00:00
Jean-Philippe Lang 671a0fa101 added the ability to set the sort order for roles
git-svn-id: http://redmine.rubyforge.org/svn/trunk@208 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-31 20:57:01 +00:00
Jean-Philippe Lang 99f006f2c9 removed png transparency (not supported in ie)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@207 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-31 20:25:24 +00:00
Jean-Philippe Lang f9c400a727 custom fields list screen split in 3 tabs (one for each kind of custom fields)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@206 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-31 20:10:21 +00:00
Jean-Philippe Lang fe22797d69 added the ability to set the sort order for issue statuses
git-svn-id: http://redmine.rubyforge.org/svn/trunk@205 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-31 19:53:24 +00:00
Jean-Philippe Lang d29ba0b80f updated css for online help
git-svn-id: http://redmine.rubyforge.org/svn/trunk@204 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-29 18:45:33 +00:00
Jean-Philippe Lang 18fd46ab6b fixed french issue_edit mail template
git-svn-id: http://redmine.rubyforge.org/svn/trunk@203 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-28 19:59:39 +00:00
Jean-Philippe Lang ec7ac956e4 removed environment information
git-svn-id: http://redmine.rubyforge.org/svn/trunk@202 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-28 19:54:41 +00:00
Jean-Philippe Lang 99c560295f indentation corrections
git-svn-id: http://redmine.rubyforge.org/svn/trunk@201 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-28 00:00:21 +00:00
Jean-Philippe Lang 90c7cf9763 proper language setting for mail notifications
git-svn-id: http://redmine.rubyforge.org/svn/trunk@200 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-27 23:22:58 +00:00
Jean-Philippe Lang 3dc9ceaadf credits for english help translation to Todd McGrath
git-svn-id: http://redmine.rubyforge.org/svn/trunk@199 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-27 22:48:43 +00:00
Jean-Philippe Lang 23f6185bfe updated changelog and doc files
git-svn-id: http://redmine.rubyforge.org/svn/trunk@198 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-27 22:35:58 +00:00
Jean-Philippe Lang 1cb0294388 added unit tests for issue statuses, user preferences and tokens
git-svn-id: http://redmine.rubyforge.org/svn/trunk@197 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-27 22:31:42 +00:00
Jean-Philippe Lang 2d47a6d71c mail notifications added when:
* a document is added
* a file is added to the project
* an attachment is added to an issue or a document

git-svn-id: http://redmine.rubyforge.org/svn/trunk@196 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-27 15:01:19 +00:00
Jean-Philippe Lang 22c2209cf9 redmine_demo.db added to svn:ignored
git-svn-id: http://redmine.rubyforge.org/svn/trunk@195 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-27 12:32:39 +00:00
Jean-Philippe Lang 471679b0f0 deprecated @headers replaced
git-svn-id: http://redmine.rubyforge.org/svn/trunk@194 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-27 09:25:55 +00:00
Jean-Philippe Lang 862c9d76e6 smaller footer font
git-svn-id: http://redmine.rubyforge.org/svn/trunk@193 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-27 09:23:00 +00:00
Jean-Philippe Lang 2c187c43bf fixed: filters ignored when exporting a predefined query to csv/pdf
git-svn-id: http://redmine.rubyforge.org/svn/trunk@192 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 22:05:32 +00:00
Jean-Philippe Lang 486b4e9ea2 deprecated ":post => true" replaced
git-svn-id: http://redmine.rubyforge.org/svn/trunk@191 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 21:09:27 +00:00
Jean-Philippe Lang 5f361e71df only active users are now proposed when adding a member to a project
git-svn-id: http://redmine.rubyforge.org/svn/trunk@190 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 20:52:52 +00:00
Jean-Philippe Lang 13881f5a5b deprecated projects_count replaced
git-svn-id: http://redmine.rubyforge.org/svn/trunk@189 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 20:41:16 +00:00
Jean-Philippe Lang c38a2112c6 deprecated children_count replaced
git-svn-id: http://redmine.rubyforge.org/svn/trunk@188 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 20:34:09 +00:00
Jean-Philippe Lang a9fc777b20 removed space before argument parentheses
git-svn-id: http://redmine.rubyforge.org/svn/trunk@187 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 20:24:51 +00:00
Jean-Philippe Lang ab15456119 deprecated count call modified
git-svn-id: http://redmine.rubyforge.org/svn/trunk@186 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 20:06:05 +00:00
Jean-Philippe Lang 0d55613359 replaced deprecated human_size by number_to_human_size
git-svn-id: http://redmine.rubyforge.org/svn/trunk@185 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 20:02:49 +00:00
Jean-Philippe Lang 845460b165 deprecated start_form_tag replaced by form_tag
git-svn-id: http://redmine.rubyforge.org/svn/trunk@184 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 19:56:25 +00:00
Jean-Philippe Lang 7c27fb7c1d fixed: crash when changing the status of an issue
git-svn-id: http://redmine.rubyforge.org/svn/trunk@183 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 18:55:02 +00:00
Jean-Philippe Lang e51c20c496 project settings tabs redesign
git-svn-id: http://redmine.rubyforge.org/svn/trunk@182 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 18:27:10 +00:00
Jean-Philippe Lang 190fef513f * project settings split in 4 tabs
* prototype.js upgraded to 1.5.0_rc1

git-svn-id: http://redmine.rubyforge.org/svn/trunk@181 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 17:59:06 +00:00
Jean-Philippe Lang b9ef10fe3f fixed: unable to delete an issue status even if it's not used yet
git-svn-id: http://redmine.rubyforge.org/svn/trunk@180 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 17:50:48 +00:00
Jean-Philippe Lang 2b9777555f fixed: setting an issue status as default status leads to an sql error with SQLite
git-svn-id: http://redmine.rubyforge.org/svn/trunk@179 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-26 17:39:36 +00:00
Jean-Philippe Lang 1f79394ac4 updated date removed from my/account view (useless since account is updated at each login)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@178 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-25 19:35:39 +00:00
Jean-Philippe Lang e2e111f95e * added missing fields to csv export: priority, start date, due date, done ratio
git-svn-id: http://redmine.rubyforge.org/svn/trunk@177 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-25 19:32:07 +00:00
Jean-Philippe Lang 8985d05a66 mongrel_debug unversioned
git-svn-id: http://redmine.rubyforge.org/svn/trunk@176 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-25 18:28:54 +00:00
Jean-Philippe Lang 4e03668eec * replaced :controller => '' broken statements by :controller => 'welcome'
* request_uri method no more used

git-svn-id: http://redmine.rubyforge.org/svn/trunk@175 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-25 18:27:42 +00:00
Jean-Philippe Lang 2789b08dd0 removed serialize class_name option in user_preference
git-svn-id: http://redmine.rubyforge.org/svn/trunk@174 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-25 18:25:57 +00:00
Jean-Philippe Lang 66a02688ad Modified GLoc helpers to ensure compatibility with rails 1.2
git-svn-id: http://redmine.rubyforge.org/svn/trunk@173 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-24 18:15:27 +00:00
Jean-Philippe Lang 681bbf02ae removed "selected" class on Home menu item
git-svn-id: http://redmine.rubyforge.org/svn/trunk@172 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-21 16:52:32 +00:00
Jean-Philippe Lang c80de840d1 updated online help with settings description
git-svn-id: http://redmine.rubyforge.org/svn/trunk@171 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-21 14:39:50 +00:00
Jean-Philippe Lang bd3458da6e added Redmine::VERSION module
git-svn-id: http://redmine.rubyforge.org/svn/trunk@170 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-21 12:54:51 +00:00
Jean-Philippe Lang 4ccbc65628 added translated strings for settings view
git-svn-id: http://redmine.rubyforge.org/svn/trunk@169 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-21 12:31:16 +00:00
Jean-Philippe Lang 9a2753931b added missing string en de, en, es
git-svn-id: http://redmine.rubyforge.org/svn/trunk@168 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-21 11:56:20 +00:00
Jean-Philippe Lang 0b4d4db131 settings are now stored in the database (config_custom.rb no more used) and editable through the application in: Admin -> Settings
git-svn-id: http://redmine.rubyforge.org/svn/trunk@167 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-21 11:50:22 +00:00
Jean-Philippe Lang a1b12335ab 'new query' link moved
git-svn-id: http://redmine.rubyforge.org/svn/trunk@166 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-19 17:23:47 +00:00
Jean-Philippe Lang a8137d5d17 added back "fixed version" in issue/show and filters
git-svn-id: http://redmine.rubyforge.org/svn/trunk@165 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-15 20:22:23 +00:00
Jean-Philippe Lang 1e5362b1f7 added package icon on changelog
git-svn-id: http://redmine.rubyforge.org/svn/trunk@164 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-15 20:14:06 +00:00
Jean-Philippe Lang 51ea8b0295 tooltips added on calendar
git-svn-id: http://redmine.rubyforge.org/svn/trunk@161 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-13 10:36:18 +00:00
Jean-Philippe Lang f35194e604 tooltips added on Gantt chart to view the details of the issues
(added csshover behaviour to make it work under IE)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@160 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-09 21:27:21 +00:00
Jean-Philippe Lang 3ef57c1ac3 folder icon replaced
git-svn-id: http://redmine.rubyforge.org/svn/trunk@159 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-08 19:56:47 +00:00
Jean-Philippe Lang 69b7f9e9a2 folder icon removed
git-svn-id: http://redmine.rubyforge.org/svn/trunk@158 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-08 19:56:16 +00:00
Jean-Philippe Lang 220641909c * all icons replaced (new icons are based on GPL icon set: "KDE Crystal Diamond 2.5" -by paolino- and "kNeu! Alpha v0.1" -by Pablo Fabregat-)
* fixed: subprojects count is always 0 on projects list

git-svn-id: http://redmine.rubyforge.org/svn/trunk@157 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-08 19:21:59 +00:00
Jean-Philippe Lang 31bc6054a3 replaced details.png by zoom_in.png on reports
git-svn-id: http://redmine.rubyforge.org/svn/trunk@156 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-06 14:28:08 +00:00
Jean-Philippe Lang 94391723c0 added a button on users/list to manually activate an account
git-svn-id: http://redmine.rubyforge.org/svn/trunk@155 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-06 14:22:22 +00:00
Jean-Philippe Lang 5fd3decb16 added "auto" option for user's language (browser based)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@154 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-06 14:11:28 +00:00
Jean-Philippe Lang d7c015a884 removed "add a comment" h3
git-svn-id: http://redmine.rubyforge.org/svn/trunk@153 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-06 12:47:20 +00:00
Jean-Philippe Lang 8ed55e8d7a added total number of issues per tracker on projects/show
git-svn-id: http://redmine.rubyforge.org/svn/trunk@152 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-06 12:18:29 +00:00
Jean-Philippe Lang 979892a109 calendar javascripts and stylesheets removed from base layout. they're now only added to html header when needed.
git-svn-id: http://redmine.rubyforge.org/svn/trunk@151 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-06 09:08:17 +00:00
Jean-Philippe Lang 776d5ce554 XHTML compliance improvements
git-svn-id: http://redmine.rubyforge.org/svn/trunk@150 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-01-05 19:10:57 +00:00
748 changed files with 33669 additions and 11176 deletions
+25
View File
@@ -0,0 +1,25 @@
# 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 SysApi < ActionWebService::API::Base
api_method :projects,
:expects => [],
:returns => [[Project]]
api_method :repository_created,
:expects => [:int, :string],
:returns => [:int]
end
+22 -12
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -22,12 +22,17 @@ class AccountController < ApplicationController
# prevents login action to be filtered by check_if_login_required application scope filter # prevents login action to be filtered by check_if_login_required application scope filter
skip_before_filter :check_if_login_required, :only => [:login, :lost_password, :register] skip_before_filter :check_if_login_required, :only => [:login, :lost_password, :register]
before_filter :require_login, :except => [:show, :login, :lost_password, :register] before_filter :require_login, :only => :logout
# Show user's account # Show user's account
def show def show
@user = User.find(params[:id]) @user = User.find(params[:id])
@custom_values = @user.custom_values.find(:all, :include => :custom_field) @custom_values = @user.custom_values.find(:all, :include => :custom_field)
# 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? || (logged_in_user && logged_in_user.role_for_project(membership.project))
end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
@@ -42,6 +47,11 @@ class AccountController < ApplicationController
user = User.try_to_login(params[:login], params[:password]) user = User.try_to_login(params[:login], params[:password])
if user if user
self.logged_in_user = user self.logged_in_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
redirect_back_or_default :controller => 'my', :action => 'page' redirect_back_or_default :controller => 'my', :action => 'page'
else else
flash.now[:notice] = l(:notice_account_invalid_creditentials) flash.now[:notice] = l(:notice_account_invalid_creditentials)
@@ -51,15 +61,18 @@ class AccountController < ApplicationController
# Log out current user and redirect to welcome page # Log out current user and redirect to welcome page
def logout def logout
cookies.delete :autologin
Token.delete_all(["user_id = ? AND action = ?", logged_in_user.id, "autologin"]) if logged_in_user
self.logged_in_user = nil self.logged_in_user = nil
redirect_to :controller => '' redirect_to :controller => 'welcome'
end end
# Enable user to choose a new password # Enable user to choose a new password
def lost_password def lost_password
redirect_to :controller => 'welcome' and return unless Setting.lost_password?
if params[:token] if params[:token]
@token = Token.find_by_action_and_value("recovery", params[:token]) @token = Token.find_by_action_and_value("recovery", params[:token])
redirect_to :controller => '' and return unless @token and !@token.expired? redirect_to :controller => 'welcome' and return unless @token and !@token.expired?
@user = @token.user @user = @token.user
if request.post? if request.post?
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
@@ -82,8 +95,6 @@ class AccountController < ApplicationController
# create a new token for password recovery # create a new token for password recovery
token = Token.new(:user => user, :action => "recovery") token = Token.new(:user => user, :action => "recovery")
if token.save if token.save
# send token to user via email
Mailer.set_language_if_valid(user.language)
Mailer.deliver_lost_password(token) Mailer.deliver_lost_password(token)
flash[:notice] = l(:notice_account_lost_email_sent) flash[:notice] = l(:notice_account_lost_email_sent)
redirect_to :action => 'login' redirect_to :action => 'login'
@@ -95,12 +106,12 @@ class AccountController < ApplicationController
# User self-registration # User self-registration
def register def register
redirect_to :controller => '' and return if $RDM_SELF_REGISTRATION == false redirect_to :controller => 'welcome' and return unless Setting.self_registration?
if params[:token] if params[:token]
token = Token.find_by_action_and_value("register", params[:token]) token = Token.find_by_action_and_value("register", params[:token])
redirect_to :controller => '' and return unless token and !token.expired? redirect_to :controller => 'welcome' and return unless token and !token.expired?
user = token.user user = token.user
redirect_to :controller => '' and return unless user.status == User::STATUS_REGISTERED redirect_to :controller => 'welcome' and return unless user.status == User::STATUS_REGISTERED
user.status = User::STATUS_ACTIVE user.status = User::STATUS_ACTIVE
if user.save if user.save
token.destroy token.destroy
@@ -110,7 +121,7 @@ class AccountController < ApplicationController
end end
else else
if request.get? if request.get?
@user = User.new(:language => $RDM_DEFAULT_LANG) @user = User.new(:language => Setting.default_language)
@custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) } @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
else else
@user = User.new(params[:user]) @user = User.new(params[:user])
@@ -122,10 +133,9 @@ class AccountController < ApplicationController
@user.custom_values = @custom_values @user.custom_values = @custom_values
token = Token.new(:user => @user, :action => "register") token = Token.new(:user => @user, :action => "register")
if @user.save and token.save if @user.save and token.save
Mailer.set_language_if_valid(@user.language)
Mailer.deliver_register(token) Mailer.deliver_register(token)
flash[:notice] = l(:notice_account_register_done) flash[:notice] = l(:notice_account_register_done)
redirect_to :controller => '' redirect_to :controller => 'welcome' and return
end end
end end
end end
+13 -3
View File
@@ -28,11 +28,17 @@ class AdminController < ApplicationController
def projects def projects
sort_init 'name', 'asc' sort_init 'name', 'asc'
sort_update sort_update
@project_count = Project.count
@status = params[:status] ? params[:status].to_i : 0
conditions = nil
conditions = ["status=?", @status] unless @status == 0
@project_count = Project.count(:conditions => conditions)
@project_pages = Paginator.new self, @project_count, @project_pages = Paginator.new self, @project_count,
15, 25,
params['page'] params['page']
@projects = Project.find :all, :order => sort_clause, @projects = Project.find :all, :order => sort_clause,
:conditions => conditions,
:limit => @project_pages.items_per_page, :limit => @project_pages.items_per_page,
:offset => @project_pages.current.offset :offset => @project_pages.current.offset
@@ -51,6 +57,10 @@ class AdminController < ApplicationController
end end
def info def info
@adapter_name = ActiveRecord::Base.connection.adapter_name @db_adapter_name = ActiveRecord::Base.connection.adapter_name
@flags = Hash.new
@flags[:default_admin_changed] = User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?
@flags[:file_repository_writable] = File.writable?(Attachment.storage_path)
@flags[:textile_available] = ActionView::Helpers::TextHelper.method_defined? "textilize"
end end
end end
+55 -11
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,6 +17,11 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
before_filter :check_if_login_required, :set_localization before_filter :check_if_login_required, :set_localization
filter_parameter_logging :password
REDMINE_SUPPORTED_SCM.each do |scm|
require_dependency "repository/#{scm.underscore}"
end
def logged_in_user=(user) def logged_in_user=(user)
@logged_in_user = user @logged_in_user = user
@@ -31,9 +36,22 @@ class ApplicationController < ActionController::Base
end end
end end
# Returns the role that the logged in user has on the current project
# or nil if current user is not a member of the project
def logged_in_user_membership
@user_membership ||= logged_in_user.role_for_project(@project)
end
# check if login is globally required to access the application # check if login is globally required to access the application
def check_if_login_required def check_if_login_required
require_login if $RDM_LOGIN_REQUIRED # no check needed if user is already logged in
return true if logged_in_user
# auto-login feature
autologin_key = cookies[:autologin]
if autologin_key && Setting.autologin?
self.logged_in_user = User.find_by_autologin_key(autologin_key)
end
require_login if Setting.login_required?
end end
def set_localization def set_localization
@@ -48,7 +66,7 @@ class ApplicationController < ActionController::Base
end end
rescue rescue
nil nil
end || $RDM_DEFAULT_LANG end || Setting.default_language
set_language_if_valid(lang) set_language_if_valid(lang)
end end
@@ -64,7 +82,7 @@ class ApplicationController < ActionController::Base
def require_admin def require_admin
return unless require_login return unless require_login
unless self.logged_in_user.admin? unless self.logged_in_user.admin?
render :nothing => true, :status => 403 render_403
return false return false
end end
true true
@@ -72,6 +90,11 @@ class ApplicationController < ActionController::Base
# authorizes the user for the requested action. # authorizes the user for the requested action.
def authorize(ctrl = params[:controller], action = params[:action]) def authorize(ctrl = params[:controller], action = params[:action])
unless @project.active?
@project = nil
render_404
return false
end
# check if action is allowed on public projects # check if action is allowed on public projects
if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ ctrl, action ] if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ ctrl, action ]
return true return true
@@ -81,30 +104,51 @@ class ApplicationController < ActionController::Base
# admin is always authorized # admin is always authorized
return true if self.logged_in_user.admin? return true if self.logged_in_user.admin?
# if not admin, check membership permission # if not admin, check membership permission
@user_membership ||= Member.find(:first, :conditions => ["user_id=? and project_id=?", self.logged_in_user.id, @project.id]) if logged_in_user_membership and Permission.allowed_to_role( "%s/%s" % [ ctrl, action ], logged_in_user_membership )
if @user_membership and Permission.allowed_to_role( "%s/%s" % [ ctrl, action ], @user_membership.role_id )
return true return true
end end
render :nothing => true, :status => 403 render_403
false
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
def check_project_privacy
unless @project.active?
@project = nil
render_404
return false
end
return true if @project.is_public?
return false unless logged_in_user
return true if logged_in_user.admin? || logged_in_user_membership
render_403
false false
end end
# store current uri in session. # store current uri in session.
# return to this location by calling redirect_back_or_default # return to this location by calling redirect_back_or_default
def store_location def store_location
session[:return_to] = request.request_uri session[:return_to_params] = params
end end
# move to the last store_location call or to the passed default one # move to the last store_location call or to the passed default one
def redirect_back_or_default(default) def redirect_back_or_default(default)
if session[:return_to].nil? if session[:return_to_params].nil?
redirect_to default redirect_to default
else else
redirect_to_url session[:return_to] redirect_to session[:return_to_params]
session[:return_to] = nil session[:return_to_params] = nil
end end
end end
def render_403
@html_title = "403"
@project = nil
render :template => "common/403", :layout => true, :status => 403
return false
end
def render_404 def render_404
@html_title = "404" @html_title = "404"
render :template => "common/404", :layout => true, :status => 404 render :template => "common/404", :layout => true, :status => 404
+44
View File
@@ -0,0 +1,44 @@
# 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 AttachmentsController < ApplicationController
layout 'base'
before_filter :find_project, :check_project_privacy
# sends an attachment
def download
send_file @attachment.diskfile, :filename => @attachment.filename
rescue
render_404
end
# sends an image to be displayed inline
def show
render(:nothing => true, :status => 404) and return unless @attachment.diskfile =~ /\.(jpeg|jpg|gif|png)$/i
send_file @attachment.diskfile, :filename => @attachment.filename, :type => "image/#{$1}", :disposition => 'inline'
rescue
render_404
end
private
def find_project
@attachment = Attachment.find(params[:id])
@project = @attachment.project
rescue
render_404
end
end
+89
View File
@@ -0,0 +1,89 @@
# 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 BoardsController < ApplicationController
layout 'base'
before_filter :find_project
before_filter :authorize, :except => [:index, :show]
before_filter :check_project_privacy, :only => [:index, :show]
helper :messages
include MessagesHelper
helper :sort
include SortHelper
helper :watchers
include WatchersHelper
def index
@boards = @project.boards
# show the board if there is only one
if @boards.size == 1
@board = @boards.first
show
render :action => 'show'
end
end
def show
sort_init "#{Message.table_name}.updated_on", "desc"
sort_update
@topic_count = @board.topics.count
@topic_pages = Paginator.new self, @topic_count, 25, params['page']
@topics = @board.topics.find :all, :order => sort_clause,
:include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset
render :action => 'show', :layout => false if request.xhr?
end
verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
def new
@board = Board.new(params[:board])
@board.project = @project
if request.post? && @board.save
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end
end
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
def destroy
@board.destroy
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end
private
def find_project
@project = Project.find(params[:project_id])
@board = @project.boards.find(params[:id]) if params[:id]
rescue ActiveRecord::RecordNotFound
render_404
end
end
+8 -7
View File
@@ -25,7 +25,8 @@ class CustomFieldsController < ApplicationController
end end
def list def list
@custom_field_pages, @custom_fields = paginate :custom_fields, :per_page => 15 @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.type.to_s }
@tab = params[:tab] || 'IssueCustomField'
render :action => "list", :layout => false if request.xhr? render :action => "list", :layout => false if request.xhr?
end end
@@ -44,9 +45,9 @@ class CustomFieldsController < ApplicationController
end end
if request.post? and @custom_field.save if request.post? and @custom_field.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list' redirect_to :action => 'list', :tab => @custom_field.type
end end
@trackers = Tracker.find(:all) @trackers = Tracker.find(:all, :order => 'position')
end end
def edit def edit
@@ -56,14 +57,14 @@ class CustomFieldsController < ApplicationController
@custom_field.trackers = params[:tracker_ids] ? Tracker.find(params[:tracker_ids]) : [] @custom_field.trackers = params[:tracker_ids] ? Tracker.find(params[:tracker_ids]) : []
end end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list' redirect_to :action => 'list', :tab => @custom_field.type
end end
@trackers = Tracker.find(:all) @trackers = Tracker.find(:all, :order => 'position')
end end
def destroy def destroy
CustomField.find(params[:id]).destroy @custom_field = CustomField.find(params[:id]).destroy
redirect_to :action => 'list' redirect_to :action => 'list', :tab => @custom_field.type
rescue rescue
flash[:notice] = "Unable to delete custom field" flash[:notice] = "Unable to delete custom field"
redirect_to :action => 'list' redirect_to :action => 'list'
+7 -3
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -46,9 +46,13 @@ class DocumentsController < ApplicationController
def add_attachment def add_attachment
# Save the attachments # Save the attachments
params[:attachments].each { |a| @attachments = []
Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0 params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @document, :file => file, :author => logged_in_user)
@attachments << a unless a.new_record?
} if params[:attachments] and params[:attachments].is_a? Array } if params[:attachments] and params[:attachments].is_a? Array
Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @document redirect_to :action => 'show', :id => @document
end end
+76 -3
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -16,10 +16,83 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class FeedsController < ApplicationController class FeedsController < ApplicationController
before_filter :find_scope
session :off session :off
helper :issues
include IssuesHelper
helper :custom_fields
include CustomFieldsHelper
# news feeds
def news def news
@news = News.find :all, :order => 'news.created_on DESC', :limit => 10, :include => [ :author, :project ] News.with_scope(:find => @find_options) do
@headers["Content-Type"] = "application/rss+xml" @news = News.find :all, :order => "#{News.table_name}.created_on DESC", :include => [ :author, :project ]
end
headers["Content-Type"] = "application/rss+xml"
render :action => 'news_atom' if 'atom' == params[:format]
end
# issue feeds
def issues
if @project && params[:query_id]
query = Query.find(params[:query_id])
query.executed_by = @user
# ignore query if it's not valid
query = nil unless query.valid?
# override with query conditions
@find_options[:conditions] = query.statement if query.valid? and @project == query.project
end
Issue.with_scope(:find => @find_options) do
@issues = Issue.find :all, :include => [:project, :author, :tracker, :status, :custom_values],
:order => "#{Issue.table_name}.created_on DESC"
end
@title = (@project ? @project.name : Setting.app_title) + ": " + (query ? query.name : l(:label_reported_issues))
headers["Content-Type"] = "application/rss+xml"
render :action => 'issues_atom' if 'atom' == params[:format]
end
# issue changes feeds
def history
if @project && params[:query_id]
query = Query.find(params[:query_id])
query.executed_by = @user
# ignore query if it's not valid
query = nil unless query.valid?
# override with query conditions
@find_options[:conditions] = query.statement if query.valid? and @project == query.project
end
Journal.with_scope(:find => @find_options) do
@journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status, :custom_values]} ],
:order => "#{Journal.table_name}.created_on DESC"
end
@title = (@project ? @project.name : Setting.app_title) + ": " + (query ? query.name : l(:label_reported_issues))
headers["Content-Type"] = "application/rss+xml"
render :action => 'history_atom' if 'atom' == params[:format]
end
private
# override for feeds specific authentication
def check_if_login_required
@user = User.find_by_rss_key(params[:key])
render(:nothing => true, :status => 403) and return false if !@user && Setting.login_required?
end
def find_scope
if params[:project_id]
# project feed
# check if project is public or if the user is a member
@project = Project.find(params[:project_id])
render(:nothing => true, :status => 403) and return false unless @project.is_public? || (@user && @user.role_for_project(@project))
scope = ["#{Project.table_name}.id=?", params[:project_id].to_i]
else
# global feed
scope = ["#{Project.table_name}.is_public=?", true]
end
@find_options = {:conditions => scope, :limit => Setting.feeds_limit.to_i}
return true
end end
end end
+2 -5
View File
@@ -33,11 +33,8 @@ class HelpController < ApplicationController
# choose language according to available help translations # choose language according to available help translations
lang = (@help_config['langs'].include? current_language.to_s) ? current_language.to_s : @help_config['langs'].first lang = (@help_config['langs'].include? current_language.to_s) ? current_language.to_s : @help_config['langs'].first
if template url = "/manual/#{lang}/" + (template || "index.html")
redirect_to "/manual/#{lang}/#{template}" redirect_to(request.relative_url_root + url)
else
redirect_to "/manual/#{lang}/index.html"
end
end end
private private
@@ -16,22 +16,22 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueCategoriesController < ApplicationController class IssueCategoriesController < ApplicationController
layout 'base' layout 'base'
before_filter :find_project, :authorize before_filter :find_project, :authorize
def edit def edit
if request.post? and @category.update_attributes(params[:category]) if request.post? and @category.update_attributes(params[:category])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
end end
end end
def destroy def destroy
@category.destroy @category.destroy
redirect_to :controller => 'projects', :action => 'settings', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
rescue rescue
flash[:notice] = "Categorie can't be deleted" flash[:notice] = "Categorie can't be deleted"
redirect_to :controller => 'projects', :action => 'settings', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
end end
private private
@@ -0,0 +1,59 @@
# 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 IssueRelationsController < ApplicationController
layout 'base'
before_filter :find_project, :authorize
def new
@relation = IssueRelation.new(params[:relation])
@relation.issue_from = @issue
@relation.save if request.post?
respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js do
render :update do |page|
page.replace_html "relations", :partial => 'issues/relations'
if @relation.errors.empty?
page << "$('relation_delay').value = ''"
page << "$('relation_issue_to_id').value = ''"
end
end
end
end
end
def destroy
relation = IssueRelation.find(params[:id])
if request.post? && @issue.relations.include?(relation)
relation.destroy
@issue.reload
end
respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js { render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'} }
end
end
private
def find_project
@issue = Issue.find(params[:issue_id])
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
end
+21 -5
View File
@@ -16,8 +16,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueStatusesController < ApplicationController class IssueStatusesController < ApplicationController
layout 'base' layout 'base'
before_filter :require_admin before_filter :require_admin
verify :method => :post, :only => [ :destroy, :create, :update, :move ],
:redirect_to => { :action => :list }
def index def index
list list
@@ -25,7 +28,7 @@ class IssueStatusesController < ApplicationController
end end
def list def list
@issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 10 @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
render :action => "list", :layout => false if request.xhr? render :action => "list", :layout => false if request.xhr?
end end
@@ -57,6 +60,21 @@ class IssueStatusesController < ApplicationController
end end
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 def destroy
IssueStatus.find(params[:id]).destroy IssueStatus.find(params[:id]).destroy
redirect_to :action => 'list' redirect_to :action => 'list'
@@ -64,6 +82,4 @@ class IssueStatusesController < ApplicationController
flash[:notice] = "Unable to delete issue status" flash[:notice] = "Unable to delete issue status"
redirect_to :action => 'list' redirect_to :action => 'list'
end end
end end
+51 -28
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,27 +19,37 @@ class IssuesController < ApplicationController
layout 'base', :except => :export_pdf layout 'base', :except => :export_pdf
before_filter :find_project, :authorize before_filter :find_project, :authorize
cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
helper :projects
include ProjectsHelper
helper :custom_fields helper :custom_fields
include CustomFieldsHelper include CustomFieldsHelper
helper :ifpdf helper :ifpdf
include IfpdfHelper include IfpdfHelper
helper :issue_relations
include IssueRelationsHelper
helper :watchers
include WatchersHelper
helper :attachments
include AttachmentsHelper
def show def show
@status_options = @issue.status.workflows.find(:all, :include => :new_status, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
@custom_values = @issue.custom_values.find(:all, :include => :custom_field) @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
@journals_count = @issue.journals.count @journals_count = @issue.journals.count
@journals = @issue.journals.find(:all, :include => [:user, :details], :limit => 15, :order => "journals.created_on desc") @journals = @issue.journals.find(:all, :include => [:user, :details], :limit => 15, :order => "#{Journal.table_name}.created_on desc")
end end
def history def history
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "journals.created_on desc") @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on desc")
@journals_count = @journals.length @journals_count = @journals.length
end end
def export_pdf def export_pdf
@custom_values = @issue.custom_values.find(:all, :include => :custom_field) @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
@options_for_rfpdf ||= {} @options_for_rfpdf ||= {}
@options_for_rfpdf[:file_name] = "#{@project.name}_#{@issue.long_id}.pdf" @options_for_rfpdf[:file_name] = "#{@project.name}_#{@issue.id}.pdf"
end end
def edit def edit
@@ -67,9 +77,6 @@ class IssuesController < ApplicationController
def add_note def add_note
unless params[:notes].empty? unless params[:notes].empty?
journal = @issue.init_journal(self.logged_in_user, params[:notes]) journal = @issue.init_journal(self.logged_in_user, params[:notes])
#@history = @issue.histories.build(params[:history])
#@history.author_id = self.logged_in_user.id if self.logged_in_user
#@history.status = @issue.status
if @issue.save if @issue.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled? Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
@@ -82,20 +89,29 @@ class IssuesController < ApplicationController
end end
def change_status def change_status
#@history = @issue.histories.build(params[:history]) @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
@status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
@new_status = IssueStatus.find(params[:new_status_id]) @new_status = IssueStatus.find(params[:new_status_id])
if params[:confirm] if params[:confirm]
begin begin
#@history.author_id = self.logged_in_user.id if self.logged_in_user
#@issue.status = @history.status
#@issue.fixed_version_id = (params[:issue][:fixed_version_id])
#@issue.assigned_to_id = (params[:issue][:assigned_to_id])
#@issue.done_ratio = (params[:issue][:done_ratio])
#@issue.lock_version = (params[:issue][:lock_version])
journal = @issue.init_journal(self.logged_in_user, params[:notes]) journal = @issue.init_journal(self.logged_in_user, params[:notes])
@issue.status = @new_status @issue.status = @new_status
if @issue.update_attributes(params[:issue]) if @issue.update_attributes(params[:issue])
# Save attachments
params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
journal.details << JournalDetail.new(:property => 'attachment',
:prop_key => a.id,
:value => a.filename) unless a.new_record?
} if params[:attachments] and params[:attachments].is_a? Array
# Log time
if logged_in_user.authorized_to(@project, "timelog/edit")
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
@time_entry.attributes = params[:time_entry]
@time_entry.save
end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled? Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @issue redirect_to :action => 'show', :id => @issue
@@ -106,6 +122,7 @@ class IssuesController < ApplicationController
end end
end end
@assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user } @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
@activities = Enumeration::get_values('ACTI')
end end
def destroy def destroy
@@ -115,26 +132,32 @@ class IssuesController < ApplicationController
def add_attachment def add_attachment
# Save the attachments # Save the attachments
params[:attachments].each { |a| @attachments = []
@attachment = @issue.attachments.build(:file => a, :author => self.logged_in_user) unless a.size == 0 journal = @issue.init_journal(self.logged_in_user)
@attachment.save params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
@attachments << a unless a.new_record?
journal.details << JournalDetail.new(:property => 'attachment',
:prop_key => a.id,
:value => a.filename) unless a.new_record?
} if params[:attachments] and params[:attachments].is_a? Array } if params[:attachments] and params[:attachments].is_a? Array
journal.save if journal.details.any?
Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @issue redirect_to :action => 'show', :id => @issue
end end
def destroy_attachment def destroy_attachment
@issue.attachments.find(params[:attachment_id]).destroy a = @issue.attachments.find(params[:attachment_id])
a.destroy
journal = @issue.init_journal(self.logged_in_user)
journal.details << JournalDetail.new(:property => 'attachment',
:prop_key => a.id,
:old_value => a.filename)
journal.save
redirect_to :action => 'show', :id => @issue redirect_to :action => 'show', :id => @issue
end end
# Send the file in stream mode
def download
@attachment = @issue.attachments.find(params[:attachment_id])
send_file @attachment.diskfile, :filename => @attachment.filename
rescue
render_404
end
private private
def find_project def find_project
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
+17 -14
View File
@@ -16,21 +16,25 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class MembersController < ApplicationController class MembersController < ApplicationController
layout 'base' layout 'base'
before_filter :find_project, :authorize before_filter :find_project, :authorize
def edit def edit
if request.post? and @member.update_attributes(params[:member]) if request.post? and @member.update_attributes(params[:member])
flash[:notice] = l(:notice_successful_update) respond_to do |format|
redirect_to :controller => 'projects', :action => 'settings', :id => @project format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
end format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/members'} }
end end
end
end
def destroy def destroy
@member.destroy @member.destroy
flash[:notice] = l(:notice_successful_delete) respond_to do |format|
redirect_to :controller => 'projects', :action => 'settings', :id => @project format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
end format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/members'} }
end
end
private private
def find_project def find_project
@@ -39,5 +43,4 @@ private
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
end end
+62
View File
@@ -0,0 +1,62 @@
# 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 MessagesController < ApplicationController
layout 'base'
before_filter :find_project, :check_project_privacy
before_filter :require_login, :only => [:new, :reply]
verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
helper :attachments
include AttachmentsHelper
def show
@reply = Message.new(:subject => "RE: #{@message.subject}")
render :action => "show", :layout => false if request.xhr?
end
def new
@message = Message.new(params[:message])
@message.author = logged_in_user
@message.board = @board
if request.post? && @message.save
params[:attachments].each { |file|
next unless file.size > 0
Attachment.create(:container => @message, :file => file, :author => logged_in_user)
} if params[:attachments] and params[:attachments].is_a? Array
redirect_to :action => 'show', :id => @message
end
end
def reply
@reply = Message.new(params[:reply])
@reply.author = logged_in_user
@reply.board = @board
@message.children << @reply
redirect_to :action => 'show', :id => @message
end
private
def find_project
@board = Board.find(params[:board_id], :include => :project)
@project = @board.project
@message = @board.topics.find(params[:id]) if params[:id]
rescue ActiveRecord::RecordNotFound
render_404
end
end
+13 -7
View File
@@ -19,13 +19,18 @@ class MyController < ApplicationController
layout 'base' layout 'base'
before_filter :require_login before_filter :require_login
BLOCKS = { 'issues_assigned_to_me' => :label_assigned_to_me_issues, BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
'issues_reported_by_me' => :label_reported_issues, 'issuesreportedbyme' => :label_reported_issues,
'latest_news' => :label_news_latest, 'issueswatched' => :label_watched_issues,
'news' => :label_news_latest,
'calendar' => :label_calendar, 'calendar' => :label_calendar,
'documents' => :label_document_plural 'documents' => :label_document_plural
}.freeze }.freeze
DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
'right' => ['issuesreportedbyme']
}.freeze
verify :xhr => true, verify :xhr => true,
:session => :page_layout, :session => :page_layout,
:only => [:add_block, :remove_block, :order_blocks] :only => [:add_block, :remove_block, :order_blocks]
@@ -38,7 +43,7 @@ class MyController < ApplicationController
# Show user's page # Show user's page
def page def page
@user = self.logged_in_user @user = self.logged_in_user
@blocks = @user.pref[:my_page_layout] || { 'left' => ['issues_assigned_to_me'], 'right' => ['issues_reported_by_me'] } @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
end end
# Edit user's account # Edit user's account
@@ -47,7 +52,7 @@ class MyController < ApplicationController
@pref = @user.pref @pref = @user.pref
@user.attributes = params[:user] @user.attributes = params[:user]
@user.pref.attributes = params[:pref] @user.pref.attributes = params[:pref]
if request.post? and @user.save if request.post? and @user.save and @user.pref.save
set_localization set_localization
flash.now[:notice] = l(:notice_account_updated) flash.now[:notice] = l(:notice_account_updated)
self.logged_in_user.reload self.logged_in_user.reload
@@ -75,7 +80,7 @@ class MyController < ApplicationController
# User's page layout configuration # User's page layout configuration
def page_layout def page_layout
@user = self.logged_in_user @user = self.logged_in_user
@blocks = @user.pref[:my_page_layout] || { 'left' => ['issues_assigned_to_me'], 'right' => ['issues_reported_by_me'] } @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
session[:page_layout] = @blocks session[:page_layout] = @blocks
%w(top left right).each {|f| session[:page_layout][f] ||= [] } %w(top left right).each {|f| session[:page_layout][f] ||= [] }
@block_options = [] @block_options = []
@@ -86,8 +91,9 @@ class MyController < ApplicationController
# The block is added on top of the page # The block is added on top of the page
# params[:block] : id of the block to add # params[:block] : id of the block to add
def add_block def add_block
@user = self.logged_in_user
block = params[:block] block = params[:block]
render(:nothing => true) and return unless block && (BLOCKS.keys.include? block)
@user = self.logged_in_user
# remove if already present in a group # 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| (session[:page_layout][f] ||= []).delete block }
# add it on top # add it on top
+6 -6
View File
@@ -16,8 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class NewsController < ApplicationController class NewsController < ApplicationController
layout 'base' layout 'base'
before_filter :find_project, :authorize before_filter :find_project, :authorize
def show def show
end end
@@ -45,10 +45,10 @@ class NewsController < ApplicationController
redirect_to :action => 'show', :id => @news redirect_to :action => 'show', :id => @news
end end
def destroy def destroy
@news.destroy @news.destroy
redirect_to :controller => 'projects', :action => 'list_news', :id => @project redirect_to :controller => 'projects', :action => 'list_news', :id => @project
end end
private private
def find_project def find_project
+222 -92
View File
@@ -15,10 +15,16 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'csv'
class ProjectsController < ApplicationController class ProjectsController < ApplicationController
layout 'base' layout 'base'
before_filter :find_project, :authorize, :except => [ :index, :list, :add ] before_filter :find_project, :except => [ :index, :list, :add ]
before_filter :require_admin, :only => [ :add, :destroy ] before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
cache_sweeper :issue_sweeper, :only => [ :add_issue ]
helper :sort helper :sort
include SortHelper include SortHelper
@@ -29,6 +35,8 @@ class ProjectsController < ApplicationController
helper IssuesHelper helper IssuesHelper
helper :queries helper :queries
include QueriesHelper include QueriesHelper
helper :repositories
include RepositoriesHelper
def index def index
list list
@@ -37,14 +45,15 @@ class ProjectsController < ApplicationController
# Lists public projects # Lists public projects
def list def list
sort_init 'name', 'asc' sort_init "#{Project.table_name}.name", "asc"
sort_update sort_update
@project_count = Project.count(["is_public=?", true]) @project_count = Project.count(:all, :conditions => Project.visible_by(logged_in_user))
@project_pages = Paginator.new self, @project_count, @project_pages = Paginator.new self, @project_count,
15, 15,
params['page'] params['page']
@projects = Project.find :all, :order => sort_clause, @projects = Project.find :all, :order => sort_clause,
:conditions => ["is_public=?", true], :conditions => Project.visible_by(logged_in_user),
:include => :parent,
:limit => @project_pages.items_per_page, :limit => @project_pages.items_per_page,
:offset => @project_pages.current.offset :offset => @project_pages.current.offset
@@ -63,9 +72,13 @@ class ProjectsController < ApplicationController
@custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) } @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
@project.custom_values = @custom_values @project.custom_values = @custom_values
if params[:repository_enabled] && params[:repository_enabled] == "1" if params[:repository_enabled] && params[:repository_enabled] == "1"
@project.repository = Repository.new @project.repository = Repository.factory(params[:repository_scm])
@project.repository.attributes = params[:repository] @project.repository.attributes = params[:repository]
end end
if "1" == params[:wiki_enabled]
@project.wiki = Wiki.new
@project.wiki.attributes = params[:wiki]
end
if @project.save if @project.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'admin', :action => 'projects' redirect_to :controller => 'admin', :action => 'projects'
@@ -76,10 +89,12 @@ class ProjectsController < ApplicationController
# Show @project # Show @project
def show def show
@custom_values = @project.custom_values.find(:all, :include => :custom_field) @custom_values = @project.custom_values.find(:all, :include => :custom_field)
@members = @project.members.find(:all, :include => [:user, :role]) @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
@subprojects = @project.children if @project.children_count > 0 @subprojects = @project.active_children
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "news.created_on DESC") @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@trackers = Tracker.find(:all) @trackers = Tracker.find(:all, :order => 'position')
@open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false])
@total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
end end
def settings def settings
@@ -87,8 +102,6 @@ class ProjectsController < ApplicationController
@custom_fields = IssueCustomField.find(:all) @custom_fields = IssueCustomField.find(:all)
@issue_category ||= IssueCategory.new @issue_category ||= IssueCategory.new
@member ||= @project.members.new @member ||= @project.members.new
@roles = Role.find(:all)
@users = User.find(:all) - @project.members.find(:all, :include => :user).collect{|m| m.user }
@custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) } @custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
end end
@@ -105,8 +118,17 @@ class ProjectsController < ApplicationController
when "0" when "0"
@project.repository = nil @project.repository = nil
when "1" when "1"
@project.repository ||= Repository.new @project.repository ||= Repository.factory(params[:repository_scm])
@project.repository.attributes = params[:repository] @project.repository.update_attributes params[:repository] if @project.repository
end
end
if params[:wiki_enabled]
case params[:wiki_enabled]
when "0"
@project.wiki.destroy if @project.wiki
when "1"
@project.wiki ||= Wiki.new
@project.wiki.update_attributes params[:wiki]
end end
end end
@project.attributes = params[:project] @project.attributes = params[:project]
@@ -120,25 +142,33 @@ class ProjectsController < ApplicationController
end end
end end
def archive
@project.archive if request.post? && @project.active?
redirect_to :controller => 'admin', :action => 'projects'
end
def unarchive
@project.unarchive if request.post? && !@project.active?
redirect_to :controller => 'admin', :action => 'projects'
end
# Delete @project # Delete @project
def destroy def destroy
@project_to_destroy = @project
if request.post? and params[:confirm] if request.post? and params[:confirm]
@project.destroy @project_to_destroy.destroy
redirect_to :controller => 'admin', :action => 'projects' redirect_to :controller => 'admin', :action => 'projects'
end end
# hide project in layout
@project = nil
end end
# Add a new issue category to @project # Add a new issue category to @project
def add_issue_category def add_issue_category
if request.post? @category = @project.issue_categories.build(params[:category])
@issue_category = @project.issue_categories.build(params[:issue_category]) if request.post? and @category.save
if @issue_category.save flash[:notice] = l(:notice_successful_create)
flash[:notice] = l(:notice_successful_create) redirect_to :action => 'settings', :tab => 'categories', :id => @project
redirect_to :action => 'settings', :id => @project
else
settings
render :action => 'settings'
end
end end
end end
@@ -147,27 +177,27 @@ class ProjectsController < ApplicationController
@version = @project.versions.build(params[:version]) @version = @project.versions.build(params[:version])
if request.post? and @version.save if request.post? and @version.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'settings', :id => @project redirect_to :action => 'settings', :tab => 'versions', :id => @project
end end
end end
# Add a new member to @project # Add a new member to @project
def add_member def add_member
@member = @project.members.build(params[:member]) @member = @project.members.build(params[:member])
if request.post? if request.post? && @member.save
if @member.save respond_to do |format|
flash[:notice] = l(:notice_successful_create) format.html { redirect_to :action => 'settings', :tab => 'members', :id => @project }
redirect_to :action => 'settings', :id => @project format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'members'} }
else
settings
render :action => 'settings'
end end
else
settings
render :action => 'settings'
end end
end end
# Show members list of @project # Show members list of @project
def list_members def list_members
@members = @project.members @members = @project.members.find(:all)
end end
# Add a new document to @project # Add a new document to @project
@@ -180,6 +210,7 @@ class ProjectsController < ApplicationController
Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
} if params[:attachments] and params[:attachments].is_a? Array } if params[:attachments] and params[:attachments].is_a? Array
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
Mailer.deliver_document_add(@document) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'list_documents', :id => @project redirect_to :action => 'list_documents', :id => @project
end end
end end
@@ -193,12 +224,25 @@ class ProjectsController < ApplicationController
def add_issue def add_issue
@tracker = Tracker.find(params[:tracker_id]) @tracker = Tracker.find(params[:tracker_id])
@priorities = Enumeration::get_values('IPRI') @priorities = Enumeration::get_values('IPRI')
default_status = IssueStatus.default
unless default_status
flash.now[:notice] = 'No default issue status defined. Please check your configuration.'
render :nothing => true, :layout => true
return
end
@issue = Issue.new(:project => @project, :tracker => @tracker) @issue = Issue.new(:project => @project, :tracker => @tracker)
@issue.status = default_status
@allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
if request.get? if request.get?
@issue.start_date = Date.today @issue.start_date = Date.today
@custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) }
else else
@issue.attributes = params[:issue] @issue.attributes = params[:issue]
requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
@issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
@issue.author_id = self.logged_in_user.id if self.logged_in_user @issue.author_id = self.logged_in_user.id if self.logged_in_user
# Multiple file upload # Multiple file upload
@attachments = [] @attachments = []
@@ -218,7 +262,7 @@ class ProjectsController < ApplicationController
# Show filtered/sorted issues list of @project # Show filtered/sorted issues list of @project
def list_issues def list_issues
sort_init 'issues.id', 'desc' sort_init "#{Issue.table_name}.id", "desc"
sort_update sort_update
retrieve_query retrieve_query
@@ -232,46 +276,70 @@ class ProjectsController < ApplicationController
end end
if @query.valid? if @query.valid?
@issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement) @issue_count = Issue.count(:include => [:status, :project, :custom_values], :conditions => @query.statement)
@issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page'] @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
@issues = Issue.find :all, :order => sort_clause, @issues = Issue.find :all, :order => sort_clause,
:include => [ :author, :status, :tracker, :project ], :include => [ :assigned_to, :status, :tracker, :project, :priority, :custom_values ],
:conditions => @query.statement, :conditions => @query.statement,
:limit => @issue_pages.items_per_page, :limit => @issue_pages.items_per_page,
:offset => @issue_pages.current.offset :offset => @issue_pages.current.offset
end end
@trackers = Tracker.find :all
render :layout => false if request.xhr? render :layout => false if request.xhr?
end end
# Export filtered/sorted issues list to CSV # Export filtered/sorted issues list to CSV
def export_issues_csv def export_issues_csv
sort_init 'issues.id', 'desc' sort_init "#{Issue.table_name}.id", "desc"
sort_update sort_update
retrieve_query retrieve_query
render :action => 'list_issues' and return unless @query.valid? render :action => 'list_issues' and return unless @query.valid?
@issues = Issue.find :all, :order => sort_clause, @issues = Issue.find :all, :order => sort_clause,
:include => [ :author, :status, :tracker, :project, :custom_values ], :include => [ :assigned_to, :author, :status, :tracker, :priority, :project, {:custom_values => :custom_field} ],
:conditions => @query.statement :conditions => @query.statement,
:limit => Setting.issues_export_limit.to_i
ic = Iconv.new('ISO-8859-1', 'UTF-8') ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
export = StringIO.new export = StringIO.new
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv| CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
# csv header fields # csv header fields
headers = [ "#", l(:field_status), l(:field_tracker), l(:field_subject), l(:field_author), l(:field_created_on), l(:field_updated_on) ] headers = [ "#", l(:field_status),
l(:field_project),
l(:field_tracker),
l(:field_priority),
l(:field_subject),
l(:field_assigned_to),
l(:field_author),
l(:field_start_date),
l(:field_due_date),
l(:field_done_ratio),
l(:field_created_on),
l(:field_updated_on)
]
for custom_field in @project.all_custom_fields for custom_field in @project.all_custom_fields
headers << custom_field.name headers << custom_field.name
end end
csv << headers.collect {|c| ic.iconv(c) } csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
# csv lines # csv lines
@issues.each do |issue| @issues.each do |issue|
fields = [issue.id, issue.status.name, issue.tracker.name, issue.subject, issue.author.display_name, l_datetime(issue.created_on), l_datetime(issue.updated_on)] fields = [issue.id, issue.status.name,
issue.project.name,
issue.tracker.name,
issue.priority.name,
issue.subject,
(issue.assigned_to ? issue.assigned_to.name : ""),
issue.author.name,
issue.start_date ? l_date(issue.start_date) : nil,
issue.due_date ? l_date(issue.due_date) : nil,
issue.done_ratio,
l_datetime(issue.created_on),
l_datetime(issue.updated_on)
]
for custom_field in @project.all_custom_fields for custom_field in @project.all_custom_fields
fields << (show_value issue.custom_value_for(custom_field)) fields << (show_value issue.custom_value_for(custom_field))
end end
csv << fields.collect {|c| ic.iconv(c.to_s) } csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
end end
end end
export.rewind export.rewind
@@ -280,15 +348,16 @@ class ProjectsController < ApplicationController
# Export filtered/sorted issues to PDF # Export filtered/sorted issues to PDF
def export_issues_pdf def export_issues_pdf
sort_init 'issues.id', 'desc' sort_init "#{Issue.table_name}.id", "desc"
sort_update sort_update
retrieve_query retrieve_query
render :action => 'list_issues' and return unless @query.valid? render :action => 'list_issues' and return unless @query.valid?
@issues = Issue.find :all, :order => sort_clause, @issues = Issue.find :all, :order => sort_clause,
:include => [ :author, :status, :tracker, :project, :custom_values ], :include => [ :author, :status, :tracker, :priority, :project, :custom_values ],
:conditions => @query.statement :conditions => @query.statement,
:limit => Setting.issues_export_limit.to_i
@options_for_rfpdf ||= {} @options_for_rfpdf ||= {}
@options_for_rfpdf[:file_name] = "export.pdf" @options_for_rfpdf[:file_name] = "export.pdf"
@@ -300,7 +369,7 @@ class ProjectsController < ApplicationController
redirect_to :action => 'list_issues', :id => @project and return unless @issues redirect_to :action => 'list_issues', :id => @project and return unless @issues
@projects = [] @projects = []
# find projects to which the user is allowed to move the issue # find projects to which the user is allowed to move the issue
@logged_in_user.memberships.each {|m| @projects << m.project if Permission.allowed_to_role("projects/move_issues", m.role_id)} @logged_in_user.memberships.each {|m| @projects << m.project if Permission.allowed_to_role("projects/move_issues", m.role)}
# issue can be moved to any tracker # issue can be moved to any tracker
@trackers = Tracker.find(:all) @trackers = Tracker.find(:all)
if request.post? and params[:new_project_id] and params[:new_tracker_id] if request.post? and params[:new_project_id] and params[:new_tracker_id]
@@ -311,6 +380,9 @@ class ProjectsController < ApplicationController
unless i.project_id == new_project.id unless i.project_id == new_project.id
i.category = nil i.category = nil
i.fixed_version = nil i.fixed_version = nil
# delete issue relations
i.relations_from.clear
i.relations_to.clear
end end
# move the issue # move the issue
i.project = new_project i.project = new_project
@@ -322,22 +394,6 @@ class ProjectsController < ApplicationController
end end
end end
def add_query
@query = Query.new(params[:query])
@query.project = @project
@query.user = logged_in_user
params[:fields].each do |field|
@query.add_filter(field, params[:operators][field], params[:values][field])
end if params[:fields]
if request.post? and @query.save
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'reports', :action => 'issue_report', :id => @project
end
render :layout => false if request.xhr?
end
# Add a news to @project # Add a news to @project
def add_news def add_news
@news = News.new(:project => @project) @news = News.new(:project => @project)
@@ -353,7 +409,7 @@ class ProjectsController < ApplicationController
# Show news list of @project # Show news list of @project
def list_news def list_news
@news_pages, @news = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "news.created_on DESC" @news_pages, @news = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "#{News.table_name}.created_on DESC"
render :action => "list_news", :layout => false if request.xhr? render :action => "list_news", :layout => false if request.xhr?
end end
@@ -361,33 +417,34 @@ class ProjectsController < ApplicationController
if request.post? if request.post?
@version = @project.versions.find_by_id(params[:version_id]) @version = @project.versions.find_by_id(params[:version_id])
# Save the attachments # Save the attachments
params[:attachments].each { |a| @attachments = []
Attachment.create(:container => @version, :file => a, :author => logged_in_user) unless a.size == 0 params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
@attachments << a unless a.new_record?
} if params[:attachments] and params[:attachments].is_a? Array } if params[:attachments] and params[:attachments].is_a? Array
Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :controller => 'projects', :action => 'list_files', :id => @project redirect_to :controller => 'projects', :action => 'list_files', :id => @project
end end
@versions = @project.versions @versions = @project.versions.sort
end end
def list_files def list_files
@versions = @project.versions @versions = @project.versions.sort
end end
# Show changelog for @project # Show changelog for @project
def changelog def changelog
@trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true]) @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
if request.get? retrieve_selected_tracker_ids(@trackers)
@selected_tracker_ids = @trackers.collect {|t| t.id.to_s } @versions = @project.versions.sort
else end
@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 def roadmap
@selected_tracker_ids ||= [] @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
@fixed_issues = @project.issues.find(:all, retrieve_selected_tracker_ids(@trackers)
:include => [ :fixed_version, :status, :tracker ], conditions = ("1" == params[:completed] ? nil : [ "#{Version.table_name}.effective_date > ? OR #{Version.table_name}.effective_date IS NULL", Date.today])
:conditions => [ "issue_statuses.is_closed=? and issues.tracker_id in (#{@selected_tracker_ids.join(',')}) and issues.fixed_version_id is not null", true], @versions = @project.versions.find(:all, :conditions => conditions).sort
:order => "versions.effective_date DESC, issues.id DESC"
) unless @selected_tracker_ids.empty?
@fixed_issues ||= []
end end
def activity def activity
@@ -401,12 +458,12 @@ class ProjectsController < ApplicationController
@month ||= Date.today.month @month ||= Date.today.month
@date_from = Date.civil(@year, @month, 1) @date_from = Date.civil(@year, @month, 1)
@date_to = (@date_from >> 1)-1 @date_to = @date_from >> 1
@events_by_day = {} @events_by_day = {}
unless params[:show_issues] == "0" unless params[:show_issues] == "0"
@project.issues.find(:all, :include => [:author, :status], :conditions => ["issues.created_on>=? and issues.created_on<=?", @date_from, @date_to] ).each { |i| @project.issues.find(:all, :include => [:author], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] ).each { |i|
@events_by_day[i.created_on.to_date] ||= [] @events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i @events_by_day[i.created_on.to_date] << i
} }
@@ -414,7 +471,7 @@ class ProjectsController < ApplicationController
end end
unless params[:show_news] == "0" unless params[:show_news] == "0"
@project.news.find(:all, :conditions => ["news.created_on>=? and news.created_on<=?", @date_from, @date_to], :include => :author ).each { |i| @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author ).each { |i|
@events_by_day[i.created_on.to_date] ||= [] @events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i @events_by_day[i.created_on.to_date] << i
} }
@@ -422,7 +479,7 @@ class ProjectsController < ApplicationController
end end
unless params[:show_files] == "0" unless params[:show_files] == "0"
Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN versions ON versions.id = attachments.container_id", :conditions => ["attachments.container_type='Version' and versions.project_id=? and attachments.created_on>=? and attachments.created_on<=?", @project.id, @date_from, @date_to], :include => :author ).each { |i| Attachment.find(:all, :select => "#{Attachment.table_name}.*", :joins => "LEFT JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Version' and #{Version.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author ).each { |i|
@events_by_day[i.created_on.to_date] ||= [] @events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i @events_by_day[i.created_on.to_date] << i
} }
@@ -430,21 +487,55 @@ class ProjectsController < ApplicationController
end end
unless params[:show_documents] == "0" unless params[:show_documents] == "0"
@project.documents.find(:all, :conditions => ["documents.created_on>=? and documents.created_on<=?", @date_from, @date_to] ).each { |i| @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] ).each { |i|
@events_by_day[i.created_on.to_date] ||= [] @events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i @events_by_day[i.created_on.to_date] << i
} }
Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN documents ON documents.id = attachments.container_id", :conditions => ["attachments.container_type='Document' and documents.project_id=? and attachments.created_on>=? and attachments.created_on<=?", @project.id, @date_from, @date_to], :include => :author ).each { |i| Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN #{Document.table_name} ON #{Document.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Document' and #{Document.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author ).each { |i|
@events_by_day[i.created_on.to_date] ||= [] @events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i @events_by_day[i.created_on.to_date] << i
} }
@show_documents = 1 @show_documents = 1
end end
unless @project.wiki.nil? || params[:show_wiki_edits] == "0"
select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
"#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title"
joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
"LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
@project.id, @date_from, @date_to]
WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions).each { |i|
# We provide this alias so all events can be treated in the same manner
def i.created_on
self.updated_on
end
@events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i
}
@show_wiki_edits = 1
end
unless @project.repository.nil? || params[:show_changesets] == "0"
@project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to]).each { |i|
def i.created_on
self.committed_on
end
@events_by_day[i.created_on.to_date] ||= []
@events_by_day[i.created_on.to_date] << i
}
@show_changesets = 1
end
render :layout => false if request.xhr? render :layout => false if request.xhr?
end end
def calendar def calendar
@trackers = Tracker.find(:all, :order => 'position')
retrieve_selected_tracker_ids(@trackers)
if params[:year] and params[:year].to_i > 1900 if params[:year] and params[:year].to_i > 1900
@year = params[:year].to_i @year = params[:year].to_i
if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
@@ -461,11 +552,25 @@ class ProjectsController < ApplicationController
# finish on sunday # finish on sunday
@date_to = @date_to + (7-@date_to.cwday) @date_to = @date_to + (7-@date_to.cwday)
@issues = @project.issues.find(:all, :include => :tracker, :conditions => ["((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?))", @date_from, @date_to, @date_from, @date_to]) @events = []
@project.issues_with_subprojects(params[:with_subprojects]) do
@events += Issue.find(:all,
:include => [:tracker, :status, :assigned_to, :priority, :project],
:conditions => ["((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)) and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')})", @date_from, @date_to, @date_from, @date_to]
) unless @selected_tracker_ids.empty?
end
@events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
@ending_events_by_days = @events.group_by {|event| event.due_date}
@starting_events_by_days = @events.group_by {|event| event.start_date}
render :layout => false if request.xhr? render :layout => false if request.xhr?
end end
def gantt def gantt
@trackers = Tracker.find(:all, :order => 'position')
retrieve_selected_tracker_ids(@trackers)
if params[:year] and params[:year].to_i >0 if params[:year] and params[:year].to_i >0
@year_from = params[:year].to_i @year_from = params[:year].to_i
if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
@@ -483,7 +588,17 @@ class ProjectsController < ApplicationController
@date_from = Date.civil(@year_from, @month_from, 1) @date_from = Date.civil(@year_from, @month_from, 1)
@date_to = (@date_from >> @months) - 1 @date_to = (@date_from >> @months) - 1
@issues = @project.issues.find(:all, :order => "start_date, due_date", :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to])
@events = []
@project.issues_with_subprojects(params[:with_subprojects]) do
@events += Issue.find(:all,
:order => "start_date, due_date",
:include => [:tracker, :status, :assigned_to, :priority, :project],
:conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
) unless @selected_tracker_ids.empty?
end
@events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
@events.sort! {|x,y| x.start_date <=> y.start_date }
if params[:output]=='pdf' if params[:output]=='pdf'
@options_for_rfpdf ||= {} @options_for_rfpdf ||= {}
@@ -494,6 +609,11 @@ class ProjectsController < ApplicationController
end end
end end
def feeds
@queries = @project.queries.find :all, :conditions => ["is_public=? or user_id=?", true, (logged_in_user ? logged_in_user.id : 0)]
@key = logged_in_user.get_or_create_rss_key.value if logged_in_user
end
private private
# Find project of id params[:id] # Find project of id params[:id]
# if not found, redirect to project list # if not found, redirect to project list
@@ -505,14 +625,24 @@ private
render_404 render_404
end end
def retrieve_selected_tracker_ids(selectable_trackers)
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 }
end
end
# Retrieve query from session or build a new query # Retrieve query from session or build a new query
def retrieve_query def retrieve_query
if params[:query_id] if params[:query_id]
@query = @project.queries.find(params[:query_id]) @query = @project.queries.find(params[:query_id])
@query.executed_by = logged_in_user
session[:query] = @query
else else
if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id
# Give it a name, required to be valid # Give it a name, required to be valid
@query = Query.new(:name => "_") @query = Query.new(:name => "_", :executed_by => logged_in_user)
@query.project = @project @query.project = @project
if params[:fields] and params[:fields].is_a? Array if params[:fields] and params[:fields].is_a? Array
params[:fields].each do |field| params[:fields].each do |field|
+39 -8
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,7 +17,33 @@
class QueriesController < ApplicationController class QueriesController < ApplicationController
layout 'base' layout 'base'
before_filter :require_login, :find_query before_filter :require_login, :except => :index
before_filter :find_project, :check_project_privacy
def index
@queries = @project.queries.find(:all,
:order => "name ASC",
:conditions => ["is_public = ? or user_id = ?", true, (logged_in_user ? logged_in_user.id : 0)])
end
def new
@query = Query.new(params[:query])
@query.project = @project
@query.user = logged_in_user
@query.executed_by = logged_in_user
@query.is_public = false unless logged_in_user.authorized_to(@project, 'projects/add_query')
params[:fields].each do |field|
@query.add_filter(field, params[:operators][field], params[:values][field])
end if params[:fields]
if request.post? and @query.save
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => @query
return
end
render :layout => false if request.xhr?
end
def edit def edit
if request.post? if request.post?
@@ -26,6 +52,7 @@ class QueriesController < ApplicationController
@query.add_filter(field, params[:operators][field], params[:values][field]) @query.add_filter(field, params[:operators][field], params[:values][field])
end if params[:fields] end if params[:fields]
@query.attributes = params[:query] @query.attributes = params[:query]
@query.is_public = false unless logged_in_user.authorized_to(@project, 'projects/add_query')
if @query.save if @query.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
@@ -36,15 +63,19 @@ class QueriesController < ApplicationController
def destroy def destroy
@query.destroy if request.post? @query.destroy if request.post?
redirect_to :controller => 'reports', :action => 'issue_report', :id => @project redirect_to :controller => 'queries', :project_id => @project
end end
private private
def find_query def find_project
@query = Query.find(params[:id]) if params[:id]
@project = @query.project @query = Query.find(params[:id])
# check if user is allowed to manage queries (same permission as add_query) @query.executed_by = logged_in_user
authorize('projects', 'add_query') @project = @query.project
render_403 unless @query.editable_by?(logged_in_user)
else
@project = Project.find(params[:project_id])
end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
+64 -18
View File
@@ -16,19 +16,25 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ReportsController < ApplicationController class ReportsController < ApplicationController
layout 'base' layout 'base'
before_filter :find_project, :authorize before_filter :find_project, :authorize
def issue_report def issue_report
@statuses = IssueStatus.find :all @statuses = IssueStatus.find(:all, :order => 'position')
case params[:detail] case params[:detail]
when "tracker" when "tracker"
@field = "tracker_id" @field = "tracker_id"
@rows = Tracker.find :all @rows = Tracker.find :all, :order => 'position'
@data = issues_by_tracker @data = issues_by_tracker
@report_title = l(:field_tracker) @report_title = l(:field_tracker)
render :template => "reports/issue_report_details" render :template => "reports/issue_report_details"
when "version"
@field = "fixed_version_id"
@rows = @project.versions.sort
@data = issues_by_version
@report_title = l(:field_version)
render :template => "reports/issue_report_details"
when "priority" when "priority"
@field = "priority_id" @field = "priority_id"
@rows = Enumeration::get_values('IPRI') @rows = Enumeration::get_values('IPRI')
@@ -47,16 +53,26 @@ class ReportsController < ApplicationController
@data = issues_by_author @data = issues_by_author
@report_title = l(:field_author) @report_title = l(:field_author)
render :template => "reports/issue_report_details" render :template => "reports/issue_report_details"
when "subproject"
@field = "project_id"
@rows = @project.active_children
@data = issues_by_subproject
@report_title = l(:field_subproject)
render :template => "reports/issue_report_details"
else else
@queries = @project.queries.find :all, :conditions => ["is_public=? or user_id=?", true, (logged_in_user ? logged_in_user.id : 0)] @trackers = Tracker.find(:all, :order => 'position')
@trackers = Tracker.find(:all) @versions = @project.versions.sort
@priorities = Enumeration::get_values('IPRI') @priorities = Enumeration::get_values('IPRI')
@categories = @project.issue_categories @categories = @project.issue_categories
@authors = @project.members.collect { |m| m.user } @authors = @project.members.collect { |m| m.user }
@subprojects = @project.active_children
issues_by_tracker issues_by_tracker
issues_by_version
issues_by_priority issues_by_priority
issues_by_category issues_by_category
issues_by_author issues_by_author
issues_by_subproject
@total_hours = @project.time_entries.sum(:hours)
render :template => "reports/issue_report" render :template => "reports/issue_report"
end end
end end
@@ -105,63 +121,93 @@ private
render_404 render_404
end end
def issues_by_tracker def issues_by_tracker
@issues_by_tracker ||= @issues_by_tracker ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id, ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed, s.is_closed as closed,
t.id as tracker_id, t.id as tracker_id,
count(i.id) as total count(i.id) as total
from from
issues i, issue_statuses s, trackers t #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Tracker.table_name} t
where where
i.status_id=s.id i.status_id=s.id
and i.tracker_id=t.id and i.tracker_id=t.id
and i.project_id=#{@project.id} and i.project_id=#{@project.id}
group by s.id, s.is_closed, t.id") group by s.id, s.is_closed, t.id")
end end
def issues_by_priority def issues_by_version
@issues_by_version ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
v.id as fixed_version_id,
count(i.id) as total
from
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{Version.table_name} v
where
i.status_id=s.id
and i.fixed_version_id=v.id
and i.project_id=#{@project.id}
group by s.id, s.is_closed, v.id")
end
def issues_by_priority
@issues_by_priority ||= @issues_by_priority ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id, ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed, s.is_closed as closed,
p.id as priority_id, p.id as priority_id,
count(i.id) as total count(i.id) as total
from from
issues i, issue_statuses s, enumerations p #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Enumeration.table_name} p
where where
i.status_id=s.id i.status_id=s.id
and i.priority_id=p.id and i.priority_id=p.id
and i.project_id=#{@project.id} and i.project_id=#{@project.id}
group by s.id, s.is_closed, p.id") group by s.id, s.is_closed, p.id")
end end
def issues_by_category def issues_by_category
@issues_by_category ||= @issues_by_category ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id, ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed, s.is_closed as closed,
c.id as category_id, c.id as category_id,
count(i.id) as total count(i.id) as total
from from
issues i, issue_statuses s, issue_categories c #{Issue.table_name} i, #{IssueStatus.table_name} s, #{IssueCategory.table_name} c
where where
i.status_id=s.id i.status_id=s.id
and i.category_id=c.id and i.category_id=c.id
and i.project_id=#{@project.id} and i.project_id=#{@project.id}
group by s.id, s.is_closed, c.id") group by s.id, s.is_closed, c.id")
end end
def issues_by_author def issues_by_author
@issues_by_author ||= @issues_by_author ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id, ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed, s.is_closed as closed,
a.id as author_id, a.id as author_id,
count(i.id) as total count(i.id) as total
from from
issues i, issue_statuses s, users a #{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a
where where
i.status_id=s.id i.status_id=s.id
and i.author_id=a.id and i.author_id=a.id
and i.project_id=#{@project.id} and i.project_id=#{@project.id}
group by s.id, s.is_closed, a.id") group by s.id, s.is_closed, a.id")
end end
def issues_by_subproject
@issues_by_subproject ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
i.project_id as project_id,
count(i.id) as total
from
#{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?
@issues_by_subproject ||= []
end
end end
+177 -20
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -15,54 +15,114 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'SVG/Graph/Bar'
require 'SVG/Graph/BarHorizontal'
require 'digest/sha1'
class RepositoriesController < ApplicationController class RepositoriesController < ApplicationController
layout 'base' layout 'base'
before_filter :find_project, :authorize before_filter :find_project, :except => [:update_form]
before_filter :authorize, :except => [:update_form, :stats, :graph]
before_filter :check_project_privacy, :only => [:stats, :graph]
def show def show
@entries = @repository.scm.entries('') # check if new revisions have been committed in the repository
show_error and return unless @entries @repository.fetch_changesets if Setting.autofetch_changesets?
@latest_revision = @entries.revisions.latest # get entries for the browse frame
@entries = @repository.entries('')
# latest changesets
@changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
show_error and return unless @entries || @changesets.any?
end end
def browse def browse
@entries = @repository.scm.entries(@path, @rev) @entries = @repository.entries(@path, @rev)
show_error and return unless @entries show_error and return unless @entries
end end
def revisions def changes
@entry = @repository.scm.entry(@path, @rev) @entry = @repository.scm.entry(@path, @rev)
@revisions = @repository.scm.revisions(@path, @rev) show_error and return unless @entry
show_error and return unless @entry && @revisions @changes = Change.find(:all, :include => :changeset,
:conditions => ["repository_id = ? AND path = ?", @repository.id, @path.with_leading_slash],
:order => "committed_on DESC")
end
def revisions
@changeset_count = @repository.changesets.count
@changeset_pages = Paginator.new self, @changeset_count,
25,
params['page']
@changesets = @repository.changesets.find(:all,
:limit => @changeset_pages.items_per_page,
:offset => @changeset_pages.current.offset)
render :action => "revisions", :layout => false if request.xhr?
end end
def entry def entry
@content = @repository.scm.cat(@path, @rev)
show_error and return unless @content
if 'raw' == params[:format] if 'raw' == params[:format]
content = @repository.scm.cat(@path, @rev) send_data @content, :filename => @path.split('/').last
show_error and return unless content
send_data content, :filename => @path.split('/').last
end end
end end
def revision def revision
@revisions = @repository.scm.revisions '', @rev, @rev, :with_paths => true @changeset = @repository.changesets.find_by_revision(@rev)
show_error and return unless @revisions show_error and return unless @changeset
@revision = @revisions.first @changes_count = @changeset.changes.size
@changes_pages = Paginator.new self, @changes_count, 150, params['page']
@changes = @changeset.changes.find(:all,
:limit => @changes_pages.items_per_page,
:offset => @changes_pages.current.offset)
render :action => "revision", :layout => false if request.xhr?
end end
def diff def diff
@rev_to = params[:rev_to] || (@rev-1) @rev_to = params[:rev_to] ? params[:rev_to].to_i : (@rev - 1)
@diff = @repository.scm.diff(params[:path], @rev, @rev_to) @diff_type = ('sbs' == params[:type]) ? 'sbs' : 'inline'
show_error and return unless @diff
@cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
unless read_fragment(@cache_key)
@diff = @repository.diff(@path, @rev, @rev_to, type)
show_error and return unless @diff
end
end
def stats
end
def graph
data = nil
case params[:graph]
when "commits_per_month"
data = graph_commits_per_month(@repository)
when "commits_per_author"
data = graph_commits_per_author(@repository)
end
if data
headers["Content-Type"] = "image/svg+xml"
send_data(data, :type => "image/svg+xml", :disposition => "inline")
else
render_404
end
end
def update_form
@repository = Repository.factory(params[:repository_scm])
render :partial => 'projects/repository', :locals => {:repository => @repository}
end end
private private
def find_project def find_project
@project = Project.find(params[:id]) @project = Project.find(params[:id])
@repository = @project.repository @repository = @project.repository
@path = params[:path].squeeze('/').gsub(/^\//, '') if params[:path] render_404 and return false unless @repository
@path = params[:path].squeeze('/') if params[:path]
@path ||= '' @path ||= ''
@rev = params[:rev].to_i if params[:rev] and params[:rev].to_i > 0 @rev = params[:rev].to_i if params[:rev]
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
@@ -71,4 +131,101 @@ private
flash.now[:notice] = l(:notice_scm_error) flash.now[:notice] = l(:notice_scm_error)
render :nothing => true, :layout => true render :nothing => true, :layout => true
end end
def graph_commits_per_month(repository)
@date_to = Date.today
@date_from = @date_to << 12
commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
commits_by_month = [0] * 12
commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
changes_by_day = repository.changes.count(:all, :group => :commit_date)
changes_by_month = [0] * 12
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)]}
graph = SVG::Graph::Bar.new(
:height => 300,
:width => 500,
:fields => fields.reverse,
:stack => :side,
:scale_integers => true,
:step_x_labels => 2,
:show_data_values => false,
:graph_title => l(:label_commits_per_month),
:show_graph_title => true
)
graph.add_data(
:data => commits_by_month[0..11].reverse,
:title => l(:label_revision_plural)
)
graph.add_data(
:data => changes_by_month[0..11].reverse,
:title => l(:label_change_plural)
)
graph.burn
end
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}
changes_by_author = repository.changes.count(:all, :group => :committer)
h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
fields = commits_by_author.collect {|r| r.first}
commits_data = commits_by_author.collect {|r| r.last}
changes_data = commits_by_author.collect {|r| h[r.first] || 0}
fields = fields + [""]*(10 - fields.length) if fields.length<10
commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
graph = SVG::Graph::BarHorizontal.new(
:height => 300,
:width => 500,
:fields => fields,
:stack => :side,
:scale_integers => true,
:show_data_values => false,
:rotate_y_labels => false,
:graph_title => l(:label_commits_per_author),
:show_graph_title => true
)
graph.add_data(
:data => commits_data,
:title => l(:label_revision_plural)
)
graph.add_data(
:data => changes_data,
:title => l(:label_change_plural)
)
graph.burn
end
end
class Date
def months_ago(date = Date.today)
(date.year - self.year)*12 + (date.month - self.month)
end
def weeks_ago(date = Date.today)
(date.year - self.year)*52 + (date.cweek - self.cweek)
end
end
class String
def with_leading_slash
starts_with?('/') ? self : "/#{self}"
end
end end
+37 -6
View File
@@ -16,8 +16,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class RolesController < ApplicationController class RolesController < ApplicationController
layout 'base' layout 'base'
before_filter :require_admin before_filter :require_admin
verify :method => :post, :only => [ :destroy, :move ],
:redirect_to => { :action => :list }
def index def index
list list
@@ -25,7 +28,7 @@ class RolesController < ApplicationController
end end
def list def list
@role_pages, @roles = paginate :roles, :per_page => 10 @role_pages, @roles = paginate :roles, :per_page => 25, :order => "position"
render :action => "list", :layout => false if request.xhr? render :action => "list", :layout => false if request.xhr?
end end
@@ -62,6 +65,21 @@ class RolesController < ApplicationController
redirect_to :action => 'list' redirect_to :action => 'list'
end 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 workflow def workflow
@role = Role.find_by_id(params[:role_id]) @role = Role.find_by_id(params[:role_id])
@tracker = Tracker.find_by_id(params[:tracker_id]) @tracker = Tracker.find_by_id(params[:tracker_id])
@@ -77,8 +95,21 @@ class RolesController < ApplicationController
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
end end
end end
@roles = Role.find :all @roles = Role.find(:all, :order => 'position')
@trackers = Tracker.find :all @trackers = Tracker.find(:all, :order => 'position')
@statuses = IssueStatus.find(:all, :include => :workflows) @statuses = IssueStatus.find(:all, :include => :workflows, :order => 'position')
end
def report
@roles = Role.find(:all, :order => 'position')
@permissions = Permission.find :all, :conditions => ["is_public=?", false], :order => 'sort'
if request.post?
@roles.each do |role|
role.permissions = Permission.find(params[:permission_ids] ? (params[:permission_ids][role.id.to_s] || []) : [] )
end
Permission.allowed_to_role_expired
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list'
end
end end
end end
+81
View File
@@ -0,0 +1,81 @@
# 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 SearchController < ApplicationController
layout 'base'
helper :messages
include MessagesHelper
def index
@question = params[:q] || ""
@question.strip!
@all_words = params[:all_words] || (params[:submit] ? false : true)
@scope = params[:scope] || (params[:submit] ? [] : %w(projects issues changesets news documents wiki messages) )
# quick jump to an issue
if @scope.include?('issues') && @question.match(/^#?(\d+)$/) && Issue.find_by_id($1, :include => :project, :conditions => Project.visible_by(logged_in_user))
redirect_to :controller => "issues", :action => "show", :id => $1
return
end
if params[:id]
find_project
return unless check_project_privacy
end
# tokens must be at least 3 character long
@tokens = @question.split.uniq.select {|w| w.length > 2 }
if !@tokens.empty?
# no more than 5 tokens to search for
@tokens.slice! 5..-1 if @tokens.size > 5
# strings used in sql like statement
like_tokens = @tokens.collect {|w| "%#{w.downcase}%"}
operator = @all_words ? " AND " : " OR "
limit = 10
@results = []
if @project
@results += @project.issues.find(:all, :limit => limit, :include => :author, :conditions => [ (["(LOWER(subject) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'issues'
@results += @project.news.find(:all, :limit => limit, :conditions => [ (["(LOWER(title) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort], :include => :author ) if @scope.include? 'news'
@results += @project.documents.find(:all, :limit => limit, :conditions => [ (["(LOWER(title) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'documents'
@results += @project.wiki.pages.find(:all, :limit => limit, :include => :content, :conditions => [ (["(LOWER(title) like ? OR LOWER(text) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @project.wiki && @scope.include?('wiki')
@results += @project.repository.changesets.find(:all, :limit => limit, :conditions => [ (["(LOWER(comments) like ?)"] * like_tokens.size).join(operator), * (like_tokens).sort] ) if @project.repository && @scope.include?('changesets')
Message.with_scope :find => {:conditions => ["#{Board.table_name}.project_id = ?", @project.id]} do
@results += Message.find(:all, :include => :board, :limit => limit, :conditions => [ (["(LOWER(subject) like ? OR LOWER(content) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'messages'
end
else
Project.with_scope(:find => {:conditions => Project.visible_by(logged_in_user)}) do
@results += Project.find(:all, :limit => limit, :conditions => [ (["(LOWER(name) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'projects'
end
# if only one project is found, user is redirected to its overview
redirect_to :controller => 'projects', :action => 'show', :id => @results.first and return if @results.size == 1
end
@question = @tokens.join(" ")
else
@question = ""
end
end
private
def find_project
@project = Project.find(params[:id])
@html_title = @project.name
rescue ActiveRecord::RecordNotFound
render_404
end
end
+34
View File
@@ -0,0 +1,34 @@
# 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 SettingsController < ApplicationController
layout 'base'
before_filter :require_admin
def index
edit
render :action => 'edit'
end
def edit
if request.post? and params[:settings] and params[:settings].is_a? Hash
params[:settings].each { |name, value| Setting[name] = value }
redirect_to :action => 'edit' and return
end
@textile_available = ActionView::Helpers::TextHelper.method_defined?("textilize")
end
end
+44
View File
@@ -0,0 +1,44 @@
# 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 SysController < ActionController::Base
wsdl_service_name 'Sys'
web_service_api SysApi
web_service_scaffold :invoke
before_invocation :check_enabled
def projects
Project.find(:all, :include => :repository)
end
def repository_created(project_id, url)
project = Project.find_by_id(project_id)
return 0 unless project && project.repository.nil?
logger.debug "Repository for #{project.name} created"
repository = Repository.new(:project => project, :url => url)
repository.root_url = url
repository.save
repository.id
end
protected
def check_enabled(name, args)
Setting.sys_api_enabled?
end
end
+174
View File
@@ -0,0 +1,174 @@
# 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 TimelogController < ApplicationController
layout 'base'
before_filter :find_project
before_filter :authorize, :only => :edit
before_filter :check_project_privacy, :except => :edit
helper :sort
include SortHelper
def report
@available_criterias = { 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
:values => @project.versions,
:label => :label_version},
'category' => {:sql => "#{Issue.table_name}.category_id",
:values => @project.issue_categories,
:label => :field_category},
'member' => {:sql => "#{TimeEntry.table_name}.user_id",
:values => @project.users,
:label => :label_member},
'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
:values => Tracker.find(:all),
:label => :label_tracker},
'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
:values => Enumeration::get_values('ACTI'),
:label => :label_activity}
}
@criterias = params[:criterias] || []
@criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
@criterias.uniq!
@columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month'
if params[:date_from]
begin; @date_from = params[:date_from].to_date; rescue; end
end
if params[:date_to]
begin; @date_to = params[:date_to].to_date; rescue; end
end
@date_from ||= Date.civil(Date.today.year, 1, 1)
@date_to ||= Date.civil(Date.today.year, Date.today.month+1, 1) - 1
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 = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
sql << " FROM #{TimeEntry.table_name} LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
sql << " WHERE #{TimeEntry.table_name}.project_id = %s" % @project.id
sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)]
sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
@hours = ActiveRecord::Base.connection.select_all(sql)
@hours.each do |row|
case @columns
when 'year'
row['year'] = row['tyear']
when 'month'
row['month'] = "#{row['tyear']}-#{row['tmonth']}"
when 'week'
row['week'] = "#{row['tyear']}-#{row['tweek']}"
end
end
end
@periods = []
date_from = @date_from
# 100 columns max
while date_from < @date_to && @periods.length < 100
case @columns
when 'year'
@periods << "#{date_from.year}"
date_from = date_from >> 12
when 'month'
@periods << "#{date_from.year}-#{date_from.month}"
date_from = date_from >> 1
when 'week'
@periods << "#{date_from.year}-#{date_from.cweek}"
date_from = date_from + 7
end
end
render :layout => false if request.xhr?
end
def details
sort_init 'spent_on', 'desc'
sort_update
@entries = (@issue ? @issue : @project).time_entries.find(:all, :include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :order => sort_clause)
@total_hours = @entries.inject(0) { |sum,entry| sum + entry.hours }
@owner_id = logged_in_user ? logged_in_user.id : 0
send_csv and return if 'csv' == params[:export]
render :action => 'details', :layout => false if request.xhr?
end
def edit
render_404 and return if @time_entry && @time_entry.user != logged_in_user
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
@time_entry.attributes = params[:time_entry]
if request.post? and @time_entry.save
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'details', :project_id => @time_entry.project, :issue_id => @time_entry.issue
return
end
@activities = Enumeration::get_values('ACTI')
end
private
def find_project
if params[:id]
@time_entry = TimeEntry.find(params[:id])
@project = @time_entry.project
elsif params[:issue_id]
@issue = Issue.find(params[:issue_id])
@project = @issue.project
elsif params[:project_id]
@project = Project.find(params[:project_id])
else
render_404
return false
end
end
def send_csv
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
export = StringIO.new
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
# csv header fields
headers = [l(:field_spent_on),
l(:field_user),
l(:field_activity),
l(:field_issue),
l(:field_hours),
l(:field_comments)
]
csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
# csv lines
@entries.each do |entry|
fields = [l_date(entry.spent_on),
entry.user.name,
entry.activity.name,
(entry.issue ? entry.issue.id : nil),
entry.hours,
entry.comments
]
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
end
end
export.rewind
send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
end
end
+24 -3
View File
@@ -25,19 +25,26 @@ class TrackersController < ApplicationController
end end
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :list } verify :method => :post, :only => [ :destroy, :move ], :redirect_to => { :action => :list }
def list def list
@tracker_pages, @trackers = paginate :trackers, :per_page => 10 @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
render :action => "list", :layout => false if request.xhr? render :action => "list", :layout => false if request.xhr?
end end
def new def new
@tracker = Tracker.new(params[:tracker]) @tracker = Tracker.new(params[:tracker])
if request.post? and @tracker.save if request.post? and @tracker.save
# workflow copy
if params[:copy_workflow_from] && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
copy_from.workflows.each do |w|
@tracker.workflows << w.clone
end
end
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list' redirect_to :action => 'list'
end end
@trackers = Tracker.find :all
end end
def edit def edit
@@ -48,6 +55,21 @@ class TrackersController < ApplicationController
end end
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'
end
def destroy def destroy
@tracker = Tracker.find(params[:id]) @tracker = Tracker.find(params[:id])
unless @tracker.issues.empty? unless @tracker.issues.empty?
@@ -57,5 +79,4 @@ class TrackersController < ApplicationController
end end
redirect_to :action => 'list' redirect_to :action => 'list'
end end
end end
+12 -5
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -32,11 +32,17 @@ class UsersController < ApplicationController
def list def list
sort_init 'login', 'asc' sort_init 'login', 'asc'
sort_update sort_update
@user_count = User.count
@status = params[:status] ? params[:status].to_i : 1
conditions = nil
conditions = ["status=?", @status] unless @status == 0
@user_count = User.count(:conditions => conditions)
@user_pages = Paginator.new self, @user_count, @user_pages = Paginator.new self, @user_count,
15, 15,
params['page'] params['page']
@users = User.find :all,:order => sort_clause, @users = User.find :all,:order => sort_clause,
:conditions => conditions,
:limit => @user_pages.items_per_page, :limit => @user_pages.items_per_page,
:offset => @user_pages.current.offset :offset => @user_pages.current.offset
@@ -45,7 +51,7 @@ class UsersController < ApplicationController
def add def add
if request.get? if request.get?
@user = User.new(:language => $RDM_DEFAULT_LANG) @user = User.new(:language => Setting.default_language)
@custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) } @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
else else
@user = User.new(params[:user]) @user = User.new(params[:user])
@@ -55,6 +61,7 @@ class UsersController < ApplicationController
@custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) } @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
@user.custom_values = @custom_values @user.custom_values = @custom_values
if @user.save if @user.save
Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list' redirect_to :action => 'list'
end end
@@ -80,8 +87,8 @@ class UsersController < ApplicationController
end end
end end
@auth_sources = AuthSource.find(:all) @auth_sources = AuthSource.find(:all)
@roles = Role.find :all @roles = Role.find(:all, :order => 'position')
@projects = Project.find(:all) - @user.projects @projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects
@membership ||= Member.new @membership ||= Member.new
end end
+3 -3
View File
@@ -22,16 +22,16 @@ class VersionsController < ApplicationController
def edit def edit
if request.post? and @version.update_attributes(params[:version]) if request.post? and @version.update_attributes(params[:version])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end end
end end
def destroy def destroy
@version.destroy @version.destroy
redirect_to :controller => 'projects', :action => 'settings', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
rescue rescue
flash[:notice] = "Unable to delete version" flash[:notice] = "Unable to delete version"
redirect_to :controller => 'projects', :action => 'settings', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end end
def download def download
+49
View File
@@ -0,0 +1,49 @@
# 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 WatchersController < ApplicationController
layout 'base'
before_filter :require_login, :find_project, :check_project_privacy
def add
user = logged_in_user
@watched.add_watcher(user)
respond_to do |format|
format.html { render :text => 'Watcher added.', :layout => true }
format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
end
end
def remove
user = logged_in_user
@watched.remove_watcher(user)
respond_to do |format|
format.html { render :text => 'Watcher removed.', :layout => true }
format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
end
end
private
def find_project
klass = Object.const_get(params[:object_type].camelcase)
return false unless klass.respond_to?('watched_by')
@watched = klass.find(params[:object_id])
@project = @watched.project
rescue
render_404
end
end
+163
View File
@@ -0,0 +1,163 @@
# 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.
require 'diff'
class WikiController < ApplicationController
layout 'base'
before_filter :find_wiki, :check_project_privacy
before_filter :authorize, :only => [:destroy, :add_attachment, :destroy_attachment]
verify :method => :post, :only => [:destroy, :destroy_attachment], :redirect_to => { :action => :index }
helper :attachments
include AttachmentsHelper
# display a page (in editing mode if it doesn't exist)
def index
page_title = params[:page]
@page = @wiki.find_or_new_page(page_title)
if @page.new_record?
edit
render :action => 'edit' and return
end
@content = @page.content_for_version(params[:version])
if params[:export] == 'html'
export = render_to_string :action => 'export', :layout => false
send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
return
elsif params[:export] == 'txt'
send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
return
end
render :action => 'show'
end
# edit an existing page or a new one
def edit
@page = @wiki.find_or_new_page(params[:page])
@page.content = WikiContent.new(:page => @page) if @page.new_record?
@content = @page.content_for_version(params[:version])
@content.text = "h1. #{@page.pretty_title}" if @content.text.blank?
# don't keep previous comment
@content.comments = nil
if request.post?
if @content.text == params[:content][:text]
# don't save if text wasn't changed
redirect_to :action => 'index', :id => @project, :page => @page.title
return
end
#@content.text = params[:content][:text]
#@content.comments = params[:content][:comments]
@content.attributes = params[:content]
@content.author = logged_in_user
# 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)
redirect_to :action => 'index', :id => @project, :page => @page.title
end
end
rescue ActiveRecord::StaleObjectError
# Optimistic locking exception
flash[:notice] = l(:notice_locking_conflict)
end
# show page history
def history
@page = @wiki.find_page(params[:page])
@version_count = @page.content.versions.count
@version_pages = Paginator.new self, @version_count, 25, params['p']
# don't load text
@versions = @page.content.versions.find :all,
:select => "id, author_id, comments, updated_on, version",
:order => 'version DESC',
:limit => @version_pages.items_per_page + 1,
:offset => @version_pages.current.offset
render :layout => false if request.xhr?
end
def diff
@page = @wiki.find_page(params[:page])
@diff = @page.diff(params[:version], params[:version_from])
render_404 unless @diff
end
# remove a wiki page and its history
def destroy
@page = @wiki.find_page(params[:page])
@page.destroy if @page
redirect_to :action => 'special', :id => @project, :page => 'Page_index'
end
# display special pages
def special
page_title = params[:page].downcase
case page_title
# show pages index, sorted by title
when 'page_index'
# eager load information about last updates, without loading text
@pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
:joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id",
:order => 'title'
# export wiki to a single html file
when 'export'
@pages = @wiki.pages.find :all, :order => 'title'
export = render_to_string :action => 'export_multiple', :layout => false
send_data(export, :type => 'text/html', :filename => "wiki.html")
return
else
# requested special page doesn't exist, redirect to default page
redirect_to :action => 'index', :id => @project, :page => nil and return
end
render :action => "special_#{page_title}"
end
def preview
page = @wiki.find_page(params[:page])
@attachements = page.attachments if page
@text = params[:content][:text]
render :partial => 'preview'
end
def add_attachment
@page = @wiki.find_page(params[:page])
# Save the attachments
params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @page, :file => file, :author => logged_in_user)
} if params[:attachments] and params[:attachments].is_a? Array
redirect_to :action => 'index', :page => @page.title
end
def destroy_attachment
@page = @wiki.find_page(params[:page])
@page.attachments.find(params[:attachment_id]).destroy
redirect_to :action => 'index', :page => @page.title
end
private
def find_wiki
@project = Project.find(params[:id])
@wiki = @project.wiki
render_404 unless @wiki
rescue ActiveRecord::RecordNotFound
render_404
end
end
+4
View File
@@ -16,4 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module AdminHelper module AdminHelper
def project_status_options_for_select(selected)
options_for_select([[l(:label_all), "*"],
[l(:status_active), 1]], selected)
end
end end
+123 -17
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -15,6 +15,14 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class RedCloth
# Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
# <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
def hard_break( text )
text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
end
end
module ApplicationHelper module ApplicationHelper
# Return current logged in user or nil # Return current logged in user or nil
@@ -34,7 +42,7 @@ module ApplicationHelper
return true return true
end end
# check if user is authorized # check if user is authorized
if @logged_in_user and (@logged_in_user.admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], @logged_in_user.role_for_project(@project.id) ) ) if @logged_in_user and (@logged_in_user.admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], @logged_in_user.role_for_project(@project) ) )
return true return true
end end
return false return false
@@ -47,15 +55,39 @@ module ApplicationHelper
# Display a link to user's account page # Display a link to user's account page
def link_to_user(user) def link_to_user(user)
link_to user.display_name, :controller => 'account', :action => 'show', :id => user link_to user.name, :controller => 'account', :action => 'show', :id => user
end
def link_to_issue(issue)
link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
end
def toggle_link(name, id, options={})
onclick = "Element.toggle('#{id}'); "
onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
onclick << "return false;"
link_to(name, "#", :onclick => onclick)
end
def image_to_function(name, function, html_options = {})
html_options.symbolize_keys!
tag(:input, html_options.merge({
:type => "image", :src => image_path(name),
:onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
}))
end end
def format_date(date) def format_date(date)
l_date(date) if date return nil unless date
@date_format_setting ||= Setting.date_format.to_i
@date_format_setting == 0 ? l_date(date) : date.strftime("%Y-%m-%d")
end end
def format_time(time) def format_time(time)
l_datetime(time) if time return nil unless time
@date_format_setting ||= Setting.date_format.to_i
time = time.to_time if time.is_a?(String)
@date_format_setting == 0 ? l_datetime(time) : (time.strftime("%Y-%m-%d") + ' ' + l_time(time))
end end
def day_name(day) def day_name(day)
@@ -67,25 +99,89 @@ module ApplicationHelper
end end
def pagination_links_full(paginator, options={}, html_options={}) def pagination_links_full(paginator, options={}, html_options={})
page_param = options.delete(:page_param) || :page
html = '' html = ''
html << link_to_remote(('&#171; ' + l(:label_previous)), html << link_to_remote(('&#171; ' + l(:label_previous)),
{:update => "content", :url => { :page => paginator.current.previous }}, {:update => "content", :url => options.merge(page_param => paginator.current.previous)},
{:href => url_for(:action => 'list', :params => params.merge({:page => paginator.current.previous}))}) + ' ' if paginator.current.previous {:href => url_for(:params => options.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
html << (pagination_links_each(paginator, options) do |n| html << (pagination_links_each(paginator, options) do |n|
link_to_remote(n.to_s, link_to_remote(n.to_s,
{:url => {:action => 'list', :params => params.merge({:page => n})}, :update => 'content'}, {:url => {:params => options.merge(page_param => n)}, :update => 'content'},
{:href => url_for(:action => 'list', :params => params.merge({:page => n}))}) {:href => url_for(:params => options.merge(page_param => n))})
end || '') end || '')
html << ' ' + link_to_remote((l(:label_next) + ' &#187;'), html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
{:update => "content", :url => { :page => paginator.current.next }}, {:update => "content", :url => options.merge(page_param => paginator.current.next)},
{:href => url_for(:action => 'list', :params => params.merge({:page => paginator.current.next}))}) if paginator.current.next {:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next
html html
end end
def textilizable(text) # textilize text according to system settings and RedCloth availability
$RDM_TEXTILE_DISABLED ? simple_format(auto_link(h(text))) : RedCloth.new(h(text)).to_html def textilizable(text, options = {})
return "" if text.blank?
# different methods for formatting wiki links
case options[:wiki_links]
when :local
# used for local links to html files
format_wiki_link = Proc.new {|title| "#{title}.html" }
when :anchor
# used for single-file wiki export
format_wiki_link = Proc.new {|title| "##{title}" }
else
if @project
format_wiki_link = Proc.new {|title| url_for :controller => 'wiki', :action => 'index', :id => @project, :page => title }
else
format_wiki_link = Proc.new {|title| title }
end
end
# turn wiki links into textile links:
# example:
# [[link]] -> "link":link
# [[link|title]] -> "title":link
text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) {|m| "\"#{$3 || $1}\":" + format_wiki_link.call(Wiki.titleize($1)) }
# turn issue ids into links
# example:
# #52 -> <a href="/issues/show/52">#52</a>
text = text.gsub(/#(\d+)(?=\b)/) {|m| link_to "##{$1}", :controller => 'issues', :action => 'show', :id => $1}
# turn revision ids into links (@project needed)
# example:
# r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (@project.id is 6)
text = text.gsub(/(?=\b)r(\d+)(?=\b)/) {|m| link_to "r#{$1}", :controller => 'repositories', :action => 'revision', :id => @project.id, :rev => $1} if @project
# when using an image link, try to use an attachment, if possible
attachments = options[:attachments]
if attachments
text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
align = $1
filename = $2
rf = Regexp.new(filename, Regexp::IGNORECASE)
# search for the picture in attachments
if found = attachments.detect { |att| att.filename =~ rf }
image_url = url_for :controller => 'attachments', :action => 'show', :id => found.id
"!#{align}#{image_url}!"
else
"!#{align}#{filename}!"
end
end
end
# finally textilize text
@do_textilize ||= (Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize")
text = @do_textilize ? auto_link(RedCloth.new(text, [:hard_breaks]).to_html) : simple_format(auto_link(h(text)))
end
# Same as Rails' simple_format helper without using paragraphs
def simple_format_without_paragraph(text)
text.to_s.
gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
end end
def error_messages_for(object_name, options = {}) def error_messages_for(object_name, options = {})
@@ -96,6 +192,7 @@ module ApplicationHelper
full_messages = [] full_messages = []
object.errors.each do |attr, msg| object.errors.each do |attr, msg|
next if msg.nil? next if msg.nil?
msg = msg.first if msg.is_a? Array
if attr == "base" if attr == "base"
full_messages << l(msg) full_messages << l(msg)
else else
@@ -107,6 +204,7 @@ module ApplicationHelper
object.custom_values.each do |v| object.custom_values.each do |v|
v.errors.each do |attr, msg| v.errors.each do |attr, msg|
next if msg.nil? next if msg.nil?
msg = msg.first if msg.is_a? Array
full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg) full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
end end
end end
@@ -123,8 +221,9 @@ module ApplicationHelper
end end
end end
def lang_options_for_select def lang_options_for_select(blank=true)
(GLoc.valid_languages.sort {|x,y| x.to_s <=> y.to_s }).collect {|lang| [ l_lang_name(lang.to_s, lang), lang.to_s]} (blank ? [["(auto)", ""]] : []) +
GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.first <=> y.first }
end end
def label_tag_for(name, option_tags = nil, options = {}) def label_tag_for(name, option_tags = nil, options = {})
@@ -145,9 +244,14 @@ module ApplicationHelper
end end
def calendar_for(field_id) def calendar_for(field_id)
image_tag("calendar", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) + image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });") javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
end end
def wikitoolbar_for(field_id)
return '' unless Setting.text_formatting == 'textile'
javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
end
end end
class TabularFormBuilder < ActionView::Helpers::FormBuilder class TabularFormBuilder < ActionView::Helpers::FormBuilder
@@ -162,7 +266,9 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
src = <<-END_SRC src = <<-END_SRC
def #{selector}(field, options = {}) def #{selector}(field, options = {})
return super if options.delete :no_label return super if options.delete :no_label
label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") label_text = l(options[:label]) if options[:label]
label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
label = @template.content_tag("label", label_text, label = @template.content_tag("label", label_text,
:class => (@object && @object.errors[field] ? "error" : nil), :class => (@object && @object.errors[field] ? "error" : nil),
:for => (@object_name.to_s + "_" + field.to_s)) :for => (@object_name.to_s + "_" + field.to_s))
+25
View File
@@ -0,0 +1,25 @@
# 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.
module AttachmentsHelper
# displays the links to a collection of attachments
def link_to_attachments(attachments, options = {})
if attachments.any?
render :partial => 'attachments/links', :locals => {:attachments => attachments, :options => options}
end
end
end
+19
View File
@@ -0,0 +1,19 @@
# 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.
module BoardsHelper
end
+3 -3
View File
@@ -34,7 +34,7 @@ module CustomFieldsHelper
when "bool" when "bool"
check_box 'custom_value', 'value', :name => field_name, :id => field_id check_box 'custom_value', 'value', :name => field_name, :id => field_id
when "list" when "list"
select 'custom_value', 'value', custom_field.possible_values.split('|'), { :include_blank => true }, :name => field_name, :id => field_id select 'custom_value', 'value', custom_field.possible_values, { :include_blank => true }, :name => field_name, :id => field_id
end end
end end
@@ -59,10 +59,10 @@ module CustomFieldsHelper
# Return a string used to display a custom value # Return a string used to display a custom value
def format_value(value, field_format) def format_value(value, field_format)
return "" unless value return "" unless value && !value.empty?
case field_format case field_format
when "date" when "date"
value.empty? ? "" : l_date(value.to_date) begin; l_date(value.to_date); rescue; value end
when "bool" when "bool"
l_YesNo(value == "1") l_YesNo(value == "1")
else else
+30 -3
View File
@@ -16,15 +16,42 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv' require 'iconv'
require 'rfpdf/chinese'
module IfpdfHelper module IfpdfHelper
class IFPDF < FPDF class IFPDF < FPDF
include GLoc
attr_accessor :footer_date attr_accessor :footer_date
def initialize(lang)
super()
set_language_if_valid lang
case current_language
when :ja
extend(PDF_Japanese)
AddSJISFont()
@font_for_content = 'SJIS'
@font_for_footer = 'SJIS'
when :zh
extend(PDF_Chinese)
AddBig5Font()
@font_for_content = 'Big5'
@font_for_footer = 'Big5'
else
@font_for_content = 'Arial'
@font_for_footer = 'Helvetica'
end
SetCreator("redMine #{Redmine::VERSION}")
SetFont(@font_for_content)
end
def SetFontStyle(style, size)
SetFont(@font_for_content, style, size)
end
def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='') def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
@ic ||= Iconv.new('ISO-8859-1', 'UTF-8') @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
txt = begin txt = begin
@ic.iconv(txt) @ic.iconv(txt)
rescue rescue
@@ -34,7 +61,7 @@ module IfpdfHelper
end end
def Footer def Footer
SetFont('Helvetica', 'I', 8) SetFont(@font_for_footer, 'I', 8)
SetY(-15) SetY(-15)
SetX(15) SetX(15)
Cell(0, 5, @footer_date, 0, 0, 'L') Cell(0, 5, @footer_date, 0, 0, 'L')
+23
View File
@@ -0,0 +1,23 @@
# 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.
module IssueRelationsHelper
def collection_for_relation_type_select
values = IssueRelation::TYPES
values.keys.sort{|x,y| values[x][:order] <=> values[y][:order]}.collect{|k| [l(values[k][:name]), k]}
end
end
+20 -8
View File
@@ -48,6 +48,8 @@ module IssuesHelper
value = format_value(detail.value, custom_field.field_format) if detail.value value = format_value(detail.value, custom_field.field_format) if detail.value
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
end end
when 'attachment'
label = l(:label_attachment)
end end
label ||= detail.prop_key label ||= detail.prop_key
@@ -56,19 +58,29 @@ module IssuesHelper
unless no_html unless no_html
label = content_tag('strong', label) label = content_tag('strong', label)
old_value = content_tag("i", h(old_value)) if old_value old_value = content_tag("i", h(old_value)) if detail.old_value
old_value = content_tag("strike", h(old_value)) if old_value and !value old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
value = content_tag("i", h(value)) if value value = content_tag("i", h(value)) if value
end end
if value if detail.value and !detail.value.to_s.empty?
if old_value case detail.property
label + " " + l(:text_journal_changed, old_value, value) when 'attr', 'cf'
else if old_value
label + " " + l(:text_journal_set_to, value) label + " " + l(:text_journal_changed, old_value, value)
else
label + " " + l(:text_journal_set_to, value)
end
when 'attachment'
"#{label} #{value} #{l(:label_added)}"
end end
else else
label + " " + l(:text_journal_deleted) + " (#{old_value})" case detail.property
when 'attr', 'cf'
label + " " + l(:text_journal_deleted) + " (#{old_value})"
when 'attachment'
"#{label} #{old_value} #{l(:label_deleted)}"
end
end end
end end
end end
+28
View File
@@ -0,0 +1,28 @@
# 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.
module MessagesHelper
def link_to_message(message)
return '' unless message
link_to h(truncate(message.subject, 60)), :controller => 'messages',
:action => 'show',
:board_id => message.board_id,
:id => message.root,
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
end
end
+9
View File
@@ -16,4 +16,13 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ProjectsHelper module ProjectsHelper
def link_to_version(version, options = {})
return '' unless version && version.is_a?(Version)
link_to version.name, {:controller => 'projects',
:action => 'roadmap',
:id => version.project_id,
:completed => (version.completed? ? 1 : nil),
:anchor => version.name
}, options
end
end end
+1 -1
View File
@@ -22,7 +22,7 @@ module ReportsHelper
data.each { |row| data.each { |row|
match = 1 match = 1
criteria.each { |k, v| criteria.each { |k, v|
match = 0 unless row[k].to_s == v.to_s match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && row[k] == (v == 0 ? "f" : "t"))
} unless criteria.nil? } unless criteria.nil?
a = a + row["total"].to_i if match == 1 a = a + row["total"].to_i if match == 1
} unless data.nil? } unless data.nil?
+39
View File
@@ -16,4 +16,43 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module RepositoriesHelper 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)
end
def scm_select_tag
container = [[]]
REDMINE_SUPPORTED_SCM.each {|scm| container << ["Repository::#{scm}".constantize.scm_name, scm]}
select_tag('repository_scm',
options_for_select(container, @project.repository.class.name.demodulize),
:disabled => (@project.repository && !@project.repository.new_record?),
:onchange => remote_function(:update => "repository_fields", :url => { :controller => 'repositories', :action => 'update_form', :id => @project }, :with => "Form.serialize(this.form)")
)
end
def with_leading_slash(path)
path ||= ''
path.starts_with?("/") ? "/#{path}" : path
end
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:///)') +
content_tag('p', form.text_field(:login, :size => 30)) +
content_tag('p', form.password_field(:password, :size => 30))
end
def darcs_field_tags(form, repository)
content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
end
def mercurial_field_tags(form, repository)
content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
end
def cvs_field_tags(form, repository)
content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
end
end end
+28
View File
@@ -0,0 +1,28 @@
# 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.
module SearchHelper
def highlight_tokens(text, tokens)
return text unless tokens && !tokens.empty?
regexp = Regexp.new "(#{tokens.join('|')})", Regexp::IGNORECASE
result = ''
text.split(regexp).each_with_index do |words, i|
result << (i.even? ? (words.length > 100 ? "#{words[0..44]} ... #{words[-45..-1]}" : words) : content_tag('span', words, :class => 'highlight'))
end
result
end
end
+19
View File
@@ -0,0 +1,19 @@
# 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.
module SettingsHelper
end
+7 -5
View File
@@ -96,10 +96,10 @@ module SortHelper
key, order = session[@sort_name][:key], session[@sort_name][:order] key, order = session[@sort_name][:key], session[@sort_name][:order]
if key == column if key == column
if order.downcase == 'asc' if order.downcase == 'asc'
icon = 'sort_asc' icon = 'sort_asc.png'
order = 'desc' order = 'desc'
else else
icon = 'sort_desc' icon = 'sort_desc.png'
order = 'asc' order = 'asc'
end end
else else
@@ -107,10 +107,12 @@ module SortHelper
order = 'desc' # changed for desc order by default order = 'desc' # changed for desc order by default
end end
caption = titleize(Inflector::humanize(column)) unless caption caption = titleize(Inflector::humanize(column)) unless caption
params = {:params => {:sort_key => column, :sort_order => order}}
url = {:sort_key => column, :sort_order => order, :issue_id => params[:issue_id], :project_id => params[:project_id]}
link_to_remote(caption, link_to_remote(caption,
{:update => "content", :url => { :sort_key => column, :sort_order => order}}, {:update => "content", :url => url},
{:href => url_for(:params => { :sort_key => column, :sort_order => order})}) + {:href => url_for(url)}) +
(icon ? nbsp(2) + image_tag(icon) : '') (icon ? nbsp(2) + image_tag(icon) : '')
end end
@@ -15,10 +15,16 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueHistory < ActiveRecord::Base module TimelogHelper
belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' def select_hours(data, criteria, value)
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' data.select {|row| row[criteria] == value.to_s}
belongs_to :issue end
validates_presence_of :status def sum_hours(data)
sum = 0
data.each do |row|
sum += row['hours'].to_f
end
sum
end
end end
+6
View File
@@ -16,4 +16,10 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module UsersHelper module UsersHelper
def status_options_for_select(selected)
options_for_select([[l(:label_all), "*"],
[l(:status_active), 1],
[l(:status_registered), 2],
[l(:status_locked), 3]], selected)
end
end end
+36
View File
@@ -0,0 +1,36 @@
# 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.
module WatchersHelper
def watcher_tag(object, user)
content_tag("span", watcher_link(object, user), :id => 'watcher')
end
def watcher_link(object, user)
return '' unless user && object.respond_to?('watched_by?')
watched = object.watched_by?(user)
url = {:controller => 'watchers',
:action => (watched ? 'remove' : 'add'),
:object_type => object.class.to_s.underscore,
:object_id => object.id}
link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
{:url => url},
:href => url_for(url),
:class => (watched ? 'icon icon-fav' : 'icon icon-fav-off'))
end
end
+56
View File
@@ -0,0 +1,56 @@
# 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.
module WikiHelper
def html_diff(wdiff)
words = wdiff.words.collect{|word| h(word)}
words_add = 0
words_del = 0
dels = 0
del_off = 0
wdiff.diff.diffs.each do |diff|
add_at = nil
add_to = nil
del_at = nil
deleted = ""
diff.each do |change|
pos = change[1]
if change[0] == "+"
add_at = pos + dels unless add_at
add_to = pos + dels
words_add += 1
else
del_at = pos unless del_at
deleted << ' ' + change[2]
words_del += 1
end
end
if add_at
words[add_at] = '<span class="diff_in">' + words[add_at]
words[add_to] = words[add_to] + '</span>'
end
if del_at
words.insert del_at - del_off + dels + words_add, '<span class="diff_out">' + deleted + '</span>'
dels += 1
del_off += words_del
words_del = 0
end
end
simple_format_without_paragraph(words.join(' '))
end
end
+13 -6
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -21,11 +21,14 @@ class Attachment < ActiveRecord::Base
belongs_to :container, :polymorphic => true belongs_to :container, :polymorphic => true
belongs_to :author, :class_name => "User", :foreign_key => "author_id" belongs_to :author, :class_name => "User", :foreign_key => "author_id"
@@max_size = $RDM_ATTACHMENT_MAX_SIZE || 5*1024*1024
cattr_reader :max_size
validates_presence_of :container, :filename validates_presence_of :container, :filename
validates_inclusion_of :filesize, :in => 1..@@max_size
cattr_accessor :storage_path
@@storage_path = "#{RAILS_ROOT}/files"
def validate
errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
end
def file=(incomming_file) def file=(incomming_file)
unless incomming_file.nil? unless incomming_file.nil?
@@ -63,7 +66,7 @@ class Attachment < ActiveRecord::Base
# Returns file's location on disk # Returns file's location on disk
def diskfile def diskfile
"#{$RDM_STORAGE_PATH}/#{self.disk_filename}" "#{@@storage_path}/#{self.disk_filename}"
end end
def increment_download def increment_download
@@ -75,6 +78,10 @@ class Attachment < ActiveRecord::Base
find(:all, :limit => 5, :order => "downloads DESC") find(:all, :limit => 5, :order => "downloads DESC")
end end
def project
container.is_a?(Project) ? container : container.project
end
private private
def sanitize_filename(value) def sanitize_filename(value)
# get only the filename, not the whole path # get only the filename, not the whole path
+29
View File
@@ -0,0 +1,29 @@
# 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 Board < ActiveRecord::Base
belongs_to :project
has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC"
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
acts_as_list :scope => :project_id
acts_as_watchable
validates_presence_of :name, :description
validates_length_of :name, :maximum => 30
validates_length_of :description, :maximum => 255
end
+22
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 Change < ActiveRecord::Base
belongs_to :changeset
validates_presence_of :changeset_id, :action, :path
end
+69
View File
@@ -0,0 +1,69 @@
# 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 Changeset < ActiveRecord::Base
belongs_to :repository
has_many :changes, :dependent => :delete_all
has_and_belongs_to_many :issues
validates_presence_of :repository_id, :revision, :committed_on, :commit_date
validates_numericality_of :revision, :only_integer => true
validates_uniqueness_of :revision, :scope => :repository_id
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
def committed_on=(date)
self.commit_date = date
super
end
def after_create
scan_comment_for_issue_ids
end
def scan_comment_for_issue_ids
return if comments.blank?
# keywords used to reference issues
ref_keywords = Setting.commit_ref_keywords.downcase.split(",")
# keywords used to fix issues
fix_keywords = Setting.commit_fix_keywords.downcase.split(",")
# status applied
fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw.strip)}.join("|")
return if kw_regexp.blank?
# remove any associated issues
self.issues.clear
comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
action = match[0]
target_issue_ids = match[1].scan(/\d+/)
target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
if fix_status && fix_keywords.include?(action.downcase)
# update status of issues
logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
target_issues.each do |issue|
# don't change the status is the issue is already closed
next if issue.status.is_closed?
issue.status = fix_status
issue.save
end
end
self.issues << target_issues
end
end
end
+1 -1
View File
@@ -19,5 +19,5 @@ class Comment < ActiveRecord::Base
belongs_to :commented, :polymorphic => true, :counter_cache => true belongs_to :commented, :polymorphic => true, :counter_cache => true
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
validates_presence_of :commented, :author, :comment validates_presence_of :commented, :author, :comments
end end
+18 -1
View File
@@ -17,6 +17,7 @@
class CustomField < ActiveRecord::Base class CustomField < ActiveRecord::Base
has_many :custom_values, :dependent => :delete_all has_many :custom_values, :dependent => :delete_all
serialize :possible_values
FIELD_FORMATS = { "string" => { :name => :label_string, :order => 1 }, FIELD_FORMATS = { "string" => { :name => :label_string, :order => 1 },
"text" => { :name => :label_text, :order => 2 }, "text" => { :name => :label_text, :order => 2 },
@@ -30,7 +31,23 @@ class CustomField < ActiveRecord::Base
validates_uniqueness_of :name validates_uniqueness_of :name
validates_format_of :name, :with => /^[\w\s\'\-]*$/i validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys
validates_presence_of :possible_values, :if => Proc.new { |field| field.field_format == "list" }
def initialize(attributes = nil)
super
self.possible_values ||= []
end
def before_validation
# remove empty values
self.possible_values = self.possible_values.collect{|v| v unless v.empty?}.compact
end
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
end
end
# to move in project_custom_field # to move in project_custom_field
def self.for_all def self.for_all
+2 -2
View File
@@ -22,7 +22,7 @@ class CustomValue < ActiveRecord::Base
protected protected
def validate def validate
errors.add(:value, :activerecord_error_blank) and return if custom_field.is_required? and value.empty? errors.add(:value, :activerecord_error_blank) and return if custom_field.is_required? and value.empty?
errors.add(:value, :activerecord_error_invalid) unless custom_field.regexp.empty? or value =~ Regexp.new(custom_field.regexp) 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 and value.length > 0 errors.add(:value, :activerecord_error_too_short) if custom_field.min_length > 0 and value.length < custom_field.min_length and value.length > 0
errors.add(:value, :activerecord_error_too_long) if custom_field.max_length > 0 and value.length > custom_field.max_length errors.add(:value, :activerecord_error_too_long) if custom_field.max_length > 0 and value.length > custom_field.max_length
case custom_field.field_format case custom_field.field_format
@@ -31,7 +31,7 @@ protected
when "date" when "date"
errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ or value.empty? errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ or value.empty?
when "list" when "list"
errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.split('|').include? value or value.empty? errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.include? value or value.empty?
end end
end end
end end
+4 -1
View File
@@ -24,7 +24,8 @@ class Enumeration < ActiveRecord::Base
OPTIONS = { OPTIONS = {
"IPRI" => :enumeration_issue_priorities, "IPRI" => :enumeration_issue_priorities,
"DCAT" => :enumeration_doc_categories "DCAT" => :enumeration_doc_categories,
"ACTI" => :enumeration_activities
}.freeze }.freeze
def self.get_values(option) def self.get_values(option)
@@ -42,6 +43,8 @@ private
raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id]) raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id])
when "DCAT" when "DCAT"
raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id]) raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id])
when "ACTI"
raise "Can't delete enumeration" if TimeEntry.find(:first, :conditions => ["activity_id=?", self.id])
end end
end end
end end
+45 -16
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -16,7 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Issue < ActiveRecord::Base class Issue < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :tracker belongs_to :tracker
belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
@@ -28,9 +27,15 @@ class Issue < ActiveRecord::Base
has_many :journals, :as => :journalized, :dependent => :destroy has_many :journals, :as => :journalized, :dependent => :destroy
has_many :attachments, :as => :container, :dependent => :destroy has_many :attachments, :as => :container, :dependent => :destroy
has_many :time_entries, :dependent => :nullify
has_many :custom_values, :dependent => :delete_all, :as => :customized has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :custom_fields, :through => :custom_values has_many :custom_fields, :through => :custom_values
has_and_belongs_to_many :changesets, :order => "revision ASC"
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
acts_as_watchable
validates_presence_of :subject, :description, :priority, :tracker, :author, :status validates_presence_of :subject, :description, :priority, :tracker, :author, :status
validates_inclusion_of :done_ratio, :in => 0..100 validates_inclusion_of :done_ratio, :in => 0..100
@@ -38,7 +43,7 @@ class Issue < ActiveRecord::Base
# set default status for new issues # set default status for new issues
def before_validation def before_validation
self.status = IssueStatus.default if new_record? self.status = IssueStatus.default if status.nil?
end end
def validate def validate
@@ -49,11 +54,18 @@ class Issue < ActiveRecord::Base
if self.due_date and self.start_date and self.due_date < self.start_date 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, :activerecord_error_greater_than_start_date
end end
if start_date && soonest_start && start_date < soonest_start
errors.add :start_date, :activerecord_error_invalid
end
end end
#def before_create def before_create
# build_history # default assignment based on category
#end if assigned_to.nil? && category && category.assigned_to
self.assigned_to = category.assigned_to
end
end
def before_save def before_save
if @current_journal if @current_journal
@@ -75,8 +87,8 @@ class Issue < ActiveRecord::Base
end end
end end
def long_id def after_save
"%05d" % self.id relations_from.each(&:set_issue_to_dates)
end end
def custom_value_for(custom_field) def custom_value_for(custom_field)
@@ -92,11 +104,28 @@ class Issue < ActiveRecord::Base
@current_journal @current_journal
end end
private def spent_hours
# Creates an history for the issue @spent_hours ||= time_entries.sum(:hours) || 0
#def build_history end
# @history = self.histories.build
# @history.status = self.status def relations
# @history.author = self.author (relations_from + relations_to).sort
#end end
def all_dependent_issues
dependencies = []
relations_from.each do |relation|
dependencies << relation.issue_to
dependencies += relation.issue_to.all_dependent_issues
end
dependencies
end
def duration
(start_date && due_date) ? due_date - start_date : 0
end
def soonest_start
@soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
end
end end
+1
View File
@@ -18,6 +18,7 @@
class IssueCategory < ActiveRecord::Base class IssueCategory < ActiveRecord::Base
before_destroy :check_integrity before_destroy :check_integrity
belongs_to :project belongs_to :project
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id] validates_uniqueness_of :name, :scope => [:project_id]
+2 -2
View File
@@ -16,8 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueCustomField < CustomField class IssueCustomField < CustomField
has_and_belongs_to_many :projects, :join_table => "custom_fields_projects", :foreign_key => "custom_field_id" has_and_belongs_to_many :projects, :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :foreign_key => "custom_field_id"
has_and_belongs_to_many :trackers, :join_table => "custom_fields_trackers", :foreign_key => "custom_field_id" has_and_belongs_to_many :trackers, :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :foreign_key => "custom_field_id"
has_many :issues, :through => :issue_custom_values has_many :issues, :through => :issue_custom_values
def type_name def type_name
+79
View File
@@ -0,0 +1,79 @@
# 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 IssueRelation < ActiveRecord::Base
belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id'
belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id'
TYPE_RELATES = "relates"
TYPE_DUPLICATES = "duplicates"
TYPE_BLOCKS = "blocks"
TYPE_PRECEDES = "precedes"
TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 },
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicates, :order => 2 },
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 },
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 },
}.freeze
validates_presence_of :issue_from, :issue_to, :relation_type
validates_inclusion_of :relation_type, :in => TYPES.keys
validates_numericality_of :delay, :allow_nil => true
validates_uniqueness_of :issue_to_id, :scope => :issue_from_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
errors.add_to_base :activerecord_error_circular_dependency if issue_to.all_dependent_issues.include? issue_from
end
end
def other_issue(issue)
(self.issue_from_id == issue.id) ? issue_to : issue_from
end
def label_for(issue)
TYPES[relation_type] ? TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : :unknow
end
def before_save
if TYPE_PRECEDES == relation_type
self.delay ||= 0
else
self.delay = nil
end
set_issue_to_dates
end
def set_issue_to_dates
soonest_start = self.successor_soonest_start
if soonest_start && (!issue_to.start_date || issue_to.start_date < soonest_start)
issue_to.start_date, issue_to.due_date = successor_soonest_start, successor_soonest_start + issue_to.duration
issue_to.save
end
end
def successor_soonest_start
return nil unless (TYPE_PRECEDES == self.relation_type) && (issue_from.start_date || issue_from.due_date)
(issue_from.due_date || issue_from.start_date) + 1 + delay
end
def <=>(relation)
TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
end
end
+16 -8
View File
@@ -17,7 +17,8 @@
class IssueStatus < ActiveRecord::Base class IssueStatus < ActiveRecord::Base
before_destroy :check_integrity before_destroy :check_integrity
has_many :workflows, :foreign_key => "old_status_id" has_many :workflows, :foreign_key => "old_status_id", :dependent => :delete_all
acts_as_list
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
@@ -26,7 +27,7 @@ class IssueStatus < ActiveRecord::Base
validates_format_of :html_color, :with => /^[a-f0-9]*$/i validates_format_of :html_color, :with => /^[a-f0-9]*$/i
def before_save def before_save
IssueStatus.update_all "is_default=false" if self.is_default? IssueStatus.update_all "is_default=#{connection.quoted_false}" if self.is_default?
end end
# Returns the default status for new issues # Returns the default status for new issues
@@ -35,16 +36,23 @@ class IssueStatus < ActiveRecord::Base
end end
# Returns an array of all statuses the given role can switch to # Returns an array of all statuses the given role can switch to
# Uses association cache when called more than one time
def new_statuses_allowed_to(role, tracker) def new_statuses_allowed_to(role, tracker)
statuses = [] new_statuses = workflows.select {|w| w.role_id == role.id && w.tracker_id == tracker.id}.collect{|w| w.new_status} if role && tracker
for workflow in self.workflows new_statuses ? new_statuses.compact.sort{|x, y| x.position <=> y.position } : []
statuses << workflow.new_status if workflow.role_id == role.id and workflow.tracker_id == tracker.id end
end unless role.nil? or tracker.nil?
statuses # 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 } : []
end end
private private
def check_integrity def check_integrity
raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id]) or IssueHistory.find(:first, :conditions => ["status_id=?", self.id]) raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
end end
end end
+4
View File
@@ -17,6 +17,10 @@
class Journal < ActiveRecord::Base class Journal < ActiveRecord::Base
belongs_to :journalized, :polymorphic => true belongs_to :journalized, :polymorphic => true
# added as a quick fix to allow eager loading of the polymorphic association
# since always associated to an issue, for now
belongs_to :issue, :foreign_key => :journalized_id
belongs_to :user belongs_to :user
has_many :details, :class_name => "JournalDetail", :dependent => :delete_all has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
end end
+5
View File
@@ -17,4 +17,9 @@
class JournalDetail < ActiveRecord::Base class JournalDetail < ActiveRecord::Base
belongs_to :journal belongs_to :journal
def before_save
self.value = value[0..254] if value && value.is_a?(String)
self.old_value = old_value[0..254] if old_value && old_value.is_a?(String)
end
end end
+40
View File
@@ -0,0 +1,40 @@
# 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 MailHandler < ActionMailer::Base
# Processes incoming emails
# Currently, it only supports adding a note to an existing issue
# by replying to the initial notification message
def receive(email)
# find related issue by parsing the subject
m = email.subject.match %r{\[.*#(\d+)\]}
return unless m
issue = Issue.find_by_id(m[1])
return unless issue
# find user
user = User.find_active(:first, :conditions => {:mail => email.from.first})
return unless user
# check permission
return unless Permission.allowed_to_role("issues/add_note", user.role_for_project(issue.project))
# add the note
issue.init_journal(user, email.body.chomp)
issue.save
end
end
+70 -7
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -16,38 +16,101 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Mailer < ActionMailer::Base class Mailer < ActionMailer::Base
helper IssuesHelper helper IssuesHelper
def account_information(user, password)
set_language_if_valid user.language
recipients user.mail
from Setting.mail_from
subject l(:mail_subject_register)
body :user => user, :password => password
end
def issue_add(issue) def issue_add(issue)
set_language_if_valid(Setting.default_language)
# Sends to all project members # Sends to all project members
@recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }.compact @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }.compact
@from = $RDM_MAIL_FROM # Sends to author and assignee (even if they turned off mail notification)
@recipients << issue.author.mail if issue.author
@recipients << issue.assigned_to.mail if issue.assigned_to
@recipients.compact!
@recipients.uniq!
@from = Setting.mail_from
@subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}" @subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
@body['issue'] = issue @body['issue'] = issue
end end
def issue_edit(journal) def issue_edit(journal)
set_language_if_valid(Setting.default_language)
# Sends to all project members # Sends to all project members
issue = journal.journalized issue = journal.journalized
@recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }.compact @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
@from = $RDM_MAIL_FROM # Sends to author and assignee (even if they turned off mail notification)
@recipients << issue.author.mail if issue.author
@recipients << issue.assigned_to.mail if issue.assigned_to
@recipients.compact!
@recipients.uniq!
# Watchers in cc
@cc = issue.watcher_recipients - @recipients
@from = Setting.mail_from
@subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}" @subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
@body['issue'] = issue @body['issue'] = issue
@body['journal']= journal @body['journal']= journal
end end
def document_add(document)
set_language_if_valid(Setting.default_language)
@recipients = document.project.users.collect { |u| u.mail if u.mail_notification }.compact
@from = Setting.mail_from
@subject = "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
@body['document'] = document
end
def attachments_add(attachments)
set_language_if_valid(Setting.default_language)
container = attachments.first.container
url = "http://#{Setting.host_name}/"
added_to = ""
case container.class.to_s
when 'Version'
url << "projects/list_files/#{container.project_id}"
added_to = "#{l(:label_version)}: #{container.name}"
when 'Document'
url << "documents/show/#{container.id}"
added_to = "#{l(:label_document)}: #{container.title}"
when 'Issue'
url << "issues/show/#{container.id}"
added_to = "#{container.tracker.name} ##{container.id}: #{container.subject}"
end
@recipients = container.project.users.collect { |u| u.mail if u.mail_notification }.compact
@from = Setting.mail_from
@subject = "[#{container.project.name}] #{l(:label_attachment_new)}"
@body['attachments'] = attachments
@body['url'] = url
@body['added_to'] = added_to
end
def lost_password(token) def lost_password(token)
set_language_if_valid(token.user.language)
@recipients = token.user.mail @recipients = token.user.mail
@from = $RDM_MAIL_FROM @from = Setting.mail_from
@subject = l(:mail_subject_lost_password) @subject = l(:mail_subject_lost_password)
@body['token'] = token @body['token'] = token
end end
def register(token) def register(token)
set_language_if_valid(token.user.language)
@recipients = token.user.mail @recipients = token.user.mail
@from = $RDM_MAIL_FROM @from = Setting.mail_from
@subject = l(:mail_subject_register) @subject = l(:mail_subject_register)
@body['token'] = token @body['token'] = token
end end
def message_posted(message, recipients)
set_language_if_valid(Setting.default_language)
@recipients = recipients
@from = Setting.mail_from
@subject = "[#{message.board.project.name} - #{message.board.name}] #{message.subject}"
@body['message'] = message
end
end end
+6 -1
View File
@@ -24,6 +24,11 @@ class Member < ActiveRecord::Base
validates_uniqueness_of :user_id, :scope => :project_id validates_uniqueness_of :user_id, :scope => :project_id
def name def name
self.user.display_name self.user.name
end
def before_destroy
# remove category based auto assignments for this member
project.issue_categories.update_all "assigned_to_id = NULL", ["assigned_to_id = ?", self.user.id]
end end
end end
+41
View File
@@ -0,0 +1,41 @@
# 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 Message < ActiveRecord::Base
belongs_to :board
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC"
has_many :attachments, :as => :container, :dependent => :destroy
belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id'
validates_presence_of :subject, :content
validates_length_of :subject, :maximum => 255
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
end
def project
board.project
end
end
+24
View File
@@ -0,0 +1,24 @@
# 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 MessageObserver < ActiveRecord::Observer
def after_create(message)
# send notification to board watchers
recipients = message.board.watcher_recipients
Mailer.deliver_message_posted(message, recipients) unless recipients.empty?
end
end
+1 -1
View File
@@ -24,6 +24,6 @@ class News < ActiveRecord::Base
# returns latest news for projects visible by user # returns latest news for projects visible by user
def self.latest(user=nil, count=5) def self.latest(user=nil, count=5)
find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "news.created_on DESC") find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
end end
end end
+5 -2
View File
@@ -30,7 +30,10 @@ class Permission < ActiveRecord::Base
1100 => :label_news_plural, 1100 => :label_news_plural,
1200 => :label_document_plural, 1200 => :label_document_plural,
1300 => :label_attachment_plural, 1300 => :label_attachment_plural,
1400 => :label_repository 1400 => :label_repository,
1500 => :label_time_tracking,
1700 => :label_wiki_page_plural,
2000 => :label_board_plural
}.freeze }.freeze
@@cached_perms_for_public = nil @@cached_perms_for_public = nil
@@ -56,7 +59,7 @@ class Permission < ActiveRecord::Base
find(:all, :include => :roles).each {|p| perms.store "#{p.controller}/#{p.action}", p.roles.collect {|r| r.id } } find(:all, :include => :roles).each {|p| perms.store "#{p.controller}/#{p.action}", p.roles.collect {|r| r.id } }
perms perms
end end
allowed_to_public(action) or (@@cached_perms_for_roles[action] and @@cached_perms_for_roles[action].include? role) allowed_to_public(action) or (role && @@cached_perms_for_roles[action] && @@cached_perms_for_roles[action].include?(role.id))
end end
def self.allowed_to_role_expired def self.allowed_to_role_expired
+71 -18
View File
@@ -16,55 +16,108 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Project < ActiveRecord::Base class Project < ActiveRecord::Base
has_many :versions, :dependent => :destroy, :order => "versions.effective_date DESC, versions.name DESC" # Project statuses
has_many :members, :dependent => :delete_all, :include => :user, :conditions => "users.status=#{User::STATUS_ACTIVE}" STATUS_ACTIVE = 1
STATUS_ARCHIVED = 9
has_many :members, :dependent => :delete_all, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
has_many :users, :through => :members has_many :users, :through => :members
has_many :custom_values, :dependent => :delete_all, :as => :customized has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :issues, :dependent => :destroy, :order => "issues.created_on DESC", :include => [:status, :tracker] has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
has_many :time_entries, :dependent => :delete_all
has_many :queries, :dependent => :delete_all has_many :queries, :dependent => :delete_all
has_many :documents, :dependent => :destroy has_many :documents, :dependent => :destroy
has_many :news, :dependent => :delete_all, :include => :author has_many :news, :dependent => :delete_all, :include => :author
has_many :issue_categories, :dependent => :delete_all, :order => "issue_categories.name" has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
has_many :boards, :order => "position ASC"
has_one :repository, :dependent => :destroy has_one :repository, :dependent => :destroy
has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => 'custom_fields_projects', :association_foreign_key => 'custom_field_id' has_one :wiki, :dependent => :destroy
has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :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_tree :order => "name", :counter_cache => true
validates_presence_of :name, :description attr_protected :status
validates_uniqueness_of :name
validates_presence_of :name, :description, :identifier
validates_uniqueness_of :name, :identifier
validates_associated :custom_values, :on => :update validates_associated :custom_values, :on => :update
validates_associated :repository validates_associated :repository, :wiki
validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\'\-]*$/i validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_length_of :description, :maximum => 255
validates_length_of :identifier, :in => 3..12
validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
def identifier=(identifier)
super unless identifier_frozen?
end
def identifier_frozen?
errors[:identifier].nil? && !(new_record? || identifier.blank?)
end
def issues_with_subprojects(include_subprojects=false)
conditions = nil
if include_subprojects && !active_children.empty?
ids = [id] + active_children.collect {|c| c.id}
conditions = ["#{Issue.table_name}.project_id IN (#{ids.join(',')})"]
end
conditions ||= ["#{Issue.table_name}.project_id = ?", id]
Issue.with_scope :find => { :conditions => conditions } do
yield
end
end
# returns latest created projects # returns latest created projects
# non public projects will be returned only if user is a member of those # non public projects will be returned only if user is a member of those
def self.latest(user=nil, count=5) def self.latest(user=nil, count=5)
find(:all, :limit => count, :conditions => visible_by(user), :order => "projects.created_on DESC") find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
end end
def self.visible_by(user=nil) def self.visible_by(user=nil)
if user && !user.memberships.empty? if user && user.admin?
return ["projects.is_public = ? or projects.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')})", true] return ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
elsif user && user.memberships.any?
return ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = ? or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))", true]
else else
return ["projects.is_public = ?", true] return ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = ?", true]
end end
end end
def active?
self.status == STATUS_ACTIVE
end
def archive
# Archive subprojects if any
children.each do |subproject|
subproject.archive
end
update_attribute :status, STATUS_ARCHIVED
end
def unarchive
return false if parent && !parent.active?
update_attribute :status, STATUS_ACTIVE
end
def active_children
children.select {|child| child.active?}
end
# Returns an array of all custom fields enabled for project issues # Returns an array of all custom fields enabled for project issues
# (explictly associated custom fields and custom fields enabled for all projects) # (explictly associated custom fields and custom fields enabled for all projects)
def custom_fields_for_issues(tracker) def custom_fields_for_issues(tracker)
tracker.custom_fields.find(:all, :include => :projects, all_custom_fields.select {|c| tracker.custom_fields.include? c }
:conditions => ["is_for_all=? or project_id=?", true, self.id])
#(CustomField.for_all + custom_fields).uniq
end end
def all_custom_fields def all_custom_fields
@all_custom_fields ||= IssueCustomField.find(:all, :include => :projects, @all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
:conditions => ["is_for_all=? or project_id=?", true, self.id])
end end
protected protected
def validate def validate
errors.add(parent_id, " must be a root project") if parent and parent.parent 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 projects_count > 0 errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
end end
end end
+102 -29
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -21,6 +21,7 @@ class Query < ActiveRecord::Base
serialize :filters serialize :filters
attr_protected :project, :user attr_protected :project, :user
attr_accessor :executed_by
validates_presence_of :name, :on => :save validates_presence_of :name, :on => :save
@@ -45,8 +46,10 @@ class Query < ActiveRecord::Base
@@operators_by_filter_type = { :list => [ "=", "!" ], @@operators_by_filter_type = { :list => [ "=", "!" ],
:list_status => [ "o", "=", "!", "c", "*" ], :list_status => [ "o", "=", "!", "c", "*" ],
:list_optional => [ "=", "!", "!*", "*" ], :list_optional => [ "=", "!", "!*", "*" ],
:list_one_or_more => [ "*", "=" ],
:date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ], :date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ],
:date_past => [ ">t-", "<t-", "t-", "t" ], :date_past => [ ">t-", "<t-", "t-", "t" ],
:string => [ "=", "~", "!", "!~" ],
:text => [ "~", "!~" ] } :text => [ "~", "!~" ] }
cattr_reader :operators_by_filter_type cattr_reader :operators_by_filter_type
@@ -54,12 +57,16 @@ class Query < ActiveRecord::Base
def initialize(attributes = nil) def initialize(attributes = nil)
super attributes super attributes
self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
self.is_public = true end
def executed_by=(user)
@executed_by = user
set_language_if_valid(user.language) if user
end end
def validate def validate
filters.each_key do |field| filters.each_key do |field|
errors.add field.gsub(/\_id$/, ""), :activerecord_error_blank unless errors.add label_for(field), :activerecord_error_blank unless
# filter requires one or more values # filter requires one or more values
(values_for(field) and !values_for(field).first.empty?) or (values_for(field) and !values_for(field).first.empty?) or
# filter doesn't require any value # filter doesn't require any value
@@ -67,21 +74,50 @@ class Query < ActiveRecord::Base
end if filters end if filters
end end
def editable_by?(user)
return false unless user
return true if !is_public && self.user_id == user.id
is_public && user.authorized_to(project, "projects/add_query")
end
def available_filters def available_filters
return @available_filters if @available_filters return @available_filters if @available_filters
@available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all).collect{|s| [s.name, s.id.to_s] } }, @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 => Tracker.find(:all).collect{|s| [s.name, s.id.to_s] } }, "tracker_id" => { :type => :list, :order => 2, :values => Tracker.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
"priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } }, "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } },
"subject" => { :type => :text, :order => 7 }, "subject" => { :type => :text, :order => 8 },
"created_on" => { :type => :date_past, :order => 8 }, "created_on" => { :type => :date_past, :order => 9 },
"updated_on" => { :type => :date_past, :order => 9 }, "updated_on" => { :type => :date_past, :order => 10 },
"start_date" => { :type => :date, :order => 10 }, "start_date" => { :type => :date, :order => 11 },
"due_date" => { :type => :date, :order => 11 } } "due_date" => { :type => :date, :order => 12 } }
unless project.nil? unless project.nil?
# project specific filters # project specific filters
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => @project.users.collect{|s| [s.name, s.id.to_s] } } user_values = []
@available_filters["author_id"] = { :type => :list, :order => 5, :values => @project.users.collect{|s| [s.name, s.id.to_s] } } user_values << ["<< #{l(:label_me)} >>", "me"] if executed_by
user_values += @project.users.collect{|s| [s.name, s.id.to_s] }
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values }
@available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values }
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } } @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
unless @project.active_children.empty?
@available_filters["subproject_id"] = { :type => :list_one_or_more, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
end
@project.all_custom_fields.select(&:is_filter?).each do |field|
case field.field_format
when "string", "int"
options = { :type => :string, :order => 20 }
when "text"
options = { :type => :text, :order => 20 }
when "list"
options = { :type => :list_optional, :values => field.possible_values, :order => 20}
when "date"
options = { :type => :date, :order => 20 }
when "bool"
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
end
@available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
end
# remove category filter if no category defined # remove category filter if no category defined
@available_filters.delete "category_id" if @available_filters["category_id"][:values].empty? @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
end end
@@ -121,45 +157,82 @@ class Query < ActiveRecord::Base
has_filter?(field) ? filters[field][:values] : nil has_filter?(field) ? filters[field][:values] : nil
end end
def label_for(field)
label = @available_filters[field][:name] if @available_filters.has_key?(field)
label ||= field.gsub(/\_id$/, "")
end
def statement def statement
sql = "1=1" sql = "1=1"
sql << " AND issues.project_id=%d" % project.id if project if has_filter?("subproject_id")
subproject_ids = []
if operator_for("subproject_id") == "="
subproject_ids = values_for("subproject_id").each(&:to_i)
else
subproject_ids = project.active_children.collect{|p| p.id}
end
sql << " AND #{Issue.table_name}.project_id IN (%d,%s)" % [project.id, subproject_ids.join(",")] if project
else
sql << " AND #{Issue.table_name}.project_id=%d" % project.id if project
end
filters.each_key do |field| filters.each_key do |field|
v = values_for field next if field == "subproject_id"
v = values_for(field).clone
next unless v and !v.empty? next unless v and !v.empty?
sql = sql + " AND " unless sql.empty? sql = sql + " AND " unless sql.empty?
sql << "("
if field =~ /^cf_(\d+)$/
# custom field
db_table = CustomValue.table_name
db_field = "value"
sql << "#{db_table}.custom_field_id = #{$1} AND "
else
# regular field
db_table = Issue.table_name
db_field = field
end
# "me" value subsitution
if %w(assigned_to_id author_id).include?(field)
v.push(executed_by ? executed_by.id.to_s : "0") if v.delete("me")
end
case operator_for field case operator_for field
when "=" when "="
sql = sql + "issues.#{field} IN (" + v.each(&:to_i).join(",") + ")" sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
when "!" when "!"
sql = sql + "issues.#{field} NOT IN (" + v.each(&:to_i).join(",") + ")" sql = sql + "#{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
when "!*" when "!*"
sql = sql + "issues.#{field} IS NULL" sql = sql + "#{db_table}.#{db_field} IS NULL"
when "*" when "*"
sql = sql + "issues.#{field} IS NOT NULL" sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
when "o" when "o"
sql = sql + "issue_statuses.is_closed=#{connection.quoted_false}" if field == "status_id" sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
when "c" when "c"
sql = sql + "issue_statuses.is_closed=#{connection.quoted_true}" if field == "status_id" sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
when ">t-" when ">t-"
sql = sql + "issues.#{field} >= '%s'" % connection.quoted_date(Date.today - v.first.to_i) sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)]
when "<t-" when "<t-"
sql = sql + "issues.#{field} <= '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'" sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time)
when "t-" when "t-"
sql = sql + "issues.#{field} = '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'" sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)]
when ">t+" when ">t+"
sql = sql + "issues.#{field} >= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'" sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
when "<t+" when "<t+"
sql = sql + "issues.#{field} <= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'" sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
when "t+" when "t+"
sql = sql + "issues.#{field} = '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'" sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
when "t" when "t"
sql = sql + "issues.#{field} = '%s'" % connection.quoted_date(Date.today) sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)]
when "~" when "~"
sql = sql + "issues.#{field} LIKE '%#{connection.quote_string(v.first)}%'" sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
when "!~" when "!~"
sql = sql + "issues.#{field} NOT LIKE '%#{connection.quote_string(v.first)}%'" sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
end end
sql << ")"
end if filters and valid? end if filters and valid?
sql sql
end end
+57 -6
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,12 +17,63 @@
class Repository < ActiveRecord::Base class Repository < ActiveRecord::Base
belongs_to :project belongs_to :project
validates_presence_of :url has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.revision DESC"
validates_format_of :url, :with => /^(http|https|svn|file):\/\/.+/i has_many :changes, :through => :changesets
@scm = nil
def scm def scm
@scm ||= SvnRepos::Base.new url @scm ||= self.scm_adapter.new url, root_url, login, password
update_attribute(:root_url, @scm.root_url) if root_url.blank?
@scm
end
def scm_name
self.class.scm_name
end
def supports_cat?
scm.supports_cat?
end
def entries(path=nil, identifier=nil)
scm.entries(path, identifier)
end
def diff(path, rev, rev_to, type)
scm.diff(path, rev, rev_to, type)
end
def latest_changeset
@latest_changeset ||= changesets.find(:first)
end
def scan_changesets_for_issue_ids
self.changesets.each(&:scan_comment_for_issue_ids)
end
# fetch new changesets for all repositories
# can be called periodically by an external script
# eg. ruby script/runner "Repository.fetch_changesets"
def self.fetch_changesets
find(:all).each(&:fetch_changesets)
end
# scan changeset comments to find related and fixed issues for all repositories
def self.scan_changesets_for_issue_ids
find(:all).each(&:scan_changesets_for_issue_ids)
end
def self.scm_name
'Abstract'
end
def self.available_scm
subclasses.collect {|klass| [klass.scm_name, klass.name]}
end
def self.factory(klass_name, *args)
klass = "Repository::#{klass_name}".constantize
klass.new(*args)
rescue
nil
end end
end end
+150
View File
@@ -0,0 +1,150 @@
# 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.
require 'redmine/scm/adapters/cvs_adapter'
require 'digest/sha1'
class Repository::Cvs < Repository
validates_presence_of :url, :root_url
def scm_adapter
Redmine::Scm::Adapters::CvsAdapter
end
def self.scm_name
'CVS'
end
def entry(path, identifier)
e = entries(path, identifier)
e ? e.first : nil
end
def entries(path=nil, identifier=nil)
entries=scm.entries(path, identifier)
if entries
entries.each() do |entry|
unless entry.lastrev.nil? || entry.lastrev.identifier
change=changes.find_by_revision_and_path( entry.lastrev.revision, scm.with_leading_slash(entry.path) )
if change
entry.lastrev.identifier=change.changeset.revision
entry.lastrev.author=change.changeset.committer
entry.lastrev.revision=change.revision
entry.lastrev.branch=change.branch
end
end
end
end
entries
end
def diff(path, rev, rev_to, type)
#convert rev to revision. CVS can't handle changesets here
diff=[]
changeset_from=changesets.find_by_revision(rev)
if rev_to.to_i > 0
changeset_to=changesets.find_by_revision(rev_to)
end
changeset_from.changes.each() do |change_from|
revision_from=nil
revision_to=nil
revision_from=change_from.revision if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
if revision_from
if changeset_to
changeset_to.changes.each() do |change_to|
revision_to=change_to.revision if change_to.path==change_from.path
end
end
unless revision_to
revision_to=scm.get_previous_revision(revision_from)
end
diff=diff+scm.diff(change_from.path, revision_from, revision_to, type)
end
end
return diff
end
def fetch_changesets
#not the preferred way with CVS. maybe we should introduce always a cron-job for this
last_commit = changesets.maximum(:committed_on)
# some nifty bits to introduce a commit-id with cvs
# natively cvs doesn't provide any kind of changesets, there is only a revision per file.
# we now take a guess using the author, the commitlog and the commit-date.
# last one is the next step to take. the commit-date is not equal for all
# commits in one changeset. cvs update the commit-date when the *,v file was touched. so
# we use a small delta here, to merge all changes belonging to _one_ changeset
time_delta=10.seconds
transaction do
scm.revisions('', last_commit, nil, :with_paths => true) do |revision|
# only add the change to the database, if it doen't exists. the cvs log
# is not exclusive at all.
unless changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
revision
cs=Changeset.find(:first, :conditions=>{
:committed_on=>revision.time-time_delta..revision.time+time_delta,
:committer=>revision.author,
:comments=>revision.message
})
# create a new changeset....
unless cs
# we use a negative changeset-number here (just for inserting)
# later on, we calculate a continous positive number
next_rev = changesets.minimum(:revision)
next_rev = 0 if next_rev.nil? or next_rev > 0
next_rev = next_rev - 1
cs=Changeset.create(:repository => self,
:revision => next_rev,
:committer => revision.author,
:committed_on => revision.time,
:comments => revision.message)
end
#convert CVS-File-States to internal Action-abbrevations
#default action is (M)odified
action="M"
if revision.paths[0][:action]=="Exp" && revision.paths[0][:revision]=="1.1"
action="A" #add-action always at first revision (= 1.1)
elsif revision.paths[0][:action]=="dead"
action="D" #dead-state is similar to Delete
end
Change.create(:changeset => cs,
:action => action,
:path => scm.with_leading_slash(revision.paths[0][:path]),
:revision => revision.paths[0][:revision],
:branch => revision.paths[0][:branch]
)
end
end
next_rev = [changesets.maximum(:revision) || 0, 0].max
changesets.find(:all, :conditions=>["revision < 0"], :order=>"committed_on ASC").each() do |changeset|
next_rev = next_rev + 1
changeset.revision = next_rev
changeset.save!
end
end
end
end
+90
View File
@@ -0,0 +1,90 @@
# 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.
require 'redmine/scm/adapters/darcs_adapter'
class Repository::Darcs < Repository
validates_presence_of :url
def scm_adapter
Redmine::Scm::Adapters::DarcsAdapter
end
def self.scm_name
'Darcs'
end
def entries(path=nil, identifier=nil)
entries=scm.entries(path, identifier)
if entries
entries.each do |entry|
# Search the DB for the entry's last change
changeset = changesets.find_by_scmid(entry.lastrev.scmid) if entry.lastrev && !entry.lastrev.scmid.blank?
if changeset
entry.lastrev.identifier = changeset.revision
entry.lastrev.name = changeset.revision
entry.lastrev.time = changeset.committed_on
entry.lastrev.author = changeset.committer
end
end
end
entries
end
def diff(path, rev, rev_to, type)
patch_from = changesets.find_by_revision(rev)
patch_to = changesets.find_by_revision(rev_to) if rev_to
if path.blank?
path = patch_from.changes.collect{|change| change.path}.join(' ')
end
scm.diff(path, patch_from.scmid, patch_to.scmid, type)
end
def fetch_changesets
scm_info = scm.info
if scm_info
db_last_id = latest_changeset ? latest_changeset.scmid : nil
next_rev = latest_changeset ? latest_changeset.revision + 1 : 1
# latest revision in the repository
scm_revision = scm_info.lastrev.scmid
unless changesets.find_by_scmid(scm_revision)
revisions = scm.revisions('', db_last_id, nil, :with_path => true)
transaction do
revisions.reverse_each do |revision|
changeset = Changeset.create(:repository => self,
:revision => next_rev,
:scmid => revision.scmid,
:committer => revision.author,
:committed_on => revision.time,
:comments => revision.message)
next if changeset.new_record?
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
next_rev += 1
end if revisions
end
end
end
end
end
+81
View File
@@ -0,0 +1,81 @@
# 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.
require 'redmine/scm/adapters/mercurial_adapter'
class Repository::Mercurial < Repository
attr_protected :root_url
validates_presence_of :url
def scm_adapter
Redmine::Scm::Adapters::MercurialAdapter
end
def self.scm_name
'Mercurial'
end
def entries(path=nil, identifier=nil)
entries=scm.entries(path, identifier)
if entries
entries.each do |entry|
next unless entry.is_file?
# Search the DB for the entry's last change
change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
if change
entry.lastrev.identifier = change.changeset.revision
entry.lastrev.name = change.changeset.revision
entry.lastrev.author = change.changeset.committer
entry.lastrev.revision = change.revision
end
end
end
entries
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.identifier.to_i
unless changesets.find_by_revision(scm_revision)
revisions = scm.revisions('', db_revision, nil)
transaction do
revisions.reverse_each do |revision|
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
end
+69
View File
@@ -0,0 +1,69 @@
# 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.
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|file):\/\/.+/i
def scm_adapter
Redmine::Scm::Adapters::SubversionAdapter
end
def self.scm_name
'Subversion'
end
def fetch_changesets
scm_info = scm.info
if scm_info
# latest revision found in database
db_revision = latest_changeset ? latest_changeset.revision : 0
# latest revision in the repository
scm_revision = scm_info.lastrev.identifier.to_i
if db_revision < scm_revision
logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
identifier_from = db_revision + 1
while (identifier_from <= scm_revision)
# loads changesets by batches of 200
identifier_to = [identifier_from + 199, scm_revision].min
revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
transaction do
revisions.reverse_each do |revision|
changeset = Changeset.create(:repository => self,
:revision => revision.identifier,
: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 unless revisions.nil?
identifier_from = identifier_to + 1
end
end
end
end
end
+5
View File
@@ -20,11 +20,16 @@ class Role < ActiveRecord::Base
has_and_belongs_to_many :permissions has_and_belongs_to_many :permissions
has_many :workflows, :dependent => :delete_all has_many :workflows, :dependent => :delete_all
has_many :members has_many :members
acts_as_list
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
validates_format_of :name, :with => /^[\w\s\'\-]*$/i validates_format_of :name, :with => /^[\w\s\'\-]*$/i
def <=>(role)
position <=> role.position
end
private private
def check_integrity def check_integrity
raise "Can't delete role" if Member.find(:first, :conditions =>["role_id=?", self.id]) raise "Can't delete role" if Member.find(:first, :conditions =>["role_id=?", self.id])
+61
View File
@@ -0,0 +1,61 @@
# 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 Setting < ActiveRecord::Base
cattr_accessor :available_settings
@@available_settings = YAML::load(File.open("#{RAILS_ROOT}/config/settings.yml"))
validates_uniqueness_of :name
validates_inclusion_of :name, :in => @@available_settings.keys
validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| @@available_settings[setting.name]['format'] == 'int' }
def self.get(name)
name = name.to_s
setting = find_by_name(name)
setting ||= new(:name => name, :value => @@available_settings[name]['default']) if @@available_settings.has_key? name
setting
end
def self.[](name)
get(name).value
end
def self.[]=(name, value)
setting = get(name)
setting.value = (value ? value.to_s : "")
setting.save
setting.value
end
@@available_settings.each do |name, params|
src = <<-END_SRC
def self.#{name}
self[:#{name}]
end
def self.#{name}?
self[:#{name}].to_i > 0
end
def self.#{name}=(value)
self[:#{name}] = value
end
END_SRC
class_eval src, __FILE__, __LINE__
end
end
-214
View File
@@ -1,214 +0,0 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'rexml/document'
module SvnRepos
class CommandFailed < StandardError #:nodoc:
end
class Base
@url = nil
@login = nil
@password = nil
def initialize(url, login=nil, password=nil)
@url = url
@login = login if login && !login.empty?
@password = (password || "") if @login
end
# Returns the entry identified by path and revision identifier
# or nil if entry doesn't exist in the repository
def entry(path=nil, identifier=nil)
e = entries(path, identifier)
e ? e.first : nil
end
# Returns an Entries collection
# or nil if the given path doesn't exist in the repository
def entries(path=nil, identifier=nil)
path ||= ''
identifier = 'HEAD' unless identifier and identifier > 0
entries = Entries.new
cmd = "svn list --xml #{target(path)}@#{identifier}"
shellout(cmd) do |io|
begin
doc = REXML::Document.new(io)
doc.elements.each("lists/list/entry") do |entry|
entries << Entry.new({:name => entry.elements['name'].text,
:path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
:kind => entry.attributes['kind'],
:size => (entry.elements['size'] and entry.elements['size'].text).to_i,
:lastrev => Revision.new({
:identifier => entry.elements['commit'].attributes['revision'],
:time => Time.parse(entry.elements['commit'].elements['date'].text),
:author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "anonymous")
})
})
end
rescue
end
end
return nil if $? && $?.exitstatus != 0
entries.sort_by_name
rescue Errno::ENOENT => e
raise CommandFailed
end
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path ||= ''
identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0
identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
revisions = Revisions.new
cmd = "svn log --xml -r #{identifier_from}:#{identifier_to} "
cmd << "--verbose " if options[:with_paths]
cmd << target(path)
shellout(cmd) do |io|
begin
doc = REXML::Document.new(io)
doc.elements.each("log/logentry") do |logentry|
paths = []
logentry.elements.each("paths/path") do |path|
paths << {:action => path.attributes['action'],
:path => path.text
}
end
paths.sort! { |x,y| x[:path] <=> y[:path] }
revisions << Revision.new({:identifier => logentry.attributes['revision'],
:author => (logentry.elements['author'] ? logentry.elements['author'].text : "anonymous"),
:time => Time.parse(logentry.elements['date'].text),
:message => logentry.elements['msg'].text,
:paths => paths
})
end
rescue
end
end
return nil if $? && $?.exitstatus != 0
revisions
rescue Errno::ENOENT => e
raise CommandFailed
end
def diff(path, identifier_from, identifier_to=nil)
path ||= ''
if identifier_to and identifier_to.to_i > 0
identifier_to = identifier_to.to_i
else
identifier_to = identifier_from.to_i - 1
end
cmd = "svn diff -r "
cmd << "#{identifier_to}:"
cmd << "#{identifier_from}"
cmd << "#{target(path)}@#{identifier_from}"
diff = []
shellout(cmd) do |io|
io.each_line do |line|
diff << line
end
end
return nil if $? && $?.exitstatus != 0
diff
rescue Errno::ENOENT => e
raise CommandFailed
end
def cat(path, identifier=nil)
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
cmd = "svn cat #{target(path)}@#{identifier}"
cat = nil
shellout(cmd) do |io|
cat = io.read
end
return nil if $? && $?.exitstatus != 0
cat
rescue Errno::ENOENT => e
raise CommandFailed
end
private
def target(path)
" \"" << "#{@url}/#{path}".gsub(/["'?<>\*]/, '') << "\""
end
def logger
RAILS_DEFAULT_LOGGER
end
def shellout(cmd, &block)
logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
IO.popen(cmd) do |io|
block.call(io) if block_given?
end
end
end
class Entries < Array
def sort_by_name
sort {|x,y|
if x.kind == y.kind
x.name <=> y.name
else
x.kind <=> y.kind
end
}
end
def revisions
revisions ||= Revisions.new(collect{|entry| entry.lastrev})
end
end
class Entry
attr_accessor :name, :path, :kind, :size, :lastrev
def initialize(attributes={})
self.name = attributes[:name] if attributes[:name]
self.path = attributes[:path] if attributes[:path]
self.kind = attributes[:kind] if attributes[:kind]
self.size = attributes[:size].to_i if attributes[:size]
self.lastrev = attributes[:lastrev]
end
def is_file?
'file' == self.kind
end
def is_dir?
'dir' == self.kind
end
end
class Revisions < Array
def latest
sort {|x,y| x.time <=> y.time}.last
end
end
class Revision
attr_accessor :identifier, :author, :time, :message, :paths
def initialize(attributes={})
self.identifier = attributes[:identifier]
self.author = attributes[:author]
self.time = attributes[:time]
self.message = attributes[:message] || ""
self.paths = attributes[:paths]
end
end
end
+50
View File
@@ -0,0 +1,50 @@
# 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 TimeEntry < ActiveRecord::Base
# could have used polymorphic association
# project association here allows easy loading of time entries at project level with one database trip
belongs_to :project
belongs_to :issue
belongs_to :user
belongs_to :activity, :class_name => 'Enumeration', :foreign_key => :activity_id
attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
validates_numericality_of :hours, :allow_nil => true
validates_length_of :comments, :maximum => 255
def before_validation
self.project = issue.project if issue && project.nil?
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)
end
# tyear, tmonth, tweek assigned where setting spent_on attributes
# these attributes make time aggregations easier
def spent_on=(date)
super
self.tyear = spent_on ? spent_on.year : nil
self.tmonth = spent_on ? spent_on.month : nil
self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
end
end
+1 -1
View File
@@ -31,7 +31,7 @@ class Token < ActiveRecord::Base
# Delete all expired tokens # Delete all expired tokens
def self.destroy_expired def self.destroy_expired
Token.delete_all ["created_on < ?", Time.now - @@validity_time] Token.delete_all ["action <> 'feeds' AND created_on < ?", Time.now - @@validity_time]
end end
private private
+2 -1
View File
@@ -19,7 +19,8 @@ class Tracker < ActiveRecord::Base
before_destroy :check_integrity before_destroy :check_integrity
has_many :issues has_many :issues
has_many :workflows, :dependent => :delete_all has_many :workflows, :dependent => :delete_all
has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => 'custom_fields_trackers', :association_foreign_key => 'custom_field_id' has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
acts_as_list
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
+55 -22
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,10 +18,17 @@
require "digest/sha1" require "digest/sha1"
class User < ActiveRecord::Base class User < ActiveRecord::Base
has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => :delete_all # Account statuses
STATUS_ACTIVE = 1
STATUS_REGISTERED = 2
STATUS_LOCKED = 3
has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name", :dependent => :delete_all
has_many :projects, :through => :memberships has_many :projects, :through => :memberships
has_many :custom_values, :dependent => :delete_all, :as => :customized has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
has_one :rss_key, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
belongs_to :auth_source belongs_to :auth_source
attr_accessor :password, :password_confirmation attr_accessor :password, :password_confirmation
@@ -32,24 +39,34 @@ class User < ActiveRecord::Base
validates_presence_of :login, :firstname, :lastname, :mail validates_presence_of :login, :firstname, :lastname, :mail
validates_uniqueness_of :login, :mail validates_uniqueness_of :login, :mail
# Login must contain lettres, numbers, underscores only # Login must contain lettres, numbers, underscores only
validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-]*$/i
validates_format_of :login, :with => /^[a-z0-9_\-@\.]+$/i validates_format_of :login, :with => /^[a-z0-9_\-@\.]+$/i
validates_length_of :login, :maximum => 30
validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-]*$/i
validates_length_of :firstname, :lastname, :maximum => 30
validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
validates_length_of :mail, :maximum => 60
# Password length between 4 and 12 # Password length between 4 and 12
validates_length_of :password, :in => 4..12, :allow_nil => true validates_length_of :password, :in => 4..12, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true validates_confirmation_of :password, :allow_nil => true
validates_associated :custom_values, :on => :update validates_associated :custom_values, :on => :update
# Account statuses
STATUS_ACTIVE = 1
STATUS_REGISTERED = 2
STATUS_LOCKED = 3
def before_save def before_save
# update hashed_password if password was set # update hashed_password if password was set
self.hashed_password = User.hash_password(self.password) if self.password self.hashed_password = User.hash_password(self.password) if self.password
end end
def self.active
with_scope :find => { :conditions => [ "status = ?", STATUS_ACTIVE ] } do
yield
end
end
def self.find_active(*args)
active do
find(*args)
end
end
# Returns the user that matches provided login and password, or nil # Returns the user that matches provided login and password, or nil
def self.try_to_login(login, password) def self.try_to_login(login, password)
user = find(:first, :conditions => ["login=?", login]) user = find(:first, :conditions => ["login=?", login])
@@ -69,7 +86,7 @@ class User < ActiveRecord::Base
if attrs if attrs
onthefly = new(*attrs) onthefly = new(*attrs)
onthefly.login = login onthefly.login = login
onthefly.language = $RDM_DEFAULT_LANG onthefly.language = Setting.default_language
if onthefly.save if onthefly.save
user = find(:first, :conditions => ["login=?", login]) user = find(:first, :conditions => ["login=?", login])
logger.info("User '#{user.login}' created on the fly.") if logger logger.info("User '#{user.login}' created on the fly.") if logger
@@ -84,12 +101,8 @@ class User < ActiveRecord::Base
end end
# Return user's full name for display # Return user's full name for display
def display_name
firstname + " " + lastname
end
def name def name
display_name "#{firstname} #{lastname}"
end end
def active? def active?
@@ -108,20 +121,40 @@ class User < ActiveRecord::Base
User.hash_password(clear_password) == self.hashed_password User.hash_password(clear_password) == self.hashed_password
end end
def role_for_project(project_id) def role_for_project(project)
@role_for_projects ||= return nil unless project
begin member = memberships.detect {|m| m.project_id == project.id}
roles = {} member ? member.role : nil
self.memberships.each { |m| roles.store m.project_id, m.role_id } end
roles
end def authorized_to(project, action)
@role_for_projects[project_id] return true if self.admin?
role = role_for_project(project)
role && Permission.allowed_to_role(action, role)
end end
def pref def pref
self.preference ||= UserPreference.new(:user => self) self.preference ||= UserPreference.new(:user => self)
end end
def get_or_create_rss_key
self.rss_key || Token.create(:user => self, :action => 'feeds')
end
def self.find_by_rss_key(key)
token = Token.find_by_value(key)
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
end
def <=>(user)
lastname == user.lastname ? firstname <=> user.firstname : lastname <=> user.lastname
end
private private
# Return password digest # Return password digest
def self.hash_password(clear_password) def self.hash_password(clear_password)
+8 -3
View File
@@ -17,7 +17,7 @@
class UserPreference < ActiveRecord::Base class UserPreference < ActiveRecord::Base
belongs_to :user belongs_to :user
serialize :others, Hash serialize :others
attr_protected :others attr_protected :others
@@ -26,11 +26,15 @@ class UserPreference < ActiveRecord::Base
self.others ||= {} self.others ||= {}
end end
def before_save
self.others ||= {}
end
def [](attr_name) def [](attr_name)
if attribute_present? attr_name if attribute_present? attr_name
super super
else else
others[attr_name] others ? others[attr_name] : nil
end end
end end
@@ -38,7 +42,8 @@ class UserPreference < ActiveRecord::Base
if attribute_present? attr_name if attribute_present? attr_name
super super
else else
others.store attr_name, value self.others ||= {}
self.others.store attr_name, value
end end
end end
end end
+30 -1
View File
@@ -23,7 +23,36 @@ class Version < ActiveRecord::Base
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id] validates_uniqueness_of :name, :scope => [:project_id]
validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :activerecord_error_not_a_date validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :activerecord_error_not_a_date, :allow_nil => true
def start_date
effective_date
end
def due_date
effective_date
end
def completed?
effective_date && effective_date <= Date.today
end
def wiki_page
if project.wiki && !wiki_page_title.blank?
@wiki_page ||= project.wiki.find_page(wiki_page_title)
end
@wiki_page
end
# Versions are sorted by effective_date
# Those with no effective_date are at the end, sorted by name
def <=>(version)
if self.effective_date
version.effective_date ? (self.effective_date <=> version.effective_date) : -1
else
version.effective_date ? 1 : (self.name <=> version.name)
end
end
private private
def check_integrity def check_integrity
+23
View File
@@ -0,0 +1,23 @@
# 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 Watcher < ActiveRecord::Base
belongs_to :watchable, :polymorphic => true
belongs_to :user
validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id]
end
+46
View File
@@ -0,0 +1,46 @@
# 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 Wiki < ActiveRecord::Base
belongs_to :project
has_many :pages, :class_name => 'WikiPage', :dependent => :destroy
validates_presence_of :start_page
validates_format_of :start_page, :with => /^[^,\.\/\?\;\|]*$/
# find the page with the given title
# if page doesn't exist, return a new page
def find_or_new_page(title)
title = Wiki.titleize(title || start_page)
find_page(title) || WikiPage.new(:wiki => self, :title => title)
end
# find the page with the given title
def find_page(title)
title = start_page if title.blank?
pages.find_by_title(Wiki.titleize(title))
end
# turn a string into a valid page title
def self.titleize(title)
# replace spaces with _ and remove unwanted caracters
title = title.gsub(/\s+/, '_').delete(',./?;|') if title
# upcase the first letter
title = title[0..0].upcase + title[1..-1] if title
title
end
end
+60
View File
@@ -0,0 +1,60 @@
# 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.
require 'zlib'
class WikiContent < ActiveRecord::Base
set_locking_column :version
belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id'
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
validates_presence_of :text
acts_as_versioned
class Version
belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id'
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
attr_protected :data
def text=(plain)
case Setting.wiki_compression
when 'gzip'
begin
self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION)
self.compression = 'gzip'
rescue
self.data = plain
self.compression = ''
end
else
self.data = plain
self.compression = ''
end
plain
end
def text
@text ||= case compression
when 'gzip'
Zlib::Inflate.inflate(data)
else
# uncompressed data
data
end
end
end
end
+76
View File
@@ -0,0 +1,76 @@
# 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.
require 'diff'
class WikiPage < ActiveRecord::Base
belongs_to :wiki
has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
has_many :attachments, :as => :container, :dependent => :destroy
validates_presence_of :title
validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
validates_associated :content
def before_save
self.title = Wiki.titleize(title)
end
def pretty_title
WikiPage.pretty_title(title)
end
def content_for_version(version=nil)
result = content.versions.find_by_version(version.to_i) if version
result ||= content
result
end
def diff(version_to=nil, version_from=nil)
version_to = version_to ? version_to.to_i : self.content.version
version_from = version_from ? version_from.to_i : version_to - 1
version_to, version_from = version_from, version_to unless version_from < version_to
content_to = content.versions.find_by_version(version_to)
content_from = content.versions.find_by_version(version_from)
(content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
end
def self.pretty_title(str)
(str && str.is_a?(String)) ? str.tr('_', ' ') : str
end
def project
wiki.project
end
end
class WikiDiff
attr_reader :diff, :words, :content_to, :content_from
def initialize(content_to, content_from)
@content_to = content_to
@content_from = content_from
@words = content_to.text.split(/(\s+)/)
@words = @words.select {|word| word != ' '}
words_from = content_from.text.split(/(\s+)/)
words_from = words_from.select {|word| word != ' '}
@diff = words_from.diff @words
end
end
+38
View File
@@ -0,0 +1,38 @@
# 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 IssueSweeper < ActionController::Caching::Sweeper
observe Issue
def after_save(issue)
expire_cache_for(issue)
end
def after_destroy(issue)
expire_cache_for(issue)
end
private
def expire_cache_for(issue)
# fragments of the main project
expire_fragment(Regexp.new("projects/(calendar|gantt)/#{issue.project_id}\\."))
# fragments of the root project that include subprojects issues
unless issue.project.parent_id.nil?
expire_fragment(Regexp.new("projects/(calendar|gantt)/#{issue.project.parent_id}\\..*subprojects"))
end
end
end
+40
View File
@@ -0,0 +1,40 @@
# 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 ProjectSweeper < ActionController::Caching::Sweeper
observe Project
def before_save(project)
if project.new_record?
expire_cache_for(project.parent) if project.parent
else
project_before_update = Project.find(project.id)
return if project_before_update.parent_id == project.parent_id && project_before_update.status == project.status
expire_cache_for(project.parent) if project.parent
expire_cache_for(project_before_update.parent) if project_before_update.parent
end
end
def after_destroy(project)
expire_cache_for(project.parent) if project.parent
end
private
def expire_cache_for(project)
expire_fragment(Regexp.new("projects/(calendar|gantt)/#{project.id}\\."))
end
end
+16 -6
View File
@@ -1,18 +1,28 @@
<center> <center>
<div class="box login"> <div class="box login">
<h2><%= image_tag 'login' %>&nbsp;&nbsp;<%=l(:label_please_login)%></h2> <h2 class="icon22 icon22-authent"><%=l(:label_please_login)%></h2>
<% form_tag({:action=> "login"}, :class => "tabular") do %>
<%= start_form_tag({:action=> "login"}, :class => "tabular") %>
<p><label for="login"><%=l(:field_login)%>:</label> <p><label for="login"><%=l(:field_login)%>:</label>
<%= text_field_tag 'login', nil, :size => 25 %></p> <%= text_field_tag 'login', nil, :size => 25 %></p>
<p><label for="password"><%=l(:field_password)%>:</label> <p><label for="password"><%=l(:field_password)%>:</label>
<%= password_field_tag 'password', nil, :size => 25 %></p> <%= password_field_tag 'password', nil, :size => 25 %></p>
<p><center><input type="submit" name="login" value="<%=l(:button_login)%> &#187;" class="primary" /></center> <% if Setting.autologin? %>
<%= end_form_tag %> <p><label for="autologin"><%= check_box_tag 'autologin' %> <%= l(:label_stay_logged_in) %></label></p>
<% end %>
<p><input type="submit" name="login" value="<%=l(:button_login)%> &#187;" class="primary" /></p>
<% end %>
<%= javascript_tag "Form.Element.focus('login');" %>
<% links = []
links << link_to(l(:label_register), :action => 'register') if Setting.self_registration?
links << link_to(l(:label_password_lost), :action => 'lost_password') if Setting.lost_password?
%>
<%= links.join(" | ") %>
<br><% unless $RDM_SELF_REGISTRATION == false %><%= link_to l(:label_register), :action => 'register' %> |<% end %>
<%= link_to l(:label_password_lost), :action => 'lost_password' %></p>
</div> </div>
</center> </center>
+2 -2
View File
@@ -2,13 +2,13 @@
<div class="box login"> <div class="box login">
<h2><%=l(:label_password_lost)%></h2> <h2><%=l(:label_password_lost)%></h2>
<%= start_form_tag({:action=> "lost_password"}, :class => "tabular") %> <% form_tag({:action=> "lost_password"}, :class => "tabular") do %>
<p><label for="mail"><%=l(:field_mail)%> <span class="required">*</span></label> <p><label for="mail"><%=l(:field_mail)%> <span class="required">*</span></label>
<%= text_field_tag 'mail', nil, :size => 40 %></p> <%= text_field_tag 'mail', nil, :size => 40 %></p>
<p><center><%= submit_tag l(:button_submit) %></center></p> <p><center><%= submit_tag l(:button_submit) %></center></p>
<%= end_form_tag %> <% end %>
</div> </div>
</center> </center>
+4 -3
View File
@@ -6,16 +6,17 @@
<%= error_messages_for 'user' %> <%= error_messages_for 'user' %>
<%= start_form_tag({:token => @token.value}, :class => "tabular") %> <% form_tag({:token => @token.value}, :class => "tabular") do %>
<p><label for="new_password"><%=l(:field_new_password)%> <span class="required">*</span></label> <p><label for="new_password"><%=l(:field_new_password)%> <span class="required">*</span></label>
<%= password_field_tag 'new_password', nil, :size => 25 %></p> <%= password_field_tag 'new_password', nil, :size => 25 %><br />
<em><%= l(:text_length_between, 4, 12) %></em></p>
<p><label for="new_password_confirmation"><%=l(:field_password_confirmation)%> <span class="required">*</span></label> <p><label for="new_password_confirmation"><%=l(:field_password_confirmation)%> <span class="required">*</span></label>
<%= password_field_tag 'new_password_confirmation', nil, :size => 25 %></p> <%= password_field_tag 'new_password_confirmation', nil, :size => 25 %></p>
<p><center><%= submit_tag l(:button_save) %></center></p> <p><center><%= submit_tag l(:button_save) %></center></p>
<%= end_form_tag %> <% end %>
</div> </div>
</center> </center>
+9 -2
View File
@@ -1,6 +1,6 @@
<h2><%=l(:label_register)%></h2> <h2><%=l(:label_register)%></h2>
<%= start_form_tag({:action => 'register'}, :class => "tabular") %> <% form_tag({:action => 'register'}, :class => "tabular") do %>
<%= error_messages_for 'user' %> <%= error_messages_for 'user' %>
<div class="box"> <div class="box">
@@ -36,4 +36,11 @@
</div> </div>
<%= submit_tag l(:button_submit) %> <%= submit_tag l(:button_submit) %>
<%= end_form_tag %> <% end %>
<% content_for :header_tags do %>
<%= javascript_include_tag 'calendar/calendar' %>
<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %>
<%= javascript_include_tag 'calendar/calendar-setup' %>
<%= stylesheet_link_tag 'calendar' %>
<% end %>
+8 -6
View File
@@ -1,4 +1,4 @@
<h2><%= @user.display_name %></h2> <h2><%=h @user.name %></h2>
<p> <p>
<%= mail_to @user.mail unless @user.pref.hide_mail %> <%= mail_to @user.mail unless @user.pref.hide_mail %>
@@ -12,13 +12,15 @@
</ul> </ul>
</p> </p>
<% unless @memberships.empty? %>
<h3><%=l(:label_project_plural)%></h3> <h3><%=l(:label_project_plural)%></h3>
<p> <ul>
<% for membership in @user.memberships %> <% for membership in @memberships %>
<%= membership.project.name %> (<%= membership.role.name %>, <%= format_date(membership.created_on) %>) <li><%= link_to membership.project.name, :controller => 'projects', :action => 'show', :id => membership.project %>
<br /> (<%= membership.role.name %>, <%= format_date(membership.created_on) %>)</li>
<% end %>
</ul>
<% end %> <% end %>
</p>
<h3><%=l(:label_activity)%></h3> <h3><%=l(:label_activity)%></h3>
<p> <p>

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