From 4b71a336d2a7b579d8088aab6d6df4f2e9dffc14 Mon Sep 17 00:00:00 2001
From: Jean-Philippe Lang
Date: Mon, 17 Dec 2007 18:24:22 +0000
Subject: [PATCH] Added branch for sys controller refactoring.
git-svn-id: http://redmine.rubyforge.org/svn/branches/work@1006 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
rest_sys/Rakefile | 10 +
rest_sys/app/apis/sys_api.rb | 25 +
.../app/controllers/account_controller.rb | 175 ++
rest_sys/app/controllers/admin_controller.rb | 82 +
rest_sys/app/controllers/application.rb | 179 ++
.../app/controllers/attachments_controller.rb | 39 +
.../controllers/auth_sources_controller.rb | 83 +
rest_sys/app/controllers/boards_controller.rb | 86 +
.../controllers/custom_fields_controller.rb | 87 +
.../app/controllers/documents_controller.rb | 65 +
.../controllers/enumerations_controller.rb | 85 +
.../issue_categories_controller.rb | 52 +
.../controllers/issue_relations_controller.rb | 59 +
.../controllers/issue_statuses_controller.rb | 85 +
rest_sys/app/controllers/issues_controller.rb | 239 ++
.../app/controllers/members_controller.rb | 62 +
.../app/controllers/messages_controller.rb | 98 +
rest_sys/app/controllers/my_controller.rb | 160 ++
rest_sys/app/controllers/news_controller.rb | 82 +
.../app/controllers/projects_controller.rb | 548 ++++
.../app/controllers/queries_controller.rb | 81 +
.../app/controllers/reports_controller.rb | 213 ++
.../controllers/repositories_controller.rb | 280 ++
rest_sys/app/controllers/roles_controller.rb | 112 +
rest_sys/app/controllers/search_controller.rb | 112 +
.../app/controllers/settings_controller.rb | 45 +
rest_sys/app/controllers/sys_controller.rb | 47 +
.../app/controllers/timelog_controller.rb | 172 ++
.../app/controllers/trackers_controller.rb | 84 +
rest_sys/app/controllers/users_controller.rb | 112 +
.../app/controllers/versions_controller.rb | 70 +
.../app/controllers/watchers_controller.rb | 49 +
.../app/controllers/welcome_controller.rb | 25 +
rest_sys/app/controllers/wiki_controller.rb | 176 ++
rest_sys/app/controllers/wikis_controller.rb | 44 +
rest_sys/app/helpers/account_helper.rb | 19 +
rest_sys/app/helpers/admin_helper.rb | 23 +
rest_sys/app/helpers/application_helper.rb | 407 +++
rest_sys/app/helpers/attachments_helper.rb | 25 +
rest_sys/app/helpers/auth_sources_helper.rb | 19 +
rest_sys/app/helpers/boards_helper.rb | 19 +
rest_sys/app/helpers/custom_fields_helper.rb | 77 +
rest_sys/app/helpers/documents_helper.rb | 19 +
rest_sys/app/helpers/enumerations_helper.rb | 19 +
rest_sys/app/helpers/ifpdf_helper.rb | 80 +
.../app/helpers/issue_categories_helper.rb | 19 +
.../app/helpers/issue_relations_helper.rb | 23 +
rest_sys/app/helpers/issue_statuses_helper.rb | 19 +
rest_sys/app/helpers/issues_helper.rb | 159 ++
rest_sys/app/helpers/members_helper.rb | 19 +
rest_sys/app/helpers/messages_helper.rb | 28 +
rest_sys/app/helpers/my_helper.rb | 19 +
rest_sys/app/helpers/news_helper.rb | 19 +
rest_sys/app/helpers/projects_helper.rb | 199 ++
rest_sys/app/helpers/queries_helper.rb | 51 +
rest_sys/app/helpers/reports_helper.rb | 36 +
rest_sys/app/helpers/repositories_helper.rb | 87 +
rest_sys/app/helpers/roles_helper.rb | 19 +
rest_sys/app/helpers/search_helper.rb | 38 +
rest_sys/app/helpers/settings_helper.rb | 19 +
rest_sys/app/helpers/sort_helper.rb | 157 +
rest_sys/app/helpers/timelog_helper.rb | 30 +
rest_sys/app/helpers/trackers_helper.rb | 19 +
rest_sys/app/helpers/users_helper.rb | 25 +
rest_sys/app/helpers/versions_helper.rb | 47 +
rest_sys/app/helpers/watchers_helper.rb | 36 +
rest_sys/app/helpers/welcome_helper.rb | 19 +
rest_sys/app/helpers/wiki_helper.rb | 56 +
rest_sys/app/models/attachment.rb | 105 +
rest_sys/app/models/auth_source.rb | 51 +
rest_sys/app/models/auth_source_ldap.rb | 82 +
rest_sys/app/models/board.rb | 29 +
rest_sys/app/models/change.rb | 22 +
rest_sys/app/models/changeset.rb | 104 +
rest_sys/app/models/comment.rb | 23 +
rest_sys/app/models/custom_field.rb | 70 +
rest_sys/app/models/custom_value.rb | 39 +
rest_sys/app/models/document.rb | 30 +
rest_sys/app/models/enabled_module.rb | 23 +
rest_sys/app/models/enumeration.rb | 67 +
rest_sys/app/models/issue.rb | 221 ++
rest_sys/app/models/issue_category.rb | 43 +
rest_sys/app/models/issue_custom_field.rb | 27 +
rest_sys/app/models/issue_relation.rb | 79 +
rest_sys/app/models/issue_status.rb | 65 +
rest_sys/app/models/journal.rb | 47 +
rest_sys/app/models/journal_detail.rb | 25 +
rest_sys/app/models/mail_handler.rb | 40 +
rest_sys/app/models/mailer.rb | 160 ++
rest_sys/app/models/member.rb | 38 +
rest_sys/app/models/message.rb | 67 +
rest_sys/app/models/message_observer.rb | 27 +
rest_sys/app/models/news.rb | 34 +
rest_sys/app/models/project.rb | 202 ++
rest_sys/app/models/project_custom_field.rb | 22 +
rest_sys/app/models/query.rb | 342 +++
rest_sys/app/models/repository.rb | 91 +
rest_sys/app/models/repository/bazaar.rb | 86 +
rest_sys/app/models/repository/cvs.rb | 150 +
rest_sys/app/models/repository/darcs.rb | 90 +
rest_sys/app/models/repository/mercurial.rb | 81 +
rest_sys/app/models/repository/subversion.rb | 74 +
rest_sys/app/models/role.rb | 108 +
rest_sys/app/models/setting.rb | 119 +
rest_sys/app/models/time_entry.rb | 50 +
rest_sys/app/models/token.rb | 44 +
rest_sys/app/models/tracker.rb | 44 +
rest_sys/app/models/user.rb | 255 ++
rest_sys/app/models/user_custom_field.rb | 23 +
rest_sys/app/models/user_preference.rb | 49 +
rest_sys/app/models/version.rb | 96 +
rest_sys/app/models/watcher.rb | 23 +
rest_sys/app/models/wiki.rb | 54 +
rest_sys/app/models/wiki_content.rb | 65 +
rest_sys/app/models/wiki_page.rb | 115 +
rest_sys/app/models/wiki_redirect.rb | 23 +
rest_sys/app/models/workflow.rb | 24 +
rest_sys/app/sweepers/issue_sweeper.rb | 38 +
rest_sys/app/sweepers/project_sweeper.rb | 40 +
rest_sys/app/sweepers/version_sweeper.rb | 34 +
rest_sys/app/views/account/login.rhtml | 33 +
.../app/views/account/lost_password.rhtml | 11 +
.../app/views/account/password_recovery.rhtml | 15 +
rest_sys/app/views/account/register.rhtml | 44 +
rest_sys/app/views/account/show.rhtml | 28 +
rest_sys/app/views/admin/_menu.rhtml | 25 +
rest_sys/app/views/admin/index.rhtml | 47 +
rest_sys/app/views/admin/info.rhtml | 27 +
rest_sys/app/views/admin/mail_options.rhtml | 33 +
rest_sys/app/views/admin/projects.rhtml | 51 +
rest_sys/app/views/attachments/_form.rhtml | 4 +
rest_sys/app/views/attachments/_links.rhtml | 13 +
rest_sys/app/views/auth_sources/_form.rhtml | 48 +
rest_sys/app/views/auth_sources/edit.rhtml | 7 +
rest_sys/app/views/auth_sources/list.rhtml | 28 +
rest_sys/app/views/auth_sources/new.rhtml | 6 +
rest_sys/app/views/boards/_form.rhtml | 8 +
rest_sys/app/views/boards/edit.rhtml | 6 +
rest_sys/app/views/boards/index.rhtml | 30 +
rest_sys/app/views/boards/new.rhtml | 6 +
rest_sys/app/views/boards/show.rhtml | 50 +
rest_sys/app/views/common/403.rhtml | 6 +
rest_sys/app/views/common/404.rhtml | 6 +
.../app/views/common/_attachments_form.rhtml | 6 +
rest_sys/app/views/common/_calendar.rhtml | 36 +
rest_sys/app/views/common/_preview.rhtml | 3 +
rest_sys/app/views/common/feed.atom.rxml | 26 +
rest_sys/app/views/custom_fields/_form.rhtml | 98 +
rest_sys/app/views/custom_fields/edit.rhtml | 6 +
rest_sys/app/views/custom_fields/list.rhtml | 55 +
rest_sys/app/views/custom_fields/new.rhtml | 7 +
rest_sys/app/views/documents/_document.rhtml | 3 +
rest_sys/app/views/documents/_form.rhtml | 15 +
rest_sys/app/views/documents/edit.rhtml | 8 +
rest_sys/app/views/documents/show.rhtml | 38 +
rest_sys/app/views/enumerations/_form.rhtml | 12 +
rest_sys/app/views/enumerations/edit.rhtml | 10 +
rest_sys/app/views/enumerations/list.rhtml | 28 +
rest_sys/app/views/enumerations/new.rhtml | 6 +
.../app/views/issue_categories/_form.rhtml | 6 +
.../app/views/issue_categories/destroy.rhtml | 15 +
.../app/views/issue_categories/edit.rhtml | 6 +
.../app/views/issue_relations/_form.rhtml | 12 +
rest_sys/app/views/issue_statuses/_form.rhtml | 15 +
rest_sys/app/views/issue_statuses/edit.rhtml | 6 +
rest_sys/app/views/issue_statuses/list.rhtml | 37 +
rest_sys/app/views/issue_statuses/new.rhtml | 6 +
.../app/views/issues/_bulk_edit_form.rhtml | 38 +
rest_sys/app/views/issues/_form.rhtml | 52 +
rest_sys/app/views/issues/_history.rhtml | 13 +
rest_sys/app/views/issues/_list.rhtml | 25 +
rest_sys/app/views/issues/_list_simple.rhtml | 25 +
rest_sys/app/views/issues/_pdf.rfpdf | 102 +
rest_sys/app/views/issues/_relations.rhtml | 31 +
rest_sys/app/views/issues/_sidebar.rhtml | 18 +
rest_sys/app/views/issues/change_status.rhtml | 38 +
rest_sys/app/views/issues/changes.rxml | 30 +
rest_sys/app/views/issues/context_menu.rhtml | 40 +
rest_sys/app/views/issues/edit.rhtml | 19 +
rest_sys/app/views/issues/index.rfpdf | 50 +
rest_sys/app/views/issues/index.rhtml | 73 +
rest_sys/app/views/issues/show.rfpdf | 10 +
rest_sys/app/views/issues/show.rhtml | 123 +
.../app/views/layouts/_project_selector.rhtml | 12 +
rest_sys/app/views/layouts/base.rhtml | 79 +
.../app/views/mailer/_issue_text_html.rhtml | 15 +
.../app/views/mailer/_issue_text_plain.rhtml | 13 +
...account_activation_request.text.html.rhtml | 2 +
...ccount_activation_request.text.plain.rhtml | 2 +
.../account_information.text.html.rhtml | 11 +
.../account_information.text.plain.rhtml | 6 +
.../mailer/attachments_added.text.html.rhtml | 5 +
.../mailer/attachments_added.text.plain.rhtml | 4 +
.../mailer/document_added.text.html.rhtml | 3 +
.../mailer/document_added.text.plain.rhtml | 4 +
.../views/mailer/issue_add.text.html.rhtml | 3 +
.../views/mailer/issue_add.text.plain.rhtml | 3 +
.../views/mailer/issue_edit.text.html.rhtml | 10 +
.../views/mailer/issue_edit.text.plain.rhtml | 8 +
.../app/views/mailer/layout.text.html.rhtml | 17 +
.../app/views/mailer/layout.text.plain.rhtml | 3 +
.../mailer/lost_password.text.html.rhtml | 2 +
.../mailer/lost_password.text.plain.rhtml | 2 +
.../mailer/message_posted.text.html.rhtml | 4 +
.../mailer/message_posted.text.plain.rhtml | 4 +
.../views/mailer/news_added.text.html.rhtml | 4 +
.../views/mailer/news_added.text.plain.rhtml | 5 +
.../app/views/mailer/register.text.html.rhtml | 2 +
.../views/mailer/register.text.plain.rhtml | 2 +
.../app/views/mailer/test.text.html.rhtml | 2 +
.../app/views/mailer/test.text.plain.rhtml | 2 +
rest_sys/app/views/messages/_form.rhtml | 21 +
rest_sys/app/views/messages/edit.rhtml | 6 +
rest_sys/app/views/messages/new.rhtml | 6 +
rest_sys/app/views/messages/show.rhtml | 39 +
rest_sys/app/views/my/_block.rhtml | 14 +
rest_sys/app/views/my/_sidebar.rhtml | 8 +
rest_sys/app/views/my/account.rhtml | 48 +
rest_sys/app/views/my/blocks/_calendar.rhtml | 8 +
rest_sys/app/views/my/blocks/_documents.rhtml | 8 +
.../views/my/blocks/_issuesassignedtome.rhtml | 17 +
.../views/my/blocks/_issuesreportedbyme.rhtml | 17 +
.../app/views/my/blocks/_issueswatched.rhtml | 10 +
rest_sys/app/views/my/blocks/_news.rhtml | 8 +
rest_sys/app/views/my/page.rhtml | 42 +
rest_sys/app/views/my/page_layout.rhtml | 107 +
rest_sys/app/views/my/password.rhtml | 22 +
rest_sys/app/views/news/_form.rhtml | 8 +
rest_sys/app/views/news/_news.rhtml | 6 +
rest_sys/app/views/news/edit.rhtml | 6 +
rest_sys/app/views/news/index.rhtml | 36 +
rest_sys/app/views/news/show.rhtml | 46 +
rest_sys/app/views/projects/_edit.rhtml | 4 +
rest_sys/app/views/projects/_form.rhtml | 52 +
rest_sys/app/views/projects/activity.rhtml | 43 +
rest_sys/app/views/projects/add.rhtml | 15 +
.../app/views/projects/add_document.rhtml | 13 +
rest_sys/app/views/projects/add_file.rhtml | 13 +
rest_sys/app/views/projects/add_issue.rhtml | 19 +
.../views/projects/add_issue_category.rhtml | 6 +
rest_sys/app/views/projects/add_news.rhtml | 6 +
rest_sys/app/views/projects/add_version.rhtml | 6 +
rest_sys/app/views/projects/calendar.rhtml | 42 +
rest_sys/app/views/projects/changelog.rhtml | 42 +
rest_sys/app/views/projects/destroy.rhtml | 14 +
rest_sys/app/views/projects/gantt.rfpdf | 188 ++
rest_sys/app/views/projects/gantt.rhtml | 249 ++
rest_sys/app/views/projects/list.rhtml | 20 +
.../app/views/projects/list_documents.rhtml | 39 +
rest_sys/app/views/projects/list_files.rhtml | 46 +
.../app/views/projects/list_members.rhtml | 13 +
rest_sys/app/views/projects/move_issues.rhtml | 29 +
rest_sys/app/views/projects/roadmap.rhtml | 50 +
rest_sys/app/views/projects/settings.rhtml | 18 +
.../app/views/projects/settings/_boards.rhtml | 28 +
.../projects/settings/_issue_categories.rhtml | 26 +
.../views/projects/settings/_members.rhtml | 48 +
.../views/projects/settings/_modules.rhtml | 16 +
.../views/projects/settings/_repository.rhtml | 20 +
.../views/projects/settings/_versions.rhtml | 29 +
.../app/views/projects/settings/_wiki.rhtml | 18 +
rest_sys/app/views/projects/show.rhtml | 85 +
rest_sys/app/views/queries/_columns.rhtml | 27 +
rest_sys/app/views/queries/_filters.rhtml | 103 +
rest_sys/app/views/queries/_form.rhtml | 21 +
rest_sys/app/views/queries/edit.rhtml | 6 +
rest_sys/app/views/queries/index.rhtml | 27 +
rest_sys/app/views/queries/new.rhtml | 6 +
rest_sys/app/views/reports/_details.rhtml | 48 +
rest_sys/app/views/reports/_simple.rhtml | 37 +
rest_sys/app/views/reports/issue_report.rhtml | 28 +
.../views/reports/issue_report_details.rhtml | 7 +
.../app/views/repositories/_dir_list.rhtml | 15 +
.../repositories/_dir_list_content.rhtml | 32 +
.../app/views/repositories/_navigation.rhtml | 19 +
.../app/views/repositories/_revisions.rhtml | 28 +
.../app/views/repositories/annotate.rhtml | 26 +
rest_sys/app/views/repositories/browse.rhtml | 13 +
rest_sys/app/views/repositories/changes.rhtml | 20 +
rest_sys/app/views/repositories/diff.rhtml | 93 +
rest_sys/app/views/repositories/entry.rhtml | 20 +
.../app/views/repositories/revision.rhtml | 66 +
.../app/views/repositories/revisions.rhtml | 20 +
rest_sys/app/views/repositories/show.rhtml | 25 +
rest_sys/app/views/repositories/stats.rhtml | 11 +
rest_sys/app/views/roles/_form.rhtml | 25 +
rest_sys/app/views/roles/edit.rhtml | 6 +
rest_sys/app/views/roles/list.rhtml | 36 +
rest_sys/app/views/roles/new.rhtml | 6 +
rest_sys/app/views/roles/report.rhtml | 37 +
rest_sys/app/views/roles/workflow.rhtml | 58 +
rest_sys/app/views/search/index.rhtml | 45 +
rest_sys/app/views/settings/edit.rhtml | 102 +
rest_sys/app/views/settings/plugin.rhtml | 10 +
.../app/views/timelog/_report_criteria.rhtml | 17 +
rest_sys/app/views/timelog/details.rhtml | 51 +
rest_sys/app/views/timelog/edit.rhtml | 23 +
rest_sys/app/views/timelog/report.rhtml | 52 +
rest_sys/app/views/trackers/_form.rhtml | 11 +
rest_sys/app/views/trackers/edit.rhtml | 6 +
rest_sys/app/views/trackers/list.rhtml | 35 +
rest_sys/app/views/trackers/new.rhtml | 6 +
rest_sys/app/views/users/_form.rhtml | 39 +
rest_sys/app/views/users/_memberships.rhtml | 29 +
rest_sys/app/views/users/add.rhtml | 7 +
rest_sys/app/views/users/edit.rhtml | 8 +
rest_sys/app/views/users/list.rhtml | 62 +
rest_sys/app/views/versions/_form.rhtml | 15 +
.../app/views/versions/_issue_counts.rhtml | 35 +
rest_sys/app/views/versions/_overview.rhtml | 24 +
rest_sys/app/views/versions/edit.rhtml | 7 +
rest_sys/app/views/versions/show.rhtml | 14 +
rest_sys/app/views/welcome/index.rhtml | 30 +
rest_sys/app/views/wiki/_content.rhtml | 3 +
rest_sys/app/views/wiki/_sidebar.rhtml | 5 +
rest_sys/app/views/wiki/diff.rhtml | 18 +
rest_sys/app/views/wiki/edit.rhtml | 31 +
rest_sys/app/views/wiki/export.rhtml | 14 +
rest_sys/app/views/wiki/export_multiple.rhtml | 27 +
rest_sys/app/views/wiki/history.rhtml | 34 +
rest_sys/app/views/wiki/rename.rhtml | 11 +
rest_sys/app/views/wiki/show.rhtml | 49 +
.../app/views/wiki/special_date_index.rhtml | 25 +
.../app/views/wiki/special_page_index.rhtml | 22 +
rest_sys/app/views/wikis/destroy.rhtml | 10 +
rest_sys/config/boot.rb | 19 +
rest_sys/config/database.yml.example | 51 +
rest_sys/config/environment.rb | 92 +
rest_sys/config/environments/demo.rb | 21 +
rest_sys/config/environments/development.rb | 16 +
rest_sys/config/environments/production.rb | 22 +
rest_sys/config/environments/test.rb | 16 +
rest_sys/config/environments/test_oracle.rb | 16 +
rest_sys/config/environments/test_pgsql.rb | 16 +
.../config/environments/test_sqlserver.rb | 16 +
rest_sys/config/routes.rb | 37 +
rest_sys/config/settings.yml | 106 +
rest_sys/db/migrate/001_setup.rb | 321 +++
rest_sys/db/migrate/002_issue_move.rb | 12 +
rest_sys/db/migrate/003_issue_add_note.rb | 12 +
rest_sys/db/migrate/004_export_pdf.rb | 14 +
rest_sys/db/migrate/005_issue_start_date.rb | 11 +
.../db/migrate/006_calendar_and_activity.rb | 16 +
rest_sys/db/migrate/007_create_journals.rb | 56 +
.../db/migrate/008_create_user_preferences.rb | 12 +
rest_sys/db/migrate/009_add_hide_mail_pref.rb | 9 +
rest_sys/db/migrate/010_create_comments.rb | 16 +
.../db/migrate/011_add_news_comments_count.rb | 9 +
.../migrate/012_add_comments_permissions.rb | 14 +
rest_sys/db/migrate/013_create_queries.rb | 15 +
.../db/migrate/014_add_queries_permissions.rb | 12 +
.../db/migrate/015_create_repositories.rb | 12 +
.../016_add_repositories_permissions.rb | 22 +
rest_sys/db/migrate/017_create_settings.rb | 12 +
.../018_set_doc_and_files_notifications.rb | 18 +
.../migrate/019_add_issue_status_position.rb | 10 +
rest_sys/db/migrate/020_add_role_position.rb | 10 +
.../db/migrate/021_add_tracker_position.rb | 10 +
.../migrate/022_serialize_possibles_values.rb | 13 +
.../migrate/023_add_tracker_is_in_roadmap.rb | 9 +
.../db/migrate/024_add_roadmap_permission.rb | 12 +
.../db/migrate/025_add_search_permission.rb | 12 +
.../026_add_repository_login_and_password.rb | 11 +
rest_sys/db/migrate/027_create_wikis.rb | 14 +
rest_sys/db/migrate/028_create_wiki_pages.rb | 14 +
.../db/migrate/029_create_wiki_contents.rb | 30 +
.../030_add_projects_feeds_permissions.rb | 12 +
.../db/migrate/031_add_repository_root_url.rb | 9 +
.../db/migrate/032_create_time_entries.rb | 24 +
.../db/migrate/033_add_timelog_permissions.rb | 12 +
rest_sys/db/migrate/034_create_changesets.rb | 16 +
rest_sys/db/migrate/035_create_changes.rb | 16 +
.../migrate/036_add_changeset_commit_date.rb | 10 +
.../db/migrate/037_add_project_identifier.rb | 9 +
.../migrate/038_add_custom_field_is_filter.rb | 9 +
rest_sys/db/migrate/039_create_watchers.rb | 13 +
.../migrate/040_create_changesets_issues.rb | 13 +
.../migrate/041_rename_comment_to_comments.rb | 13 +
.../db/migrate/042_create_issue_relations.rb | 14 +
.../migrate/043_add_relations_permissions.rb | 14 +
.../044_set_language_length_to_five.rb | 9 +
rest_sys/db/migrate/045_create_boards.rb | 18 +
rest_sys/db/migrate/046_create_messages.rb | 21 +
.../db/migrate/047_add_boards_permissions.rb | 16 +
.../048_allow_null_version_effective_date.rb | 9 +
.../049_add_wiki_destroy_page_permission.rb | 12 +
.../050_add_wiki_attachments_permissions.rb | 14 +
rest_sys/db/migrate/051_add_project_status.rb | 9 +
.../db/migrate/052_add_changes_revision.rb | 9 +
rest_sys/db/migrate/053_add_changes_branch.rb | 9 +
.../db/migrate/054_add_changesets_scmid.rb | 9 +
.../db/migrate/055_add_repositories_type.rb | 11 +
...056_add_repositories_changes_permission.rb | 12 +
.../057_add_versions_wiki_page_title.rb | 9 +
...058_add_issue_categories_assigned_to_id.rb | 9 +
.../db/migrate/059_add_roles_assignable.rb | 9 +
.../060_change_changesets_committer_limit.rb | 9 +
rest_sys/db/migrate/061_add_roles_builtin.rb | 9 +
.../db/migrate/062_insert_builtin_roles.rb | 15 +
.../db/migrate/063_add_roles_permissions.rb | 9 +
rest_sys/db/migrate/064_drop_permissions.rb | 10 +
.../db/migrate/065_add_settings_updated_on.rb | 11 +
.../066_add_custom_value_customized_index.rb | 9 +
.../db/migrate/067_create_wiki_redirects.rb | 15 +
.../db/migrate/068_create_enabled_modules.rb | 18 +
.../migrate/069_add_issues_estimated_hours.rb | 9 +
...0_change_attachments_content_type_limit.rb | 9 +
.../migrate/071_add_queries_column_names.rb | 9 +
.../migrate/072_add_enumerations_position.rb | 15 +
.../073_add_enumerations_is_default.rb | 9 +
.../db/migrate/074_add_auth_sources_tls.rb | 9 +
.../075_add_members_mail_notification.rb | 9 +
.../db/migrate/076_allow_null_position.rb | 14 +
.../077_remove_issue_statuses_html_color.rb | 9 +
.../migrate/078_add_custom_fields_position.rb | 15 +
.../079_add_user_preferences_time_zone.rb | 9 +
rest_sys/db/migrate/080_add_users_type.rb | 10 +
.../migrate/081_create_projects_trackers.rb | 19 +
.../db/migrate/082_add_messages_locked.rb | 9 +
.../db/migrate/083_add_messages_sticky.rb | 9 +
.../084_change_auth_sources_account_limit.rb | 9 +
...e_tracker_old_status_index_to_workflows.rb | 9 +
.../086_add_custom_fields_searchable.rb | 9 +
rest_sys/doc/CHANGELOG | 386 +++
rest_sys/doc/COPYING | 339 +++
rest_sys/doc/INSTALL | 62 +
rest_sys/doc/RUNNING_TESTS | 21 +
rest_sys/doc/UPGRADING | 35 +
rest_sys/extra/sample_plugin/README | 21 +
.../app/controllers/example_controller.rb | 17 +
.../app/views/example/say_goodbye.rhtml | 5 +
.../app/views/example/say_hello.rhtml | 9 +
.../app/views/settings/_settings.rhtml | 3 +
.../sample_plugin/assets/images/it_works.png | Bin 0 -> 3593 bytes
.../assets/stylesheets/example.css | 1 +
.../db/migrate/001_create_some_models.rb | 13 +
rest_sys/extra/sample_plugin/init.rb | 25 +
rest_sys/extra/sample_plugin/lang/en.yml | 4 +
rest_sys/extra/sample_plugin/lang/fr.yml | 4 +
rest_sys/extra/svn/Redmine.pm | 210 ++
rest_sys/extra/svn/create_views.sql | 24 +
rest_sys/extra/svn/reposman.pl | 142 +
rest_sys/extra/svn/reposman.rb | 234 ++
rest_sys/extra/svn/svnserve.wrapper | 25 +
rest_sys/files/delete.me | 1 +
rest_sys/lang/bg.yml | 552 ++++
rest_sys/lang/cs.yml | 552 ++++
rest_sys/lang/de.yml | 552 ++++
rest_sys/lang/en.yml | 552 ++++
rest_sys/lang/es.yml | 555 ++++
rest_sys/lang/fr.yml | 552 ++++
rest_sys/lang/he.yml | 552 ++++
rest_sys/lang/it.yml | 552 ++++
rest_sys/lang/ja.yml | 553 ++++
rest_sys/lang/ko.yml | 552 ++++
rest_sys/lang/nl.yml | 553 ++++
rest_sys/lang/pl.yml | 552 ++++
rest_sys/lang/pt-br.yml | 552 ++++
rest_sys/lang/pt.yml | 552 ++++
rest_sys/lang/ro.yml | 552 ++++
rest_sys/lang/ru.yml | 552 ++++
rest_sys/lang/sr.yml | 553 ++++
rest_sys/lang/sv.yml | 553 ++++
rest_sys/lang/zh-tw.yml | 552 ++++
rest_sys/lang/zh.yml | 555 ++++
rest_sys/lib/SVG/GPL.txt | 340 +++
rest_sys/lib/SVG/Graph/Bar.rb | 137 +
rest_sys/lib/SVG/Graph/BarBase.rb | 140 +
rest_sys/lib/SVG/Graph/BarHorizontal.rb | 136 +
rest_sys/lib/SVG/Graph/Graph.rb | 977 +++++++
rest_sys/lib/SVG/Graph/Line.rb | 444 +++
rest_sys/lib/SVG/Graph/Pie.rb | 394 +++
rest_sys/lib/SVG/Graph/Plot.rb | 494 ++++
rest_sys/lib/SVG/Graph/Schedule.rb | 373 +++
rest_sys/lib/SVG/Graph/TimeSeries.rb | 241 ++
rest_sys/lib/SVG/LICENSE.txt | 57 +
rest_sys/lib/diff.rb | 280 ++
rest_sys/lib/redcloth.rb | 1132 ++++++++
rest_sys/lib/redmine.rb | 106 +
rest_sys/lib/redmine/access_control.rb | 112 +
rest_sys/lib/redmine/helpers/calendar.rb | 76 +
rest_sys/lib/redmine/info.rb | 10 +
rest_sys/lib/redmine/menu_manager.rb | 61 +
rest_sys/lib/redmine/mime_type.rb | 61 +
rest_sys/lib/redmine/plugin.rb | 124 +
.../redmine/scm/adapters/abstract_adapter.rb | 385 +++
.../redmine/scm/adapters/bazaar_adapter.rb | 197 ++
.../lib/redmine/scm/adapters/cvs_adapter.rb | 361 +++
.../lib/redmine/scm/adapters/darcs_adapter.rb | 157 +
.../redmine/scm/adapters/mercurial_adapter.rb | 171 ++
.../scm/adapters/subversion_adapter.rb | 194 ++
rest_sys/lib/redmine/themes.rb | 72 +
rest_sys/lib/redmine/version.rb | 35 +
rest_sys/lib/redmine/wiki_formatting.rb | 160 ++
.../lib/redmine/wiki_formatting/macros.rb | 81 +
rest_sys/lib/tasks/deprecated.rake | 9 +
rest_sys/lib/tasks/extract_fixtures.rake | 24 +
rest_sys/lib/tasks/load_default_data.rake | 168 ++
rest_sys/lib/tasks/migrate_from_mantis.rake | 491 ++++
rest_sys/lib/tasks/migrate_from_trac.rake | 555 ++++
rest_sys/lib/tasks/migrate_plugins.rake | 15 +
rest_sys/log/delete.me | 1 +
rest_sys/public/.htaccess | 55 +
rest_sys/public/404.html | 23 +
rest_sys/public/500.html | 24 +
rest_sys/public/dispatch.cgi.example | 10 +
rest_sys/public/dispatch.fcgi.example | 24 +
rest_sys/public/dispatch.rb.example | 10 +
rest_sys/public/favicon.ico | 0
rest_sys/public/help/wiki_syntax.html | 57 +
rest_sys/public/images/1downarrow.png | Bin 0 -> 343 bytes
rest_sys/public/images/1uparrow.png | Bin 0 -> 335 bytes
rest_sys/public/images/22x22/authent.png | Bin 0 -> 1040 bytes
rest_sys/public/images/22x22/comment.png | Bin 0 -> 1078 bytes
rest_sys/public/images/22x22/file.png | Bin 0 -> 432 bytes
rest_sys/public/images/22x22/info.png | Bin 0 -> 985 bytes
.../public/images/22x22/notifications.png | Bin 0 -> 1055 bytes
rest_sys/public/images/22x22/options.png | Bin 0 -> 926 bytes
rest_sys/public/images/22x22/package.png | Bin 0 -> 679 bytes
rest_sys/public/images/22x22/plugin.png | Bin 0 -> 1338 bytes
rest_sys/public/images/22x22/projects.png | Bin 0 -> 1567 bytes
rest_sys/public/images/22x22/role.png | Bin 0 -> 991 bytes
rest_sys/public/images/22x22/settings.png | Bin 0 -> 772 bytes
rest_sys/public/images/22x22/tracker.png | Bin 0 -> 1551 bytes
rest_sys/public/images/22x22/users.png | Bin 0 -> 1543 bytes
rest_sys/public/images/22x22/workflow.png | Bin 0 -> 950 bytes
rest_sys/public/images/2downarrow.png | Bin 0 -> 396 bytes
rest_sys/public/images/2uparrow.png | Bin 0 -> 409 bytes
rest_sys/public/images/32x32/file.png | Bin 0 -> 646 bytes
rest_sys/public/images/add.png | Bin 0 -> 1199 bytes
rest_sys/public/images/admin.png | Bin 0 -> 595 bytes
rest_sys/public/images/arrow_bw.png | Bin 0 -> 998 bytes
rest_sys/public/images/arrow_down.png | Bin 0 -> 171 bytes
rest_sys/public/images/arrow_from.png | Bin 0 -> 995 bytes
rest_sys/public/images/arrow_to.png | Bin 0 -> 997 bytes
rest_sys/public/images/attachment.png | Bin 0 -> 259 bytes
rest_sys/public/images/calendar.png | Bin 0 -> 593 bytes
rest_sys/public/images/cancel.png | Bin 0 -> 433 bytes
rest_sys/public/images/close.png | Bin 0 -> 200 bytes
rest_sys/public/images/close_hl.png | Bin 0 -> 199 bytes
rest_sys/public/images/contentbg.png | Bin 0 -> 152 bytes
rest_sys/public/images/copy.png | Bin 0 -> 291 bytes
rest_sys/public/images/csv.png | Bin 0 -> 333 bytes
rest_sys/public/images/delete.png | Bin 0 -> 265 bytes
rest_sys/public/images/draft.png | Bin 0 -> 2103 bytes
rest_sys/public/images/edit.png | Bin 0 -> 1340 bytes
rest_sys/public/images/expand.png | Bin 0 -> 266 bytes
rest_sys/public/images/external.png | Bin 0 -> 194 bytes
rest_sys/public/images/false.png | Bin 0 -> 588 bytes
rest_sys/public/images/fav.png | Bin 0 -> 492 bytes
rest_sys/public/images/fav_off.png | Bin 0 -> 325 bytes
rest_sys/public/images/feed.png | Bin 0 -> 359 bytes
rest_sys/public/images/file.png | Bin 0 -> 362 bytes
rest_sys/public/images/folder.png | Bin 0 -> 1026 bytes
rest_sys/public/images/folder_open.png | Bin 0 -> 1031 bytes
rest_sys/public/images/help.png | Bin 0 -> 526 bytes
rest_sys/public/images/history.png | Bin 0 -> 322 bytes
rest_sys/public/images/home.png | Bin 0 -> 1377 bytes
rest_sys/public/images/html.png | Bin 0 -> 551 bytes
rest_sys/public/images/image.png | Bin 0 -> 610 bytes
rest_sys/public/images/index.png | Bin 0 -> 566 bytes
rest_sys/public/images/jstoolbar/bt_br.png | Bin 0 -> 244 bytes
rest_sys/public/images/jstoolbar/bt_code.png | Bin 0 -> 338 bytes
rest_sys/public/images/jstoolbar/bt_del.png | Bin 0 -> 368 bytes
rest_sys/public/images/jstoolbar/bt_em.png | Bin 0 -> 311 bytes
.../public/images/jstoolbar/bt_heading.png | Bin 0 -> 415 bytes
rest_sys/public/images/jstoolbar/bt_ins.png | Bin 0 -> 322 bytes
rest_sys/public/images/jstoolbar/bt_link.png | Bin 0 -> 900 bytes
rest_sys/public/images/jstoolbar/bt_ol.png | Bin 0 -> 249 bytes
rest_sys/public/images/jstoolbar/bt_quote.png | Bin 0 -> 323 bytes
.../public/images/jstoolbar/bt_strong.png | Bin 0 -> 355 bytes
rest_sys/public/images/jstoolbar/bt_ul.png | Bin 0 -> 239 bytes
rest_sys/public/images/loading.gif | Bin 0 -> 1553 bytes
rest_sys/public/images/locked.png | Bin 0 -> 566 bytes
rest_sys/public/images/mainbg.png | Bin 0 -> 150 bytes
rest_sys/public/images/milestone.png | Bin 0 -> 235 bytes
rest_sys/public/images/move.png | Bin 0 -> 435 bytes
rest_sys/public/images/note.png | Bin 0 -> 469 bytes
rest_sys/public/images/package.png | Bin 0 -> 530 bytes
rest_sys/public/images/pdf.png | Bin 0 -> 403 bytes
rest_sys/public/images/projects.png | Bin 0 -> 690 bytes
rest_sys/public/images/reload.png | Bin 0 -> 528 bytes
rest_sys/public/images/save.png | Bin 0 -> 498 bytes
rest_sys/public/images/sort_asc.png | Bin 0 -> 214 bytes
rest_sys/public/images/sort_desc.png | Bin 0 -> 213 bytes
rest_sys/public/images/stats.png | Bin 0 -> 1178 bytes
rest_sys/public/images/sticky.png | Bin 0 -> 461 bytes
rest_sys/public/images/sub.gif | Bin 0 -> 52 bytes
rest_sys/public/images/task_done.png | Bin 0 -> 970 bytes
rest_sys/public/images/task_late.png | Bin 0 -> 208 bytes
rest_sys/public/images/task_todo.png | Bin 0 -> 208 bytes
rest_sys/public/images/time.png | Bin 0 -> 404 bytes
rest_sys/public/images/true.png | Bin 0 -> 1094 bytes
rest_sys/public/images/txt.png | Bin 0 -> 333 bytes
rest_sys/public/images/unlock.png | Bin 0 -> 643 bytes
rest_sys/public/images/user.png | Bin 0 -> 455 bytes
rest_sys/public/images/user_new.png | Bin 0 -> 467 bytes
rest_sys/public/images/user_page.png | Bin 0 -> 485 bytes
rest_sys/public/images/users.png | Bin 0 -> 761 bytes
rest_sys/public/images/warning.png | Bin 0 -> 535 bytes
rest_sys/public/images/zoom_in.png | Bin 0 -> 593 bytes
rest_sys/public/images/zoom_in_g.png | Bin 0 -> 312 bytes
rest_sys/public/images/zoom_out.png | Bin 0 -> 588 bytes
rest_sys/public/images/zoom_out_g.png | Bin 0 -> 311 bytes
rest_sys/public/javascripts/application.js | 120 +
.../javascripts/calendar/calendar-setup.js | 200 ++
.../public/javascripts/calendar/calendar.js | 1806 ++++++++++++
.../javascripts/calendar/lang/calendar-bg.js | 127 +
.../javascripts/calendar/lang/calendar-cs.js | 69 +
.../javascripts/calendar/lang/calendar-de.js | 128 +
.../javascripts/calendar/lang/calendar-en.js | 127 +
.../javascripts/calendar/lang/calendar-es.js | 129 +
.../javascripts/calendar/lang/calendar-fr.js | 129 +
.../javascripts/calendar/lang/calendar-he.js | 127 +
.../javascripts/calendar/lang/calendar-it.js | 127 +
.../javascripts/calendar/lang/calendar-ja.js | 87 +
.../javascripts/calendar/lang/calendar-ko.js | 127 +
.../javascripts/calendar/lang/calendar-nl.js | 127 +
.../javascripts/calendar/lang/calendar-pl.js | 127 +
.../calendar/lang/calendar-pt-br.js | 127 +
.../javascripts/calendar/lang/calendar-pt.js | 127 +
.../javascripts/calendar/lang/calendar-ro.js | 127 +
.../javascripts/calendar/lang/calendar-ru.js | 127 +
.../javascripts/calendar/lang/calendar-sr.js | 127 +
.../javascripts/calendar/lang/calendar-sv.js | 84 +
.../calendar/lang/calendar-zh-tw.js | 127 +
.../javascripts/calendar/lang/calendar-zh.js | 127 +
rest_sys/public/javascripts/context_menu.js | 44 +
rest_sys/public/javascripts/controls.js | 833 ++++++
rest_sys/public/javascripts/dragdrop.js | 942 ++++++
rest_sys/public/javascripts/effects.js | 1088 +++++++
rest_sys/public/javascripts/jstoolbar.js | 468 +++
rest_sys/public/javascripts/prototype.js | 2515 +++++++++++++++++
.../public/javascripts/select_list_move.js | 55 +
rest_sys/public/robots.txt | 4 +
rest_sys/public/stylesheets/application.css | 492 ++++
rest_sys/public/stylesheets/calendar.css | 237 ++
rest_sys/public/stylesheets/context_menu.css | 52 +
rest_sys/public/stylesheets/csshover.htc | 120 +
rest_sys/public/stylesheets/jstoolbar.css | 81 +
rest_sys/public/stylesheets/scm.css | 144 +
rest_sys/public/themes/README | 1 +
.../alternate/stylesheets/application.css | 69 +
.../classic/stylesheets/application.css | 39 +
rest_sys/script/about | 3 +
rest_sys/script/breakpointer | 3 +
rest_sys/script/console | 3 +
rest_sys/script/destroy | 3 +
rest_sys/script/generate | 3 +
rest_sys/script/performance/benchmarker | 3 +
rest_sys/script/performance/profiler | 3 +
rest_sys/script/plugin | 3 +
rest_sys/script/process/reaper | 3 +
rest_sys/script/process/spawner | 3 +
rest_sys/script/process/spinner | 3 +
rest_sys/script/runner | 3 +
rest_sys/script/server | 3 +
rest_sys/test/fixtures/attachments.yml | 26 +
rest_sys/test/fixtures/auth_sources.yml | 2 +
rest_sys/test/fixtures/boards.yml | 19 +
rest_sys/test/fixtures/changes.yml | 16 +
rest_sys/test/fixtures/changesets.yml | 38 +
rest_sys/test/fixtures/comments.yml | 10 +
rest_sys/test/fixtures/custom_fields.yml | 58 +
.../test/fixtures/custom_fields_projects.yml | 2 +
.../test/fixtures/custom_fields_trackers.yml | 10 +
rest_sys/test/fixtures/custom_values.yml | 56 +
rest_sys/test/fixtures/documents.yml | 7 +
rest_sys/test/fixtures/enabled_modules.yml | 42 +
rest_sys/test/fixtures/enumerations.yml | 33 +
rest_sys/test/fixtures/files/testfile.txt | 1 +
rest_sys/test/fixtures/issue_categories.yml | 11 +
rest_sys/test/fixtures/issue_statuses.yml | 31 +
rest_sys/test/fixtures/issues.yml | 60 +
rest_sys/test/fixtures/journal_details.yml | 15 +
rest_sys/test/fixtures/journals.yml | 8 +
.../mail_handler/add_note_to_issue.txt | 14 +
rest_sys/test/fixtures/members.yml | 27 +
rest_sys/test/fixtures/messages.yml | 57 +
rest_sys/test/fixtures/news.yml | 22 +
rest_sys/test/fixtures/projects.yml | 45 +
rest_sys/test/fixtures/projects_trackers.yml | 46 +
rest_sys/test/fixtures/queries.yml | 22 +
rest_sys/test/fixtures/repositories.yml | 17 +
.../repositories/bazaar_repository.tar.gz | Bin 0 -> 9382 bytes
.../repositories/cvs_repository.tar.gz | Bin 0 -> 12248 bytes
.../repositories/mercurial_repository.tar.gz | Bin 0 -> 7827 bytes
.../subversion_repository.dump.gz | Bin 0 -> 11015 bytes
rest_sys/test/fixtures/roles.yml | 163 ++
rest_sys/test/fixtures/time_entries.yml | 43 +
rest_sys/test/fixtures/tokens.yml | 13 +
rest_sys/test/fixtures/trackers.yml | 13 +
rest_sys/test/fixtures/user_preferences.yml | 24 +
rest_sys/test/fixtures/users.yml | 77 +
rest_sys/test/fixtures/versions.yml | 26 +
.../test/fixtures/wiki_content_versions.yml | 52 +
rest_sys/test/fixtures/wiki_contents.yml | 24 +
rest_sys/test/fixtures/wiki_pages.yml | 12 +
rest_sys/test/fixtures/wikis.yml | 6 +
rest_sys/test/fixtures/workflows.yml | 1621 +++++++++++
.../functional/account_controller_test.rb | 73 +
.../test/functional/admin_controller_test.rb | 61 +
.../functional/application_controller_test.rb | 39 +
.../test/functional/boards_controller_test.rb | 50 +
.../test/functional/issues_controller_test.rb | 163 ++
.../functional/messages_controller_test.rb | 99 +
.../test/functional/my_controller_test.rb | 91 +
.../test/functional/news_controller_test.rb | 48 +
.../functional/projects_controller_test.rb | 257 ++
.../repositories_controller_test.rb | 64 +
.../repositories_mercurial_controller_test.rb | 117 +
...repositories_subversion_controller_test.rb | 91 +
.../test/functional/search_controller_test.rb | 65 +
rest_sys/test/functional/sys_api_test.rb | 31 +
.../functional/timelog_controller_test.rb | 52 +
.../test/functional/users_controller_test.rb | 62 +
.../functional/versions_controller_test.rb | 73 +
.../functional/welcome_controller_test.rb | 49 +
.../test/functional/wiki_controller_test.rb | 145 +
.../test/functional/wikis_controller_test.rb | 56 +
rest_sys/test/helper_testcase.rb | 35 +
rest_sys/test/integration/account_test.rb | 101 +
rest_sys/test/integration/admin_test.rb | 65 +
rest_sys/test/integration/issues_test.rb | 58 +
rest_sys/test/integration/projects_test.rb | 44 +
rest_sys/test/test_helper.rb | 73 +
rest_sys/test/unit/board_test.rb | 30 +
rest_sys/test/unit/calendar_test.rb | 43 +
rest_sys/test/unit/changeset_test.rb | 62 +
rest_sys/test/unit/comment_test.rb | 47 +
rest_sys/test/unit/custom_field_test.rb | 32 +
rest_sys/test/unit/custom_value_test.rb | 34 +
.../unit/helpers/application_helper_test.rb | 106 +
.../test/unit/helpers/projects_helper_test.rb | 41 +
rest_sys/test/unit/issue_category_test.rb | 41 +
rest_sys/test/unit/issue_status_test.rb | 49 +
rest_sys/test/unit/issue_test.rb | 73 +
rest_sys/test/unit/journal_test.rb | 39 +
rest_sys/test/unit/mail_handler_test.rb | 57 +
rest_sys/test/unit/mailer_test.rb | 100 +
rest_sys/test/unit/member_test.rb | 51 +
rest_sys/test/unit/message_test.rb | 70 +
rest_sys/test/unit/project_test.rb | 124 +
rest_sys/test/unit/query_test.rb | 44 +
rest_sys/test/unit/repository_bazaar_test.rb | 88 +
rest_sys/test/unit/repository_cvs_test.rb | 60 +
.../test/unit/repository_mercurial_test.rb | 55 +
.../test/unit/repository_subversion_test.rb | 55 +
rest_sys/test/unit/repository_test.rb | 76 +
rest_sys/test/unit/setting_test.rb | 45 +
rest_sys/test/unit/token_test.rb | 29 +
rest_sys/test/unit/user_preference_test.rb | 43 +
rest_sys/test/unit/user_test.rb | 139 +
rest_sys/test/unit/watcher_test.rb | 69 +
rest_sys/test/unit/wiki_content_test.rb | 60 +
rest_sys/test/unit/wiki_page_test.rb | 59 +
rest_sys/test/unit/wiki_redirect_test.rb | 73 +
rest_sys/test/unit/wiki_test.rb | 44 +
.../vendor/plugins/actionwebservice/CHANGELOG | 265 ++
.../plugins/actionwebservice/MIT-LICENSE | 21 +
.../vendor/plugins/actionwebservice/README | 364 +++
.../vendor/plugins/actionwebservice/Rakefile | 172 ++
rest_sys/vendor/plugins/actionwebservice/TODO | 32 +
.../vendor/plugins/actionwebservice/init.rb | 7 +
.../plugins/actionwebservice/install.rb | 30 +
.../lib/action_web_service.rb | 66 +
.../lib/action_web_service/api.rb | 297 ++
.../lib/action_web_service/base.rb | 38 +
.../lib/action_web_service/casting.rb | 138 +
.../lib/action_web_service/client.rb | 3 +
.../lib/action_web_service/client/base.rb | 28 +
.../action_web_service/client/soap_client.rb | 113 +
.../client/xmlrpc_client.rb | 58 +
.../lib/action_web_service/container.rb | 3 +
.../container/action_controller_container.rb | 93 +
.../container/delegated_container.rb | 86 +
.../container/direct_container.rb | 69 +
.../lib/action_web_service/dispatcher.rb | 2 +
.../action_web_service/dispatcher/abstract.rb | 207 ++
.../action_controller_dispatcher.rb | 379 +++
.../lib/action_web_service/invocation.rb | 202 ++
.../lib/action_web_service/protocol.rb | 4 +
.../action_web_service/protocol/abstract.rb | 112 +
.../action_web_service/protocol/discovery.rb | 37 +
.../protocol/soap_protocol.rb | 176 ++
.../protocol/soap_protocol/marshaler.rb | 235 ++
.../protocol/xmlrpc_protocol.rb | 122 +
.../lib/action_web_service/scaffolding.rb | 283 ++
.../lib/action_web_service/struct.rb | 64 +
.../support/class_inheritable_options.rb | 26 +
.../support/signature_types.rb | 226 ++
.../templates/scaffolds/layout.erb | 65 +
.../templates/scaffolds/layout.rhtml | 0
.../templates/scaffolds/methods.erb | 6 +
.../templates/scaffolds/methods.rhtml | 0
.../templates/scaffolds/parameters.erb | 29 +
.../templates/scaffolds/parameters.rhtml | 0
.../templates/scaffolds/result.erb | 30 +
.../templates/scaffolds/result.rhtml | 0
.../lib/action_web_service/test_invoke.rb | 110 +
.../lib/action_web_service/version.rb | 9 +
.../actionwebservice/lib/actionwebservice.rb | 1 +
.../vendor/plugins/actionwebservice/setup.rb | 1379 +++++++++
rest_sys/vendor/plugins/acts_as_event/init.rb | 2 +
.../acts_as_event/lib/acts_as_event.rb | 68 +
rest_sys/vendor/plugins/acts_as_list/README | 23 +
rest_sys/vendor/plugins/acts_as_list/init.rb | 3 +
.../lib/active_record/acts/list.rb | 256 ++
.../plugins/acts_as_list/test/list_test.rb | 332 +++
.../vendor/plugins/acts_as_searchable/init.rb | 2 +
.../lib/acts_as_searchable.rb | 110 +
rest_sys/vendor/plugins/acts_as_tree/README | 26 +
rest_sys/vendor/plugins/acts_as_tree/Rakefile | 22 +
rest_sys/vendor/plugins/acts_as_tree/init.rb | 1 +
.../lib/active_record/acts/tree.rb | 96 +
.../acts_as_tree/test/abstract_unit.rb | 0
.../acts_as_tree/test/acts_as_tree_test.rb | 219 ++
.../plugins/acts_as_tree/test/database.yml | 0
.../acts_as_tree/test/fixtures/mixin.rb | 0
.../acts_as_tree/test/fixtures/mixins.yml | 0
.../plugins/acts_as_tree/test/schema.rb | 0
.../plugins/acts_as_versioned/CHANGELOG | 74 +
.../plugins/acts_as_versioned/MIT-LICENSE | 20 +
.../vendor/plugins/acts_as_versioned/README | 28 +
.../acts_as_versioned/RUNNING_UNIT_TESTS | 41 +
.../vendor/plugins/acts_as_versioned/Rakefile | 182 ++
.../vendor/plugins/acts_as_versioned/init.rb | 1 +
.../lib/acts_as_versioned.rb | 511 ++++
.../acts_as_versioned/test/abstract_unit.rb | 40 +
.../acts_as_versioned/test/database.yml | 18 +
.../test/fixtures/authors.yml | 6 +
.../test/fixtures/landmark.rb | 3 +
.../test/fixtures/landmark_versions.yml | 7 +
.../test/fixtures/landmarks.yml | 6 +
.../test/fixtures/locked_pages.yml | 10 +
.../test/fixtures/locked_pages_revisions.yml | 27 +
.../migrations/1_add_versioned_tables.rb | 13 +
.../acts_as_versioned/test/fixtures/page.rb | 43 +
.../test/fixtures/page_versions.yml | 16 +
.../acts_as_versioned/test/fixtures/pages.yml | 7 +
.../acts_as_versioned/test/fixtures/widget.rb | 6 +
.../acts_as_versioned/test/migration_test.rb | 32 +
.../plugins/acts_as_versioned/test/schema.rb | 68 +
.../acts_as_versioned/test/versioned_test.rb | 313 ++
.../vendor/plugins/acts_as_watchable/init.rb | 3 +
.../lib/acts_as_watchable.rb | 53 +
.../plugins/classic_pagination/CHANGELOG | 152 +
.../vendor/plugins/classic_pagination/README | 18 +
.../plugins/classic_pagination/Rakefile | 22 +
.../vendor/plugins/classic_pagination/init.rb | 33 +
.../plugins/classic_pagination/install.rb | 1 +
.../classic_pagination/lib/pagination.rb | 405 +++
.../lib/pagination_helper.rb | 135 +
.../test/fixtures/companies.yml | 24 +
.../test/fixtures/company.rb | 9 +
.../test/fixtures/developer.rb | 7 +
.../test/fixtures/developers.yml | 21 +
.../test/fixtures/developers_projects.yml | 13 +
.../test/fixtures/project.rb | 3 +
.../test/fixtures/projects.yml | 7 +
.../test/fixtures/replies.yml | 13 +
.../classic_pagination/test/fixtures/reply.rb | 5 +
.../test/fixtures/schema.sql | 42 +
.../classic_pagination/test/fixtures/topic.rb | 3 +
.../test/fixtures/topics.yml | 22 +
.../plugins/classic_pagination/test/helper.rb | 117 +
.../test/pagination_helper_test.rb | 38 +
.../test/pagination_test.rb | 177 ++
.../vendor/plugins/coderay-0.7.6.227/FOLDERS | 53 +
.../vendor/plugins/coderay-0.7.6.227/LICENSE | 504 ++++
.../vendor/plugins/coderay-0.7.6.227/README | 128 +
.../plugins/coderay-0.7.6.227/bin/coderay | 82 +
.../coderay-0.7.6.227/bin/coderay_stylesheet | 4 +
.../plugins/coderay-0.7.6.227/lib/coderay.rb | 320 +++
.../coderay-0.7.6.227/lib/coderay/duo.rb | 87 +
.../coderay-0.7.6.227/lib/coderay/encoder.rb | 177 ++
.../lib/coderay/encoders/_map.rb | 9 +
.../lib/coderay/encoders/count.rb | 21 +
.../lib/coderay/encoders/debug.rb | 41 +
.../lib/coderay/encoders/div.rb | 20 +
.../lib/coderay/encoders/html.rb | 268 ++
.../lib/coderay/encoders/html/css.rb | 65 +
.../lib/coderay/encoders/html/numerization.rb | 122 +
.../lib/coderay/encoders/html/output.rb | 195 ++
.../lib/coderay/encoders/null.rb | 26 +
.../lib/coderay/encoders/page.rb | 21 +
.../lib/coderay/encoders/span.rb | 20 +
.../lib/coderay/encoders/statistic.rb | 77 +
.../lib/coderay/encoders/text.rb | 32 +
.../lib/coderay/encoders/tokens.rb | 44 +
.../lib/coderay/encoders/xml.rb | 70 +
.../lib/coderay/encoders/yaml.rb | 22 +
.../lib/coderay/helpers/file_type.rb | 190 ++
.../lib/coderay/helpers/gzip_simple.rb | 123 +
.../lib/coderay/helpers/plugin.rb | 329 +++
.../lib/coderay/helpers/word_list.rb | 123 +
.../coderay-0.7.6.227/lib/coderay/scanner.rb | 253 ++
.../lib/coderay/scanners/_map.rb | 15 +
.../lib/coderay/scanners/c.rb | 165 ++
.../lib/coderay/scanners/debug.rb | 60 +
.../lib/coderay/scanners/delphi.rb | 149 +
.../lib/coderay/scanners/html.rb | 177 ++
.../lib/coderay/scanners/javascript.rb | 176 ++
.../lib/coderay/scanners/nitro_xhtml.rb | 133 +
.../lib/coderay/scanners/plaintext.rb | 18 +
.../lib/coderay/scanners/rhtml.rb | 73 +
.../lib/coderay/scanners/ruby.rb | 368 +++
.../lib/coderay/scanners/ruby/patterns.rb | 230 ++
.../lib/coderay/scanners/scheme.rb | 142 +
.../lib/coderay/scanners/xml.rb | 18 +
.../coderay-0.7.6.227/lib/coderay/style.rb | 20 +
.../lib/coderay/styles/_map.rb | 7 +
.../lib/coderay/styles/cycnus.rb | 127 +
.../lib/coderay/styles/murphy.rb | 119 +
.../lib/coderay/token_classes.rb | 71 +
.../coderay-0.7.6.227/lib/coderay/tokens.rb | 383 +++
rest_sys/vendor/plugins/gloc-1.1.0/CHANGELOG | 19 +
.../vendor/plugins/gloc-1.1.0/MIT-LICENSE | 19 +
rest_sys/vendor/plugins/gloc-1.1.0/README | 208 ++
rest_sys/vendor/plugins/gloc-1.1.0/Rakefile | 15 +
.../Filters/ClassMethods.html | 230 ++
.../doc/classes/ActionMailer/Base.html | 140 +
.../doc/classes/ActionView/Base.html | 174 ++
.../ActionView/Helpers/DateHelper.html | 348 +++
.../ActionView/Helpers/InstanceTag.html | 167 ++
.../doc/classes/ActiveRecord/Errors.html | 215 ++
.../Validations/ClassMethods.html | 217 ++
.../plugins/gloc-1.1.0/doc/classes/GLoc.html | 774 +++++
.../doc/classes/GLoc/ClassMethods.html | 160 ++
.../gloc-1.1.0/doc/classes/GLoc/Helpers.html | 323 +++
.../doc/classes/GLoc/InstanceMethods.html | 364 +++
.../vendor/plugins/gloc-1.1.0/doc/created.rid | 1 +
.../gloc-1.1.0/doc/files/CHANGELOG.html | 153 +
.../plugins/gloc-1.1.0/doc/files/README.html | 480 ++++
.../doc/files/lib/gloc-helpers_rb.html | 107 +
.../doc/files/lib/gloc-internal_rb.html | 115 +
.../doc/files/lib/gloc-rails-text_rb.html | 114 +
.../doc/files/lib/gloc-rails_rb.html | 114 +
.../doc/files/lib/gloc-ruby_rb.html | 107 +
.../doc/files/lib/gloc-version_rb.html | 101 +
.../gloc-1.1.0/doc/files/lib/gloc_rb.html | 116 +
.../gloc-1.1.0/doc/fr_class_index.html | 37 +
.../plugins/gloc-1.1.0/doc/fr_file_index.html | 35 +
.../gloc-1.1.0/doc/fr_method_index.html | 72 +
.../vendor/plugins/gloc-1.1.0/doc/index.html | 24 +
.../plugins/gloc-1.1.0/doc/rdoc-style.css | 208 ++
rest_sys/vendor/plugins/gloc-1.1.0/init.rb | 11 +
.../plugins/gloc-1.1.0/lib/gloc-config.rb | 16 +
.../vendor/plugins/gloc-1.1.0/lib/gloc-dev.rb | 97 +
.../plugins/gloc-1.1.0/lib/gloc-helpers.rb | 20 +
.../plugins/gloc-1.1.0/lib/gloc-internal.rb | 134 +
.../plugins/gloc-1.1.0/lib/gloc-rails-text.rb | 150 +
.../plugins/gloc-1.1.0/lib/gloc-rails.rb | 231 ++
.../plugins/gloc-1.1.0/lib/gloc-ruby.rb | 7 +
.../plugins/gloc-1.1.0/lib/gloc-version.rb | 12 +
.../vendor/plugins/gloc-1.1.0/lib/gloc.rb | 294 ++
.../vendor/plugins/gloc-1.1.0/tasks/gloc.rake | 53 +
.../gloc-1.1.0/test/gloc_rails_test.rb | 118 +
.../plugins/gloc-1.1.0/test/gloc_test.rb | 433 +++
.../plugins/gloc-1.1.0/test/lang/en.yaml | 2 +
.../plugins/gloc-1.1.0/test/lang/en_AU.yaml | 2 +
.../plugins/gloc-1.1.0/test/lang/ja.yml | 2 +
.../plugins/gloc-1.1.0/test/lang2/en.yml | 2 +
.../plugins/gloc-1.1.0/test/lang2/ja.yaml | 2 +
.../gloc-1.1.0/test/lib/rails-string_ext.rb | 23 +
.../gloc-1.1.0/test/lib/rails-time_ext.rb | 76 +
rest_sys/vendor/plugins/rfpdf/CHANGELOG | 13 +
rest_sys/vendor/plugins/rfpdf/MIT-LICENSE | 20 +
rest_sys/vendor/plugins/rfpdf/README | 99 +
rest_sys/vendor/plugins/rfpdf/init.rb | 3 +
rest_sys/vendor/plugins/rfpdf/lib/rfpdf.rb | 31 +
.../plugins/rfpdf/lib/rfpdf/bookmark.rb | 99 +
.../vendor/plugins/rfpdf/lib/rfpdf/chinese.rb | 473 ++++
.../vendor/plugins/rfpdf/lib/rfpdf/errors.rb | 4 +
.../vendor/plugins/rfpdf/lib/rfpdf/fpdf.rb | 1550 ++++++++++
.../plugins/rfpdf/lib/rfpdf/fpdf_eps.rb | 139 +
.../plugins/rfpdf/lib/rfpdf/japanese.rb | 468 +++
.../vendor/plugins/rfpdf/lib/rfpdf/korean.rb | 436 +++
.../plugins/rfpdf/lib/rfpdf/makefont.rb | 1787 ++++++++++++
.../vendor/plugins/rfpdf/lib/rfpdf/rfpdf.rb | 346 +++
.../vendor/plugins/rfpdf/lib/rfpdf/view.rb | 75 +
.../vendor/plugins/rfpdf/test/test_helper.rb | 1 +
.../plugins/ruby-net-ldap-0.0.4/COPYING | 272 ++
.../plugins/ruby-net-ldap-0.0.4/ChangeLog | 58 +
.../plugins/ruby-net-ldap-0.0.4/LICENCE | 55 +
.../vendor/plugins/ruby-net-ldap-0.0.4/README | 32 +
.../ruby-net-ldap-0.0.4/lib/net/ber.rb | 294 ++
.../ruby-net-ldap-0.0.4/lib/net/ldap.rb | 1311 +++++++++
.../lib/net/ldap/dataset.rb | 108 +
.../ruby-net-ldap-0.0.4/lib/net/ldap/entry.rb | 165 ++
.../lib/net/ldap/filter.rb | 387 +++
.../ruby-net-ldap-0.0.4/lib/net/ldap/pdu.rb | 205 ++
.../ruby-net-ldap-0.0.4/lib/net/ldap/psw.rb | 64 +
.../ruby-net-ldap-0.0.4/lib/net/ldif.rb | 39 +
.../ruby-net-ldap-0.0.4/tests/testber.rb | 42 +
.../ruby-net-ldap-0.0.4/tests/testdata.ldif | 101 +
.../ruby-net-ldap-0.0.4/tests/testem.rb | 12 +
.../ruby-net-ldap-0.0.4/tests/testfilter.rb | 37 +
.../ruby-net-ldap-0.0.4/tests/testldap.rb | 190 ++
.../ruby-net-ldap-0.0.4/tests/testldif.rb | 69 +
.../ruby-net-ldap-0.0.4/tests/testpsw.rb | 28 +
1001 files changed, 88195 insertions(+)
create mode 100644 rest_sys/Rakefile
create mode 100644 rest_sys/app/apis/sys_api.rb
create mode 100644 rest_sys/app/controllers/account_controller.rb
create mode 100644 rest_sys/app/controllers/admin_controller.rb
create mode 100644 rest_sys/app/controllers/application.rb
create mode 100644 rest_sys/app/controllers/attachments_controller.rb
create mode 100644 rest_sys/app/controllers/auth_sources_controller.rb
create mode 100644 rest_sys/app/controllers/boards_controller.rb
create mode 100644 rest_sys/app/controllers/custom_fields_controller.rb
create mode 100644 rest_sys/app/controllers/documents_controller.rb
create mode 100644 rest_sys/app/controllers/enumerations_controller.rb
create mode 100644 rest_sys/app/controllers/issue_categories_controller.rb
create mode 100644 rest_sys/app/controllers/issue_relations_controller.rb
create mode 100644 rest_sys/app/controllers/issue_statuses_controller.rb
create mode 100644 rest_sys/app/controllers/issues_controller.rb
create mode 100644 rest_sys/app/controllers/members_controller.rb
create mode 100644 rest_sys/app/controllers/messages_controller.rb
create mode 100644 rest_sys/app/controllers/my_controller.rb
create mode 100644 rest_sys/app/controllers/news_controller.rb
create mode 100644 rest_sys/app/controllers/projects_controller.rb
create mode 100644 rest_sys/app/controllers/queries_controller.rb
create mode 100644 rest_sys/app/controllers/reports_controller.rb
create mode 100644 rest_sys/app/controllers/repositories_controller.rb
create mode 100644 rest_sys/app/controllers/roles_controller.rb
create mode 100644 rest_sys/app/controllers/search_controller.rb
create mode 100644 rest_sys/app/controllers/settings_controller.rb
create mode 100644 rest_sys/app/controllers/sys_controller.rb
create mode 100644 rest_sys/app/controllers/timelog_controller.rb
create mode 100644 rest_sys/app/controllers/trackers_controller.rb
create mode 100644 rest_sys/app/controllers/users_controller.rb
create mode 100644 rest_sys/app/controllers/versions_controller.rb
create mode 100644 rest_sys/app/controllers/watchers_controller.rb
create mode 100644 rest_sys/app/controllers/welcome_controller.rb
create mode 100644 rest_sys/app/controllers/wiki_controller.rb
create mode 100644 rest_sys/app/controllers/wikis_controller.rb
create mode 100644 rest_sys/app/helpers/account_helper.rb
create mode 100644 rest_sys/app/helpers/admin_helper.rb
create mode 100644 rest_sys/app/helpers/application_helper.rb
create mode 100644 rest_sys/app/helpers/attachments_helper.rb
create mode 100644 rest_sys/app/helpers/auth_sources_helper.rb
create mode 100644 rest_sys/app/helpers/boards_helper.rb
create mode 100644 rest_sys/app/helpers/custom_fields_helper.rb
create mode 100644 rest_sys/app/helpers/documents_helper.rb
create mode 100644 rest_sys/app/helpers/enumerations_helper.rb
create mode 100644 rest_sys/app/helpers/ifpdf_helper.rb
create mode 100644 rest_sys/app/helpers/issue_categories_helper.rb
create mode 100644 rest_sys/app/helpers/issue_relations_helper.rb
create mode 100644 rest_sys/app/helpers/issue_statuses_helper.rb
create mode 100644 rest_sys/app/helpers/issues_helper.rb
create mode 100644 rest_sys/app/helpers/members_helper.rb
create mode 100644 rest_sys/app/helpers/messages_helper.rb
create mode 100644 rest_sys/app/helpers/my_helper.rb
create mode 100644 rest_sys/app/helpers/news_helper.rb
create mode 100644 rest_sys/app/helpers/projects_helper.rb
create mode 100644 rest_sys/app/helpers/queries_helper.rb
create mode 100644 rest_sys/app/helpers/reports_helper.rb
create mode 100644 rest_sys/app/helpers/repositories_helper.rb
create mode 100644 rest_sys/app/helpers/roles_helper.rb
create mode 100644 rest_sys/app/helpers/search_helper.rb
create mode 100644 rest_sys/app/helpers/settings_helper.rb
create mode 100644 rest_sys/app/helpers/sort_helper.rb
create mode 100644 rest_sys/app/helpers/timelog_helper.rb
create mode 100644 rest_sys/app/helpers/trackers_helper.rb
create mode 100644 rest_sys/app/helpers/users_helper.rb
create mode 100644 rest_sys/app/helpers/versions_helper.rb
create mode 100644 rest_sys/app/helpers/watchers_helper.rb
create mode 100644 rest_sys/app/helpers/welcome_helper.rb
create mode 100644 rest_sys/app/helpers/wiki_helper.rb
create mode 100644 rest_sys/app/models/attachment.rb
create mode 100644 rest_sys/app/models/auth_source.rb
create mode 100644 rest_sys/app/models/auth_source_ldap.rb
create mode 100644 rest_sys/app/models/board.rb
create mode 100644 rest_sys/app/models/change.rb
create mode 100644 rest_sys/app/models/changeset.rb
create mode 100644 rest_sys/app/models/comment.rb
create mode 100644 rest_sys/app/models/custom_field.rb
create mode 100644 rest_sys/app/models/custom_value.rb
create mode 100644 rest_sys/app/models/document.rb
create mode 100644 rest_sys/app/models/enabled_module.rb
create mode 100644 rest_sys/app/models/enumeration.rb
create mode 100644 rest_sys/app/models/issue.rb
create mode 100644 rest_sys/app/models/issue_category.rb
create mode 100644 rest_sys/app/models/issue_custom_field.rb
create mode 100644 rest_sys/app/models/issue_relation.rb
create mode 100644 rest_sys/app/models/issue_status.rb
create mode 100644 rest_sys/app/models/journal.rb
create mode 100644 rest_sys/app/models/journal_detail.rb
create mode 100644 rest_sys/app/models/mail_handler.rb
create mode 100644 rest_sys/app/models/mailer.rb
create mode 100644 rest_sys/app/models/member.rb
create mode 100644 rest_sys/app/models/message.rb
create mode 100644 rest_sys/app/models/message_observer.rb
create mode 100644 rest_sys/app/models/news.rb
create mode 100644 rest_sys/app/models/project.rb
create mode 100644 rest_sys/app/models/project_custom_field.rb
create mode 100644 rest_sys/app/models/query.rb
create mode 100644 rest_sys/app/models/repository.rb
create mode 100644 rest_sys/app/models/repository/bazaar.rb
create mode 100644 rest_sys/app/models/repository/cvs.rb
create mode 100644 rest_sys/app/models/repository/darcs.rb
create mode 100644 rest_sys/app/models/repository/mercurial.rb
create mode 100644 rest_sys/app/models/repository/subversion.rb
create mode 100644 rest_sys/app/models/role.rb
create mode 100644 rest_sys/app/models/setting.rb
create mode 100644 rest_sys/app/models/time_entry.rb
create mode 100644 rest_sys/app/models/token.rb
create mode 100644 rest_sys/app/models/tracker.rb
create mode 100644 rest_sys/app/models/user.rb
create mode 100644 rest_sys/app/models/user_custom_field.rb
create mode 100644 rest_sys/app/models/user_preference.rb
create mode 100644 rest_sys/app/models/version.rb
create mode 100644 rest_sys/app/models/watcher.rb
create mode 100644 rest_sys/app/models/wiki.rb
create mode 100644 rest_sys/app/models/wiki_content.rb
create mode 100644 rest_sys/app/models/wiki_page.rb
create mode 100644 rest_sys/app/models/wiki_redirect.rb
create mode 100644 rest_sys/app/models/workflow.rb
create mode 100644 rest_sys/app/sweepers/issue_sweeper.rb
create mode 100644 rest_sys/app/sweepers/project_sweeper.rb
create mode 100644 rest_sys/app/sweepers/version_sweeper.rb
create mode 100644 rest_sys/app/views/account/login.rhtml
create mode 100644 rest_sys/app/views/account/lost_password.rhtml
create mode 100644 rest_sys/app/views/account/password_recovery.rhtml
create mode 100644 rest_sys/app/views/account/register.rhtml
create mode 100644 rest_sys/app/views/account/show.rhtml
create mode 100644 rest_sys/app/views/admin/_menu.rhtml
create mode 100644 rest_sys/app/views/admin/index.rhtml
create mode 100644 rest_sys/app/views/admin/info.rhtml
create mode 100644 rest_sys/app/views/admin/mail_options.rhtml
create mode 100644 rest_sys/app/views/admin/projects.rhtml
create mode 100644 rest_sys/app/views/attachments/_form.rhtml
create mode 100644 rest_sys/app/views/attachments/_links.rhtml
create mode 100644 rest_sys/app/views/auth_sources/_form.rhtml
create mode 100644 rest_sys/app/views/auth_sources/edit.rhtml
create mode 100644 rest_sys/app/views/auth_sources/list.rhtml
create mode 100644 rest_sys/app/views/auth_sources/new.rhtml
create mode 100644 rest_sys/app/views/boards/_form.rhtml
create mode 100644 rest_sys/app/views/boards/edit.rhtml
create mode 100644 rest_sys/app/views/boards/index.rhtml
create mode 100644 rest_sys/app/views/boards/new.rhtml
create mode 100644 rest_sys/app/views/boards/show.rhtml
create mode 100644 rest_sys/app/views/common/403.rhtml
create mode 100644 rest_sys/app/views/common/404.rhtml
create mode 100644 rest_sys/app/views/common/_attachments_form.rhtml
create mode 100644 rest_sys/app/views/common/_calendar.rhtml
create mode 100644 rest_sys/app/views/common/_preview.rhtml
create mode 100644 rest_sys/app/views/common/feed.atom.rxml
create mode 100644 rest_sys/app/views/custom_fields/_form.rhtml
create mode 100644 rest_sys/app/views/custom_fields/edit.rhtml
create mode 100644 rest_sys/app/views/custom_fields/list.rhtml
create mode 100644 rest_sys/app/views/custom_fields/new.rhtml
create mode 100644 rest_sys/app/views/documents/_document.rhtml
create mode 100644 rest_sys/app/views/documents/_form.rhtml
create mode 100644 rest_sys/app/views/documents/edit.rhtml
create mode 100644 rest_sys/app/views/documents/show.rhtml
create mode 100644 rest_sys/app/views/enumerations/_form.rhtml
create mode 100644 rest_sys/app/views/enumerations/edit.rhtml
create mode 100644 rest_sys/app/views/enumerations/list.rhtml
create mode 100644 rest_sys/app/views/enumerations/new.rhtml
create mode 100644 rest_sys/app/views/issue_categories/_form.rhtml
create mode 100644 rest_sys/app/views/issue_categories/destroy.rhtml
create mode 100644 rest_sys/app/views/issue_categories/edit.rhtml
create mode 100644 rest_sys/app/views/issue_relations/_form.rhtml
create mode 100644 rest_sys/app/views/issue_statuses/_form.rhtml
create mode 100644 rest_sys/app/views/issue_statuses/edit.rhtml
create mode 100644 rest_sys/app/views/issue_statuses/list.rhtml
create mode 100644 rest_sys/app/views/issue_statuses/new.rhtml
create mode 100644 rest_sys/app/views/issues/_bulk_edit_form.rhtml
create mode 100644 rest_sys/app/views/issues/_form.rhtml
create mode 100644 rest_sys/app/views/issues/_history.rhtml
create mode 100644 rest_sys/app/views/issues/_list.rhtml
create mode 100644 rest_sys/app/views/issues/_list_simple.rhtml
create mode 100644 rest_sys/app/views/issues/_pdf.rfpdf
create mode 100644 rest_sys/app/views/issues/_relations.rhtml
create mode 100644 rest_sys/app/views/issues/_sidebar.rhtml
create mode 100644 rest_sys/app/views/issues/change_status.rhtml
create mode 100644 rest_sys/app/views/issues/changes.rxml
create mode 100644 rest_sys/app/views/issues/context_menu.rhtml
create mode 100644 rest_sys/app/views/issues/edit.rhtml
create mode 100644 rest_sys/app/views/issues/index.rfpdf
create mode 100644 rest_sys/app/views/issues/index.rhtml
create mode 100644 rest_sys/app/views/issues/show.rfpdf
create mode 100644 rest_sys/app/views/issues/show.rhtml
create mode 100644 rest_sys/app/views/layouts/_project_selector.rhtml
create mode 100644 rest_sys/app/views/layouts/base.rhtml
create mode 100644 rest_sys/app/views/mailer/_issue_text_html.rhtml
create mode 100644 rest_sys/app/views/mailer/_issue_text_plain.rhtml
create mode 100644 rest_sys/app/views/mailer/account_activation_request.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/account_activation_request.text.plain.rhtml
create mode 100644 rest_sys/app/views/mailer/account_information.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/account_information.text.plain.rhtml
create mode 100644 rest_sys/app/views/mailer/attachments_added.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/attachments_added.text.plain.rhtml
create mode 100644 rest_sys/app/views/mailer/document_added.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/document_added.text.plain.rhtml
create mode 100644 rest_sys/app/views/mailer/issue_add.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/issue_add.text.plain.rhtml
create mode 100644 rest_sys/app/views/mailer/issue_edit.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/issue_edit.text.plain.rhtml
create mode 100644 rest_sys/app/views/mailer/layout.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/layout.text.plain.rhtml
create mode 100644 rest_sys/app/views/mailer/lost_password.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/lost_password.text.plain.rhtml
create mode 100644 rest_sys/app/views/mailer/message_posted.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/message_posted.text.plain.rhtml
create mode 100644 rest_sys/app/views/mailer/news_added.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/news_added.text.plain.rhtml
create mode 100644 rest_sys/app/views/mailer/register.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/register.text.plain.rhtml
create mode 100644 rest_sys/app/views/mailer/test.text.html.rhtml
create mode 100644 rest_sys/app/views/mailer/test.text.plain.rhtml
create mode 100644 rest_sys/app/views/messages/_form.rhtml
create mode 100644 rest_sys/app/views/messages/edit.rhtml
create mode 100644 rest_sys/app/views/messages/new.rhtml
create mode 100644 rest_sys/app/views/messages/show.rhtml
create mode 100644 rest_sys/app/views/my/_block.rhtml
create mode 100644 rest_sys/app/views/my/_sidebar.rhtml
create mode 100644 rest_sys/app/views/my/account.rhtml
create mode 100644 rest_sys/app/views/my/blocks/_calendar.rhtml
create mode 100644 rest_sys/app/views/my/blocks/_documents.rhtml
create mode 100644 rest_sys/app/views/my/blocks/_issuesassignedtome.rhtml
create mode 100644 rest_sys/app/views/my/blocks/_issuesreportedbyme.rhtml
create mode 100644 rest_sys/app/views/my/blocks/_issueswatched.rhtml
create mode 100644 rest_sys/app/views/my/blocks/_news.rhtml
create mode 100644 rest_sys/app/views/my/page.rhtml
create mode 100644 rest_sys/app/views/my/page_layout.rhtml
create mode 100644 rest_sys/app/views/my/password.rhtml
create mode 100644 rest_sys/app/views/news/_form.rhtml
create mode 100644 rest_sys/app/views/news/_news.rhtml
create mode 100644 rest_sys/app/views/news/edit.rhtml
create mode 100644 rest_sys/app/views/news/index.rhtml
create mode 100644 rest_sys/app/views/news/show.rhtml
create mode 100644 rest_sys/app/views/projects/_edit.rhtml
create mode 100644 rest_sys/app/views/projects/_form.rhtml
create mode 100644 rest_sys/app/views/projects/activity.rhtml
create mode 100644 rest_sys/app/views/projects/add.rhtml
create mode 100644 rest_sys/app/views/projects/add_document.rhtml
create mode 100644 rest_sys/app/views/projects/add_file.rhtml
create mode 100644 rest_sys/app/views/projects/add_issue.rhtml
create mode 100644 rest_sys/app/views/projects/add_issue_category.rhtml
create mode 100644 rest_sys/app/views/projects/add_news.rhtml
create mode 100644 rest_sys/app/views/projects/add_version.rhtml
create mode 100644 rest_sys/app/views/projects/calendar.rhtml
create mode 100644 rest_sys/app/views/projects/changelog.rhtml
create mode 100644 rest_sys/app/views/projects/destroy.rhtml
create mode 100644 rest_sys/app/views/projects/gantt.rfpdf
create mode 100644 rest_sys/app/views/projects/gantt.rhtml
create mode 100644 rest_sys/app/views/projects/list.rhtml
create mode 100644 rest_sys/app/views/projects/list_documents.rhtml
create mode 100644 rest_sys/app/views/projects/list_files.rhtml
create mode 100644 rest_sys/app/views/projects/list_members.rhtml
create mode 100644 rest_sys/app/views/projects/move_issues.rhtml
create mode 100644 rest_sys/app/views/projects/roadmap.rhtml
create mode 100644 rest_sys/app/views/projects/settings.rhtml
create mode 100644 rest_sys/app/views/projects/settings/_boards.rhtml
create mode 100644 rest_sys/app/views/projects/settings/_issue_categories.rhtml
create mode 100644 rest_sys/app/views/projects/settings/_members.rhtml
create mode 100644 rest_sys/app/views/projects/settings/_modules.rhtml
create mode 100644 rest_sys/app/views/projects/settings/_repository.rhtml
create mode 100644 rest_sys/app/views/projects/settings/_versions.rhtml
create mode 100644 rest_sys/app/views/projects/settings/_wiki.rhtml
create mode 100644 rest_sys/app/views/projects/show.rhtml
create mode 100644 rest_sys/app/views/queries/_columns.rhtml
create mode 100644 rest_sys/app/views/queries/_filters.rhtml
create mode 100644 rest_sys/app/views/queries/_form.rhtml
create mode 100644 rest_sys/app/views/queries/edit.rhtml
create mode 100644 rest_sys/app/views/queries/index.rhtml
create mode 100644 rest_sys/app/views/queries/new.rhtml
create mode 100644 rest_sys/app/views/reports/_details.rhtml
create mode 100644 rest_sys/app/views/reports/_simple.rhtml
create mode 100644 rest_sys/app/views/reports/issue_report.rhtml
create mode 100644 rest_sys/app/views/reports/issue_report_details.rhtml
create mode 100644 rest_sys/app/views/repositories/_dir_list.rhtml
create mode 100644 rest_sys/app/views/repositories/_dir_list_content.rhtml
create mode 100644 rest_sys/app/views/repositories/_navigation.rhtml
create mode 100644 rest_sys/app/views/repositories/_revisions.rhtml
create mode 100644 rest_sys/app/views/repositories/annotate.rhtml
create mode 100644 rest_sys/app/views/repositories/browse.rhtml
create mode 100644 rest_sys/app/views/repositories/changes.rhtml
create mode 100644 rest_sys/app/views/repositories/diff.rhtml
create mode 100644 rest_sys/app/views/repositories/entry.rhtml
create mode 100644 rest_sys/app/views/repositories/revision.rhtml
create mode 100644 rest_sys/app/views/repositories/revisions.rhtml
create mode 100644 rest_sys/app/views/repositories/show.rhtml
create mode 100644 rest_sys/app/views/repositories/stats.rhtml
create mode 100644 rest_sys/app/views/roles/_form.rhtml
create mode 100644 rest_sys/app/views/roles/edit.rhtml
create mode 100644 rest_sys/app/views/roles/list.rhtml
create mode 100644 rest_sys/app/views/roles/new.rhtml
create mode 100644 rest_sys/app/views/roles/report.rhtml
create mode 100644 rest_sys/app/views/roles/workflow.rhtml
create mode 100644 rest_sys/app/views/search/index.rhtml
create mode 100644 rest_sys/app/views/settings/edit.rhtml
create mode 100644 rest_sys/app/views/settings/plugin.rhtml
create mode 100644 rest_sys/app/views/timelog/_report_criteria.rhtml
create mode 100644 rest_sys/app/views/timelog/details.rhtml
create mode 100644 rest_sys/app/views/timelog/edit.rhtml
create mode 100644 rest_sys/app/views/timelog/report.rhtml
create mode 100644 rest_sys/app/views/trackers/_form.rhtml
create mode 100644 rest_sys/app/views/trackers/edit.rhtml
create mode 100644 rest_sys/app/views/trackers/list.rhtml
create mode 100644 rest_sys/app/views/trackers/new.rhtml
create mode 100644 rest_sys/app/views/users/_form.rhtml
create mode 100644 rest_sys/app/views/users/_memberships.rhtml
create mode 100644 rest_sys/app/views/users/add.rhtml
create mode 100644 rest_sys/app/views/users/edit.rhtml
create mode 100644 rest_sys/app/views/users/list.rhtml
create mode 100644 rest_sys/app/views/versions/_form.rhtml
create mode 100644 rest_sys/app/views/versions/_issue_counts.rhtml
create mode 100644 rest_sys/app/views/versions/_overview.rhtml
create mode 100644 rest_sys/app/views/versions/edit.rhtml
create mode 100644 rest_sys/app/views/versions/show.rhtml
create mode 100644 rest_sys/app/views/welcome/index.rhtml
create mode 100644 rest_sys/app/views/wiki/_content.rhtml
create mode 100644 rest_sys/app/views/wiki/_sidebar.rhtml
create mode 100644 rest_sys/app/views/wiki/diff.rhtml
create mode 100644 rest_sys/app/views/wiki/edit.rhtml
create mode 100644 rest_sys/app/views/wiki/export.rhtml
create mode 100644 rest_sys/app/views/wiki/export_multiple.rhtml
create mode 100644 rest_sys/app/views/wiki/history.rhtml
create mode 100644 rest_sys/app/views/wiki/rename.rhtml
create mode 100644 rest_sys/app/views/wiki/show.rhtml
create mode 100644 rest_sys/app/views/wiki/special_date_index.rhtml
create mode 100644 rest_sys/app/views/wiki/special_page_index.rhtml
create mode 100644 rest_sys/app/views/wikis/destroy.rhtml
create mode 100644 rest_sys/config/boot.rb
create mode 100644 rest_sys/config/database.yml.example
create mode 100644 rest_sys/config/environment.rb
create mode 100644 rest_sys/config/environments/demo.rb
create mode 100644 rest_sys/config/environments/development.rb
create mode 100644 rest_sys/config/environments/production.rb
create mode 100644 rest_sys/config/environments/test.rb
create mode 100644 rest_sys/config/environments/test_oracle.rb
create mode 100644 rest_sys/config/environments/test_pgsql.rb
create mode 100644 rest_sys/config/environments/test_sqlserver.rb
create mode 100644 rest_sys/config/routes.rb
create mode 100644 rest_sys/config/settings.yml
create mode 100644 rest_sys/db/migrate/001_setup.rb
create mode 100644 rest_sys/db/migrate/002_issue_move.rb
create mode 100644 rest_sys/db/migrate/003_issue_add_note.rb
create mode 100644 rest_sys/db/migrate/004_export_pdf.rb
create mode 100644 rest_sys/db/migrate/005_issue_start_date.rb
create mode 100644 rest_sys/db/migrate/006_calendar_and_activity.rb
create mode 100644 rest_sys/db/migrate/007_create_journals.rb
create mode 100644 rest_sys/db/migrate/008_create_user_preferences.rb
create mode 100644 rest_sys/db/migrate/009_add_hide_mail_pref.rb
create mode 100644 rest_sys/db/migrate/010_create_comments.rb
create mode 100644 rest_sys/db/migrate/011_add_news_comments_count.rb
create mode 100644 rest_sys/db/migrate/012_add_comments_permissions.rb
create mode 100644 rest_sys/db/migrate/013_create_queries.rb
create mode 100644 rest_sys/db/migrate/014_add_queries_permissions.rb
create mode 100644 rest_sys/db/migrate/015_create_repositories.rb
create mode 100644 rest_sys/db/migrate/016_add_repositories_permissions.rb
create mode 100644 rest_sys/db/migrate/017_create_settings.rb
create mode 100644 rest_sys/db/migrate/018_set_doc_and_files_notifications.rb
create mode 100644 rest_sys/db/migrate/019_add_issue_status_position.rb
create mode 100644 rest_sys/db/migrate/020_add_role_position.rb
create mode 100644 rest_sys/db/migrate/021_add_tracker_position.rb
create mode 100644 rest_sys/db/migrate/022_serialize_possibles_values.rb
create mode 100644 rest_sys/db/migrate/023_add_tracker_is_in_roadmap.rb
create mode 100644 rest_sys/db/migrate/024_add_roadmap_permission.rb
create mode 100644 rest_sys/db/migrate/025_add_search_permission.rb
create mode 100644 rest_sys/db/migrate/026_add_repository_login_and_password.rb
create mode 100644 rest_sys/db/migrate/027_create_wikis.rb
create mode 100644 rest_sys/db/migrate/028_create_wiki_pages.rb
create mode 100644 rest_sys/db/migrate/029_create_wiki_contents.rb
create mode 100644 rest_sys/db/migrate/030_add_projects_feeds_permissions.rb
create mode 100644 rest_sys/db/migrate/031_add_repository_root_url.rb
create mode 100644 rest_sys/db/migrate/032_create_time_entries.rb
create mode 100644 rest_sys/db/migrate/033_add_timelog_permissions.rb
create mode 100644 rest_sys/db/migrate/034_create_changesets.rb
create mode 100644 rest_sys/db/migrate/035_create_changes.rb
create mode 100644 rest_sys/db/migrate/036_add_changeset_commit_date.rb
create mode 100644 rest_sys/db/migrate/037_add_project_identifier.rb
create mode 100644 rest_sys/db/migrate/038_add_custom_field_is_filter.rb
create mode 100644 rest_sys/db/migrate/039_create_watchers.rb
create mode 100644 rest_sys/db/migrate/040_create_changesets_issues.rb
create mode 100644 rest_sys/db/migrate/041_rename_comment_to_comments.rb
create mode 100644 rest_sys/db/migrate/042_create_issue_relations.rb
create mode 100644 rest_sys/db/migrate/043_add_relations_permissions.rb
create mode 100644 rest_sys/db/migrate/044_set_language_length_to_five.rb
create mode 100644 rest_sys/db/migrate/045_create_boards.rb
create mode 100644 rest_sys/db/migrate/046_create_messages.rb
create mode 100644 rest_sys/db/migrate/047_add_boards_permissions.rb
create mode 100644 rest_sys/db/migrate/048_allow_null_version_effective_date.rb
create mode 100644 rest_sys/db/migrate/049_add_wiki_destroy_page_permission.rb
create mode 100644 rest_sys/db/migrate/050_add_wiki_attachments_permissions.rb
create mode 100644 rest_sys/db/migrate/051_add_project_status.rb
create mode 100644 rest_sys/db/migrate/052_add_changes_revision.rb
create mode 100644 rest_sys/db/migrate/053_add_changes_branch.rb
create mode 100644 rest_sys/db/migrate/054_add_changesets_scmid.rb
create mode 100644 rest_sys/db/migrate/055_add_repositories_type.rb
create mode 100644 rest_sys/db/migrate/056_add_repositories_changes_permission.rb
create mode 100644 rest_sys/db/migrate/057_add_versions_wiki_page_title.rb
create mode 100644 rest_sys/db/migrate/058_add_issue_categories_assigned_to_id.rb
create mode 100644 rest_sys/db/migrate/059_add_roles_assignable.rb
create mode 100644 rest_sys/db/migrate/060_change_changesets_committer_limit.rb
create mode 100644 rest_sys/db/migrate/061_add_roles_builtin.rb
create mode 100644 rest_sys/db/migrate/062_insert_builtin_roles.rb
create mode 100644 rest_sys/db/migrate/063_add_roles_permissions.rb
create mode 100644 rest_sys/db/migrate/064_drop_permissions.rb
create mode 100644 rest_sys/db/migrate/065_add_settings_updated_on.rb
create mode 100644 rest_sys/db/migrate/066_add_custom_value_customized_index.rb
create mode 100644 rest_sys/db/migrate/067_create_wiki_redirects.rb
create mode 100644 rest_sys/db/migrate/068_create_enabled_modules.rb
create mode 100644 rest_sys/db/migrate/069_add_issues_estimated_hours.rb
create mode 100644 rest_sys/db/migrate/070_change_attachments_content_type_limit.rb
create mode 100644 rest_sys/db/migrate/071_add_queries_column_names.rb
create mode 100644 rest_sys/db/migrate/072_add_enumerations_position.rb
create mode 100644 rest_sys/db/migrate/073_add_enumerations_is_default.rb
create mode 100644 rest_sys/db/migrate/074_add_auth_sources_tls.rb
create mode 100644 rest_sys/db/migrate/075_add_members_mail_notification.rb
create mode 100644 rest_sys/db/migrate/076_allow_null_position.rb
create mode 100644 rest_sys/db/migrate/077_remove_issue_statuses_html_color.rb
create mode 100644 rest_sys/db/migrate/078_add_custom_fields_position.rb
create mode 100644 rest_sys/db/migrate/079_add_user_preferences_time_zone.rb
create mode 100644 rest_sys/db/migrate/080_add_users_type.rb
create mode 100644 rest_sys/db/migrate/081_create_projects_trackers.rb
create mode 100644 rest_sys/db/migrate/082_add_messages_locked.rb
create mode 100644 rest_sys/db/migrate/083_add_messages_sticky.rb
create mode 100644 rest_sys/db/migrate/084_change_auth_sources_account_limit.rb
create mode 100644 rest_sys/db/migrate/085_add_role_tracker_old_status_index_to_workflows.rb
create mode 100644 rest_sys/db/migrate/086_add_custom_fields_searchable.rb
create mode 100644 rest_sys/doc/CHANGELOG
create mode 100644 rest_sys/doc/COPYING
create mode 100644 rest_sys/doc/INSTALL
create mode 100644 rest_sys/doc/RUNNING_TESTS
create mode 100644 rest_sys/doc/UPGRADING
create mode 100644 rest_sys/extra/sample_plugin/README
create mode 100644 rest_sys/extra/sample_plugin/app/controllers/example_controller.rb
create mode 100644 rest_sys/extra/sample_plugin/app/views/example/say_goodbye.rhtml
create mode 100644 rest_sys/extra/sample_plugin/app/views/example/say_hello.rhtml
create mode 100644 rest_sys/extra/sample_plugin/app/views/settings/_settings.rhtml
create mode 100644 rest_sys/extra/sample_plugin/assets/images/it_works.png
create mode 100644 rest_sys/extra/sample_plugin/assets/stylesheets/example.css
create mode 100644 rest_sys/extra/sample_plugin/db/migrate/001_create_some_models.rb
create mode 100644 rest_sys/extra/sample_plugin/init.rb
create mode 100644 rest_sys/extra/sample_plugin/lang/en.yml
create mode 100644 rest_sys/extra/sample_plugin/lang/fr.yml
create mode 100644 rest_sys/extra/svn/Redmine.pm
create mode 100644 rest_sys/extra/svn/create_views.sql
create mode 100755 rest_sys/extra/svn/reposman.pl
create mode 100755 rest_sys/extra/svn/reposman.rb
create mode 100755 rest_sys/extra/svn/svnserve.wrapper
create mode 100644 rest_sys/files/delete.me
create mode 100644 rest_sys/lang/bg.yml
create mode 100644 rest_sys/lang/cs.yml
create mode 100644 rest_sys/lang/de.yml
create mode 100644 rest_sys/lang/en.yml
create mode 100644 rest_sys/lang/es.yml
create mode 100644 rest_sys/lang/fr.yml
create mode 100644 rest_sys/lang/he.yml
create mode 100644 rest_sys/lang/it.yml
create mode 100644 rest_sys/lang/ja.yml
create mode 100644 rest_sys/lang/ko.yml
create mode 100644 rest_sys/lang/nl.yml
create mode 100644 rest_sys/lang/pl.yml
create mode 100644 rest_sys/lang/pt-br.yml
create mode 100644 rest_sys/lang/pt.yml
create mode 100644 rest_sys/lang/ro.yml
create mode 100644 rest_sys/lang/ru.yml
create mode 100644 rest_sys/lang/sr.yml
create mode 100644 rest_sys/lang/sv.yml
create mode 100644 rest_sys/lang/zh-tw.yml
create mode 100644 rest_sys/lang/zh.yml
create mode 100644 rest_sys/lib/SVG/GPL.txt
create mode 100644 rest_sys/lib/SVG/Graph/Bar.rb
create mode 100644 rest_sys/lib/SVG/Graph/BarBase.rb
create mode 100644 rest_sys/lib/SVG/Graph/BarHorizontal.rb
create mode 100644 rest_sys/lib/SVG/Graph/Graph.rb
create mode 100644 rest_sys/lib/SVG/Graph/Line.rb
create mode 100644 rest_sys/lib/SVG/Graph/Pie.rb
create mode 100644 rest_sys/lib/SVG/Graph/Plot.rb
create mode 100644 rest_sys/lib/SVG/Graph/Schedule.rb
create mode 100644 rest_sys/lib/SVG/Graph/TimeSeries.rb
create mode 100644 rest_sys/lib/SVG/LICENSE.txt
create mode 100644 rest_sys/lib/diff.rb
create mode 100644 rest_sys/lib/redcloth.rb
create mode 100644 rest_sys/lib/redmine.rb
create mode 100644 rest_sys/lib/redmine/access_control.rb
create mode 100644 rest_sys/lib/redmine/helpers/calendar.rb
create mode 100644 rest_sys/lib/redmine/info.rb
create mode 100644 rest_sys/lib/redmine/menu_manager.rb
create mode 100644 rest_sys/lib/redmine/mime_type.rb
create mode 100644 rest_sys/lib/redmine/plugin.rb
create mode 100644 rest_sys/lib/redmine/scm/adapters/abstract_adapter.rb
create mode 100644 rest_sys/lib/redmine/scm/adapters/bazaar_adapter.rb
create mode 100644 rest_sys/lib/redmine/scm/adapters/cvs_adapter.rb
create mode 100644 rest_sys/lib/redmine/scm/adapters/darcs_adapter.rb
create mode 100644 rest_sys/lib/redmine/scm/adapters/mercurial_adapter.rb
create mode 100644 rest_sys/lib/redmine/scm/adapters/subversion_adapter.rb
create mode 100644 rest_sys/lib/redmine/themes.rb
create mode 100644 rest_sys/lib/redmine/version.rb
create mode 100644 rest_sys/lib/redmine/wiki_formatting.rb
create mode 100644 rest_sys/lib/redmine/wiki_formatting/macros.rb
create mode 100644 rest_sys/lib/tasks/deprecated.rake
create mode 100644 rest_sys/lib/tasks/extract_fixtures.rake
create mode 100644 rest_sys/lib/tasks/load_default_data.rake
create mode 100644 rest_sys/lib/tasks/migrate_from_mantis.rake
create mode 100644 rest_sys/lib/tasks/migrate_from_trac.rake
create mode 100644 rest_sys/lib/tasks/migrate_plugins.rake
create mode 100644 rest_sys/log/delete.me
create mode 100644 rest_sys/public/.htaccess
create mode 100644 rest_sys/public/404.html
create mode 100644 rest_sys/public/500.html
create mode 100755 rest_sys/public/dispatch.cgi.example
create mode 100755 rest_sys/public/dispatch.fcgi.example
create mode 100755 rest_sys/public/dispatch.rb.example
create mode 100644 rest_sys/public/favicon.ico
create mode 100644 rest_sys/public/help/wiki_syntax.html
create mode 100644 rest_sys/public/images/1downarrow.png
create mode 100644 rest_sys/public/images/1uparrow.png
create mode 100644 rest_sys/public/images/22x22/authent.png
create mode 100644 rest_sys/public/images/22x22/comment.png
create mode 100644 rest_sys/public/images/22x22/file.png
create mode 100644 rest_sys/public/images/22x22/info.png
create mode 100644 rest_sys/public/images/22x22/notifications.png
create mode 100644 rest_sys/public/images/22x22/options.png
create mode 100644 rest_sys/public/images/22x22/package.png
create mode 100644 rest_sys/public/images/22x22/plugin.png
create mode 100644 rest_sys/public/images/22x22/projects.png
create mode 100644 rest_sys/public/images/22x22/role.png
create mode 100644 rest_sys/public/images/22x22/settings.png
create mode 100644 rest_sys/public/images/22x22/tracker.png
create mode 100644 rest_sys/public/images/22x22/users.png
create mode 100644 rest_sys/public/images/22x22/workflow.png
create mode 100644 rest_sys/public/images/2downarrow.png
create mode 100644 rest_sys/public/images/2uparrow.png
create mode 100644 rest_sys/public/images/32x32/file.png
create mode 100644 rest_sys/public/images/add.png
create mode 100644 rest_sys/public/images/admin.png
create mode 100644 rest_sys/public/images/arrow_bw.png
create mode 100644 rest_sys/public/images/arrow_down.png
create mode 100644 rest_sys/public/images/arrow_from.png
create mode 100644 rest_sys/public/images/arrow_to.png
create mode 100644 rest_sys/public/images/attachment.png
create mode 100644 rest_sys/public/images/calendar.png
create mode 100644 rest_sys/public/images/cancel.png
create mode 100644 rest_sys/public/images/close.png
create mode 100644 rest_sys/public/images/close_hl.png
create mode 100644 rest_sys/public/images/contentbg.png
create mode 100644 rest_sys/public/images/copy.png
create mode 100644 rest_sys/public/images/csv.png
create mode 100644 rest_sys/public/images/delete.png
create mode 100644 rest_sys/public/images/draft.png
create mode 100644 rest_sys/public/images/edit.png
create mode 100644 rest_sys/public/images/expand.png
create mode 100644 rest_sys/public/images/external.png
create mode 100644 rest_sys/public/images/false.png
create mode 100644 rest_sys/public/images/fav.png
create mode 100644 rest_sys/public/images/fav_off.png
create mode 100644 rest_sys/public/images/feed.png
create mode 100644 rest_sys/public/images/file.png
create mode 100644 rest_sys/public/images/folder.png
create mode 100644 rest_sys/public/images/folder_open.png
create mode 100644 rest_sys/public/images/help.png
create mode 100644 rest_sys/public/images/history.png
create mode 100644 rest_sys/public/images/home.png
create mode 100644 rest_sys/public/images/html.png
create mode 100644 rest_sys/public/images/image.png
create mode 100644 rest_sys/public/images/index.png
create mode 100644 rest_sys/public/images/jstoolbar/bt_br.png
create mode 100644 rest_sys/public/images/jstoolbar/bt_code.png
create mode 100644 rest_sys/public/images/jstoolbar/bt_del.png
create mode 100644 rest_sys/public/images/jstoolbar/bt_em.png
create mode 100644 rest_sys/public/images/jstoolbar/bt_heading.png
create mode 100644 rest_sys/public/images/jstoolbar/bt_ins.png
create mode 100644 rest_sys/public/images/jstoolbar/bt_link.png
create mode 100644 rest_sys/public/images/jstoolbar/bt_ol.png
create mode 100644 rest_sys/public/images/jstoolbar/bt_quote.png
create mode 100644 rest_sys/public/images/jstoolbar/bt_strong.png
create mode 100644 rest_sys/public/images/jstoolbar/bt_ul.png
create mode 100644 rest_sys/public/images/loading.gif
create mode 100644 rest_sys/public/images/locked.png
create mode 100644 rest_sys/public/images/mainbg.png
create mode 100644 rest_sys/public/images/milestone.png
create mode 100644 rest_sys/public/images/move.png
create mode 100644 rest_sys/public/images/note.png
create mode 100644 rest_sys/public/images/package.png
create mode 100644 rest_sys/public/images/pdf.png
create mode 100644 rest_sys/public/images/projects.png
create mode 100644 rest_sys/public/images/reload.png
create mode 100644 rest_sys/public/images/save.png
create mode 100644 rest_sys/public/images/sort_asc.png
create mode 100644 rest_sys/public/images/sort_desc.png
create mode 100644 rest_sys/public/images/stats.png
create mode 100644 rest_sys/public/images/sticky.png
create mode 100644 rest_sys/public/images/sub.gif
create mode 100644 rest_sys/public/images/task_done.png
create mode 100644 rest_sys/public/images/task_late.png
create mode 100644 rest_sys/public/images/task_todo.png
create mode 100644 rest_sys/public/images/time.png
create mode 100644 rest_sys/public/images/true.png
create mode 100644 rest_sys/public/images/txt.png
create mode 100644 rest_sys/public/images/unlock.png
create mode 100644 rest_sys/public/images/user.png
create mode 100644 rest_sys/public/images/user_new.png
create mode 100644 rest_sys/public/images/user_page.png
create mode 100644 rest_sys/public/images/users.png
create mode 100644 rest_sys/public/images/warning.png
create mode 100644 rest_sys/public/images/zoom_in.png
create mode 100644 rest_sys/public/images/zoom_in_g.png
create mode 100644 rest_sys/public/images/zoom_out.png
create mode 100644 rest_sys/public/images/zoom_out_g.png
create mode 100644 rest_sys/public/javascripts/application.js
create mode 100644 rest_sys/public/javascripts/calendar/calendar-setup.js
create mode 100644 rest_sys/public/javascripts/calendar/calendar.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-bg.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-cs.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-de.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-en.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-es.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-fr.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-he.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-it.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-ja.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-ko.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-nl.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-pl.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-pt-br.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-pt.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-ro.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-ru.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-sr.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-sv.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-zh-tw.js
create mode 100644 rest_sys/public/javascripts/calendar/lang/calendar-zh.js
create mode 100644 rest_sys/public/javascripts/context_menu.js
create mode 100644 rest_sys/public/javascripts/controls.js
create mode 100644 rest_sys/public/javascripts/dragdrop.js
create mode 100644 rest_sys/public/javascripts/effects.js
create mode 100644 rest_sys/public/javascripts/jstoolbar.js
create mode 100644 rest_sys/public/javascripts/prototype.js
create mode 100644 rest_sys/public/javascripts/select_list_move.js
create mode 100644 rest_sys/public/robots.txt
create mode 100644 rest_sys/public/stylesheets/application.css
create mode 100644 rest_sys/public/stylesheets/calendar.css
create mode 100644 rest_sys/public/stylesheets/context_menu.css
create mode 100644 rest_sys/public/stylesheets/csshover.htc
create mode 100644 rest_sys/public/stylesheets/jstoolbar.css
create mode 100644 rest_sys/public/stylesheets/scm.css
create mode 100644 rest_sys/public/themes/README
create mode 100644 rest_sys/public/themes/alternate/stylesheets/application.css
create mode 100644 rest_sys/public/themes/classic/stylesheets/application.css
create mode 100755 rest_sys/script/about
create mode 100755 rest_sys/script/breakpointer
create mode 100755 rest_sys/script/console
create mode 100755 rest_sys/script/destroy
create mode 100755 rest_sys/script/generate
create mode 100755 rest_sys/script/performance/benchmarker
create mode 100755 rest_sys/script/performance/profiler
create mode 100755 rest_sys/script/plugin
create mode 100755 rest_sys/script/process/reaper
create mode 100755 rest_sys/script/process/spawner
create mode 100755 rest_sys/script/process/spinner
create mode 100755 rest_sys/script/runner
create mode 100755 rest_sys/script/server
create mode 100644 rest_sys/test/fixtures/attachments.yml
create mode 100644 rest_sys/test/fixtures/auth_sources.yml
create mode 100644 rest_sys/test/fixtures/boards.yml
create mode 100644 rest_sys/test/fixtures/changes.yml
create mode 100644 rest_sys/test/fixtures/changesets.yml
create mode 100644 rest_sys/test/fixtures/comments.yml
create mode 100644 rest_sys/test/fixtures/custom_fields.yml
create mode 100644 rest_sys/test/fixtures/custom_fields_projects.yml
create mode 100644 rest_sys/test/fixtures/custom_fields_trackers.yml
create mode 100644 rest_sys/test/fixtures/custom_values.yml
create mode 100644 rest_sys/test/fixtures/documents.yml
create mode 100644 rest_sys/test/fixtures/enabled_modules.yml
create mode 100644 rest_sys/test/fixtures/enumerations.yml
create mode 100644 rest_sys/test/fixtures/files/testfile.txt
create mode 100644 rest_sys/test/fixtures/issue_categories.yml
create mode 100644 rest_sys/test/fixtures/issue_statuses.yml
create mode 100644 rest_sys/test/fixtures/issues.yml
create mode 100644 rest_sys/test/fixtures/journal_details.yml
create mode 100644 rest_sys/test/fixtures/journals.yml
create mode 100644 rest_sys/test/fixtures/mail_handler/add_note_to_issue.txt
create mode 100644 rest_sys/test/fixtures/members.yml
create mode 100644 rest_sys/test/fixtures/messages.yml
create mode 100644 rest_sys/test/fixtures/news.yml
create mode 100644 rest_sys/test/fixtures/projects.yml
create mode 100644 rest_sys/test/fixtures/projects_trackers.yml
create mode 100644 rest_sys/test/fixtures/queries.yml
create mode 100644 rest_sys/test/fixtures/repositories.yml
create mode 100644 rest_sys/test/fixtures/repositories/bazaar_repository.tar.gz
create mode 100644 rest_sys/test/fixtures/repositories/cvs_repository.tar.gz
create mode 100644 rest_sys/test/fixtures/repositories/mercurial_repository.tar.gz
create mode 100644 rest_sys/test/fixtures/repositories/subversion_repository.dump.gz
create mode 100644 rest_sys/test/fixtures/roles.yml
create mode 100644 rest_sys/test/fixtures/time_entries.yml
create mode 100644 rest_sys/test/fixtures/tokens.yml
create mode 100644 rest_sys/test/fixtures/trackers.yml
create mode 100644 rest_sys/test/fixtures/user_preferences.yml
create mode 100644 rest_sys/test/fixtures/users.yml
create mode 100644 rest_sys/test/fixtures/versions.yml
create mode 100644 rest_sys/test/fixtures/wiki_content_versions.yml
create mode 100644 rest_sys/test/fixtures/wiki_contents.yml
create mode 100644 rest_sys/test/fixtures/wiki_pages.yml
create mode 100644 rest_sys/test/fixtures/wikis.yml
create mode 100644 rest_sys/test/fixtures/workflows.yml
create mode 100644 rest_sys/test/functional/account_controller_test.rb
create mode 100644 rest_sys/test/functional/admin_controller_test.rb
create mode 100644 rest_sys/test/functional/application_controller_test.rb
create mode 100644 rest_sys/test/functional/boards_controller_test.rb
create mode 100644 rest_sys/test/functional/issues_controller_test.rb
create mode 100644 rest_sys/test/functional/messages_controller_test.rb
create mode 100644 rest_sys/test/functional/my_controller_test.rb
create mode 100644 rest_sys/test/functional/news_controller_test.rb
create mode 100644 rest_sys/test/functional/projects_controller_test.rb
create mode 100644 rest_sys/test/functional/repositories_controller_test.rb
create mode 100644 rest_sys/test/functional/repositories_mercurial_controller_test.rb
create mode 100644 rest_sys/test/functional/repositories_subversion_controller_test.rb
create mode 100644 rest_sys/test/functional/search_controller_test.rb
create mode 100644 rest_sys/test/functional/sys_api_test.rb
create mode 100644 rest_sys/test/functional/timelog_controller_test.rb
create mode 100644 rest_sys/test/functional/users_controller_test.rb
create mode 100644 rest_sys/test/functional/versions_controller_test.rb
create mode 100644 rest_sys/test/functional/welcome_controller_test.rb
create mode 100644 rest_sys/test/functional/wiki_controller_test.rb
create mode 100644 rest_sys/test/functional/wikis_controller_test.rb
create mode 100644 rest_sys/test/helper_testcase.rb
create mode 100644 rest_sys/test/integration/account_test.rb
create mode 100644 rest_sys/test/integration/admin_test.rb
create mode 100644 rest_sys/test/integration/issues_test.rb
create mode 100644 rest_sys/test/integration/projects_test.rb
create mode 100644 rest_sys/test/test_helper.rb
create mode 100644 rest_sys/test/unit/board_test.rb
create mode 100644 rest_sys/test/unit/calendar_test.rb
create mode 100644 rest_sys/test/unit/changeset_test.rb
create mode 100644 rest_sys/test/unit/comment_test.rb
create mode 100644 rest_sys/test/unit/custom_field_test.rb
create mode 100644 rest_sys/test/unit/custom_value_test.rb
create mode 100644 rest_sys/test/unit/helpers/application_helper_test.rb
create mode 100644 rest_sys/test/unit/helpers/projects_helper_test.rb
create mode 100644 rest_sys/test/unit/issue_category_test.rb
create mode 100644 rest_sys/test/unit/issue_status_test.rb
create mode 100644 rest_sys/test/unit/issue_test.rb
create mode 100644 rest_sys/test/unit/journal_test.rb
create mode 100644 rest_sys/test/unit/mail_handler_test.rb
create mode 100644 rest_sys/test/unit/mailer_test.rb
create mode 100644 rest_sys/test/unit/member_test.rb
create mode 100644 rest_sys/test/unit/message_test.rb
create mode 100644 rest_sys/test/unit/project_test.rb
create mode 100644 rest_sys/test/unit/query_test.rb
create mode 100644 rest_sys/test/unit/repository_bazaar_test.rb
create mode 100644 rest_sys/test/unit/repository_cvs_test.rb
create mode 100644 rest_sys/test/unit/repository_mercurial_test.rb
create mode 100644 rest_sys/test/unit/repository_subversion_test.rb
create mode 100644 rest_sys/test/unit/repository_test.rb
create mode 100644 rest_sys/test/unit/setting_test.rb
create mode 100644 rest_sys/test/unit/token_test.rb
create mode 100644 rest_sys/test/unit/user_preference_test.rb
create mode 100644 rest_sys/test/unit/user_test.rb
create mode 100644 rest_sys/test/unit/watcher_test.rb
create mode 100644 rest_sys/test/unit/wiki_content_test.rb
create mode 100644 rest_sys/test/unit/wiki_page_test.rb
create mode 100644 rest_sys/test/unit/wiki_redirect_test.rb
create mode 100644 rest_sys/test/unit/wiki_test.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/CHANGELOG
create mode 100644 rest_sys/vendor/plugins/actionwebservice/MIT-LICENSE
create mode 100644 rest_sys/vendor/plugins/actionwebservice/README
create mode 100644 rest_sys/vendor/plugins/actionwebservice/Rakefile
create mode 100644 rest_sys/vendor/plugins/actionwebservice/TODO
create mode 100644 rest_sys/vendor/plugins/actionwebservice/init.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/install.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/api.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/base.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/casting.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/client.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/client/base.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/client/soap_client.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/container.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/container/action_controller_container.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/container/delegated_container.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/container/direct_container.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/abstract.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/invocation.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/protocol.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/protocol/abstract.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/protocol/discovery.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/scaffolding.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/struct.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/support/class_inheritable_options.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/support/signature_types.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.erb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.erb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.erb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.erb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/test_invoke.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/action_web_service/version.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/lib/actionwebservice.rb
create mode 100644 rest_sys/vendor/plugins/actionwebservice/setup.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_event/init.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_event/lib/acts_as_event.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_list/README
create mode 100644 rest_sys/vendor/plugins/acts_as_list/init.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_list/test/list_test.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_searchable/init.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_searchable/lib/acts_as_searchable.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_tree/README
create mode 100644 rest_sys/vendor/plugins/acts_as_tree/Rakefile
create mode 100644 rest_sys/vendor/plugins/acts_as_tree/init.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_tree/lib/active_record/acts/tree.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_tree/test/abstract_unit.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_tree/test/acts_as_tree_test.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_tree/test/database.yml
create mode 100644 rest_sys/vendor/plugins/acts_as_tree/test/fixtures/mixin.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_tree/test/fixtures/mixins.yml
create mode 100644 rest_sys/vendor/plugins/acts_as_tree/test/schema.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/CHANGELOG
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/MIT-LICENSE
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/README
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/RUNNING_UNIT_TESTS
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/Rakefile
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/init.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/abstract_unit.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/database.yml
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/fixtures/authors.yml
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/fixtures/landmark.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/fixtures/landmark_versions.yml
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/fixtures/landmarks.yml
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/fixtures/locked_pages.yml
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/fixtures/locked_pages_revisions.yml
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/fixtures/migrations/1_add_versioned_tables.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/fixtures/page.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/fixtures/page_versions.yml
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/fixtures/pages.yml
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/fixtures/widget.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/migration_test.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/schema.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_versioned/test/versioned_test.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_watchable/init.rb
create mode 100644 rest_sys/vendor/plugins/acts_as_watchable/lib/acts_as_watchable.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/CHANGELOG
create mode 100644 rest_sys/vendor/plugins/classic_pagination/README
create mode 100644 rest_sys/vendor/plugins/classic_pagination/Rakefile
create mode 100644 rest_sys/vendor/plugins/classic_pagination/init.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/install.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/lib/pagination.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/lib/pagination_helper.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/companies.yml
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/company.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/developer.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/developers.yml
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/developers_projects.yml
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/project.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/projects.yml
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/replies.yml
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/reply.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/schema.sql
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/topic.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/fixtures/topics.yml
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/helper.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/pagination_helper_test.rb
create mode 100644 rest_sys/vendor/plugins/classic_pagination/test/pagination_test.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/FOLDERS
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/LICENSE
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/README
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/bin/coderay
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/bin/coderay_stylesheet
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/duo.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoder.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/_map.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/count.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/debug.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/div.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/html.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/html/css.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/html/numerization.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/html/output.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/null.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/page.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/span.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/statistic.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/text.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/tokens.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/xml.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/encoders/yaml.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/helpers/file_type.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/helpers/gzip_simple.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/helpers/plugin.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/helpers/word_list.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanner.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/_map.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/c.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/debug.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/delphi.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/html.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/javascript.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/nitro_xhtml.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/plaintext.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/rhtml.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/ruby.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/ruby/patterns.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/scheme.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/scanners/xml.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/style.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/styles/_map.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/styles/cycnus.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/styles/murphy.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/token_classes.rb
create mode 100644 rest_sys/vendor/plugins/coderay-0.7.6.227/lib/coderay/tokens.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/CHANGELOG
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/MIT-LICENSE
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/README
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/Rakefile
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/classes/ActionController/Filters/ClassMethods.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/classes/ActionMailer/Base.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/classes/ActionView/Base.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/classes/ActionView/Helpers/DateHelper.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/classes/ActionView/Helpers/InstanceTag.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/classes/ActiveRecord/Errors.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/classes/ActiveRecord/Validations/ClassMethods.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/classes/GLoc.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/classes/GLoc/ClassMethods.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/classes/GLoc/Helpers.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/classes/GLoc/InstanceMethods.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/created.rid
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/files/CHANGELOG.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/files/README.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/files/lib/gloc-helpers_rb.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/files/lib/gloc-internal_rb.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/files/lib/gloc-rails-text_rb.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/files/lib/gloc-rails_rb.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/files/lib/gloc-ruby_rb.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/files/lib/gloc-version_rb.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/files/lib/gloc_rb.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/fr_class_index.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/fr_file_index.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/fr_method_index.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/index.html
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/doc/rdoc-style.css
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/init.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/lib/gloc-config.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/lib/gloc-dev.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/lib/gloc-helpers.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/lib/gloc-internal.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/lib/gloc-rails-text.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/lib/gloc-rails.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/lib/gloc-ruby.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/lib/gloc-version.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/lib/gloc.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/tasks/gloc.rake
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/test/gloc_rails_test.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/test/gloc_test.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/test/lang/en.yaml
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/test/lang/en_AU.yaml
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/test/lang/ja.yml
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/test/lang2/en.yml
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/test/lang2/ja.yaml
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/test/lib/rails-string_ext.rb
create mode 100644 rest_sys/vendor/plugins/gloc-1.1.0/test/lib/rails-time_ext.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/CHANGELOG
create mode 100644 rest_sys/vendor/plugins/rfpdf/MIT-LICENSE
create mode 100644 rest_sys/vendor/plugins/rfpdf/README
create mode 100644 rest_sys/vendor/plugins/rfpdf/init.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/lib/rfpdf.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/lib/rfpdf/bookmark.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/lib/rfpdf/chinese.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/lib/rfpdf/errors.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/lib/rfpdf/fpdf.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/lib/rfpdf/fpdf_eps.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/lib/rfpdf/japanese.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/lib/rfpdf/korean.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/lib/rfpdf/makefont.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/lib/rfpdf/rfpdf.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/lib/rfpdf/view.rb
create mode 100644 rest_sys/vendor/plugins/rfpdf/test/test_helper.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/COPYING
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/ChangeLog
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/LICENCE
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/README
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ber.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/dataset.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/entry.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/filter.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/pdu.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/psw.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldif.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/tests/testber.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/tests/testdata.ldif
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/tests/testem.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/tests/testfilter.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/tests/testldap.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/tests/testldif.rb
create mode 100644 rest_sys/vendor/plugins/ruby-net-ldap-0.0.4/tests/testpsw.rb
diff --git a/rest_sys/Rakefile b/rest_sys/Rakefile
new file mode 100644
index 000000000..cffd19f0c
--- /dev/null
+++ b/rest_sys/Rakefile
@@ -0,0 +1,10 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake.
+
+require(File.join(File.dirname(__FILE__), 'config', 'boot'))
+
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+require 'tasks/rails'
\ No newline at end of file
diff --git a/rest_sys/app/apis/sys_api.rb b/rest_sys/app/apis/sys_api.rb
new file mode 100644
index 000000000..f52f9e7ef
--- /dev/null
+++ b/rest_sys/app/apis/sys_api.rb
@@ -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 => [:string, :string],
+ :returns => [:int]
+end
diff --git a/rest_sys/app/controllers/account_controller.rb b/rest_sys/app/controllers/account_controller.rb
new file mode 100644
index 000000000..a1cbf5ffd
--- /dev/null
+++ b/rest_sys/app/controllers/account_controller.rb
@@ -0,0 +1,175 @@
+# 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 AccountController < ApplicationController
+ layout 'base'
+ helper :custom_fields
+ include CustomFieldsHelper
+
+ # prevents login action to be filtered by check_if_login_required application scope filter
+ skip_before_filter :check_if_login_required, :only => [:login, :lost_password, :register, :activate]
+
+ # Show user's account
+ def show
+ @user = User.find_active(params[:id])
+ @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? || (User.current.role_for_project(membership.project))
+ end
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+
+ # Login request and validation
+ def login
+ if request.get?
+ # Logout user
+ self.logged_user = nil
+ else
+ # Authenticate user
+ user = User.try_to_login(params[:login], params[:password])
+ if user
+ self.logged_user = user
+ # generate a key and set cookie if autologin
+ if params[:autologin] && Setting.autologin?
+ token = Token.create(:user => user, :action => 'autologin')
+ cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now }
+ end
+ redirect_back_or_default :controller => 'my', :action => 'page'
+ else
+ flash.now[:error] = l(:notice_account_invalid_creditentials)
+ end
+ end
+ end
+
+ # Log out current user and redirect to welcome page
+ def logout
+ cookies.delete :autologin
+ Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) if User.current.logged?
+ self.logged_user = nil
+ redirect_to home_url
+ end
+
+ # Enable user to choose a new password
+ def lost_password
+ redirect_to(home_url) && return unless Setting.lost_password?
+ if params[:token]
+ @token = Token.find_by_action_and_value("recovery", params[:token])
+ redirect_to(home_url) && return unless @token and !@token.expired?
+ @user = @token.user
+ if request.post?
+ @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
+ if @user.save
+ @token.destroy
+ flash[:notice] = l(:notice_account_password_updated)
+ redirect_to :action => 'login'
+ return
+ end
+ end
+ render :template => "account/password_recovery"
+ return
+ else
+ if request.post?
+ user = User.find_by_mail(params[:mail])
+ # user not found in db
+ flash.now[:error] = l(:notice_account_unknown_email) and return unless user
+ # user uses an external authentification
+ flash.now[:error] = l(:notice_can_t_change_password) and return if user.auth_source_id
+ # create a new token for password recovery
+ token = Token.new(:user => user, :action => "recovery")
+ if token.save
+ Mailer.deliver_lost_password(token)
+ flash[:notice] = l(:notice_account_lost_email_sent)
+ redirect_to :action => 'login'
+ return
+ end
+ end
+ end
+ end
+
+ # User self-registration
+ def register
+ redirect_to(home_url) && return unless Setting.self_registration?
+ if request.get?
+ @user = User.new(:language => Setting.default_language)
+ @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
+ else
+ @user = User.new(params[:user])
+ @user.admin = false
+ @user.login = params[:user][:login]
+ @user.status = User::STATUS_REGISTERED
+ @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
+ @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x,
+ :customized => @user,
+ :value => (params["custom_fields"] ? params["custom_fields"][x.id.to_s] : nil)) }
+ @user.custom_values = @custom_values
+ case Setting.self_registration
+ when '1'
+ # Email activation
+ token = Token.new(:user => @user, :action => "register")
+ if @user.save and token.save
+ Mailer.deliver_register(token)
+ flash[:notice] = l(:notice_account_register_done)
+ redirect_to :action => 'login'
+ end
+ when '3'
+ # Automatic activation
+ @user.status = User::STATUS_ACTIVE
+ if @user.save
+ flash[:notice] = l(:notice_account_activated)
+ redirect_to :action => 'login'
+ end
+ else
+ # Manual activation by the administrator
+ if @user.save
+ # Sends an email to the administrators
+ Mailer.deliver_account_activation_request(@user)
+ flash[:notice] = l(:notice_account_pending)
+ redirect_to :action => 'login'
+ end
+ end
+ end
+ end
+
+ # Token based account activation
+ def activate
+ redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
+ token = Token.find_by_action_and_value('register', params[:token])
+ redirect_to(home_url) && return unless token and !token.expired?
+ user = token.user
+ redirect_to(home_url) && return unless user.status == User::STATUS_REGISTERED
+ user.status = User::STATUS_ACTIVE
+ if user.save
+ token.destroy
+ flash[:notice] = l(:notice_account_activated)
+ end
+ redirect_to :action => 'login'
+ end
+
+private
+ def logged_user=(user)
+ if user && user.is_a?(User)
+ User.current = user
+ session[:user_id] = user.id
+ else
+ User.current = User.anonymous
+ session[:user_id] = nil
+ end
+ end
+end
diff --git a/rest_sys/app/controllers/admin_controller.rb b/rest_sys/app/controllers/admin_controller.rb
new file mode 100644
index 000000000..5ad3d696b
--- /dev/null
+++ b/rest_sys/app/controllers/admin_controller.rb
@@ -0,0 +1,82 @@
+# 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 AdminController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+
+ helper :sort
+ include SortHelper
+
+ def index
+ end
+
+ def projects
+ sort_init 'name', 'asc'
+ sort_update
+
+ @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,
+ 25,
+ params['page']
+ @projects = Project.find :all, :order => sort_clause,
+ :conditions => conditions,
+ :limit => @project_pages.items_per_page,
+ :offset => @project_pages.current.offset
+
+ render :action => "projects", :layout => false if request.xhr?
+ end
+
+ def mail_options
+ @notifiables = %w(issue_added issue_updated news_added document_added file_added message_posted)
+ if request.post?
+ settings = (params[:settings] || {}).dup.symbolize_keys
+ settings[:notified_events] ||= []
+ settings.each { |name, value| Setting[name] = value }
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :controller => 'admin', :action => 'mail_options'
+ end
+ end
+
+ def test_email
+ raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
+ # Force ActionMailer to raise delivery errors so we can catch it
+ ActionMailer::Base.raise_delivery_errors = true
+ begin
+ @test = Mailer.deliver_test(User.current)
+ flash[:notice] = l(:notice_email_sent, User.current.mail)
+ rescue Exception => e
+ flash[:error] = l(:notice_email_error, e.message)
+ end
+ ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
+ redirect_to :action => 'mail_options'
+ end
+
+ def info
+ @db_adapter_name = ActiveRecord::Base.connection.adapter_name
+ @flags = {
+ :default_admin_changed => User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?,
+ :file_repository_writable => File.writable?(Attachment.storage_path),
+ :rmagick_available => Object.const_defined?(:Magick)
+ }
+ @plugins = Redmine::Plugin.registered_plugins
+ end
+end
diff --git a/rest_sys/app/controllers/application.rb b/rest_sys/app/controllers/application.rb
new file mode 100644
index 000000000..ad86b6b33
--- /dev/null
+++ b/rest_sys/app/controllers/application.rb
@@ -0,0 +1,179 @@
+# 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 ApplicationController < ActionController::Base
+ before_filter :user_setup, :check_if_login_required, :set_localization
+ filter_parameter_logging :password
+
+ REDMINE_SUPPORTED_SCM.each do |scm|
+ require_dependency "repository/#{scm.underscore}"
+ end
+
+ def current_role
+ @current_role ||= User.current.role_for_project(@project)
+ end
+
+ def user_setup
+ Setting.check_cache
+ if session[:user_id]
+ # existing session
+ User.current = User.find(session[:user_id])
+ elsif cookies[:autologin] && Setting.autologin?
+ # auto-login feature
+ User.current = User.find_by_autologin_key(cookies[:autologin])
+ elsif params[:key] && accept_key_auth_actions.include?(params[:action])
+ # RSS key authentication
+ User.current = User.find_by_rss_key(params[:key])
+ else
+ User.current = User.anonymous
+ end
+ end
+
+ # check if login is globally required to access the application
+ def check_if_login_required
+ # no check needed if user is already logged in
+ return true if User.current.logged?
+ require_login if Setting.login_required?
+ end
+
+ def set_localization
+ lang = begin
+ if !User.current.language.blank? and GLoc.valid_languages.include? User.current.language.to_sym
+ User.current.language
+ elsif request.env['HTTP_ACCEPT_LANGUAGE']
+ accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
+ if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym
+ accept_lang
+ end
+ end
+ rescue
+ nil
+ end || Setting.default_language
+ set_language_if_valid(lang)
+ end
+
+ def require_login
+ if !User.current.logged?
+ store_location
+ redirect_to :controller => "account", :action => "login"
+ return false
+ end
+ true
+ end
+
+ def require_admin
+ return unless require_login
+ if !User.current.admin?
+ render_403
+ return false
+ end
+ true
+ end
+
+ # Authorize the user for the requested action
+ def authorize(ctrl = params[:controller], action = params[:action])
+ allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project)
+ allowed ? true : (User.current.logged? ? render_403 : require_login)
+ 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? || User.current.member_of?(@project) || User.current.admin?
+ User.current.logged? ? render_403 : require_login
+ end
+
+ # store current uri in session.
+ # return to this location by calling redirect_back_or_default
+ def store_location
+ session[:return_to_params] = params
+ end
+
+ # move to the last store_location call or to the passed default one
+ def redirect_back_or_default(default)
+ if session[:return_to_params].nil?
+ redirect_to default
+ else
+ redirect_to session[:return_to_params]
+ session[:return_to_params] = nil
+ end
+ end
+
+ def render_403
+ @project = nil
+ render :template => "common/403", :layout => !request.xhr?, :status => 403
+ return false
+ end
+
+ def render_404
+ render :template => "common/404", :layout => !request.xhr?, :status => 404
+ return false
+ end
+
+ def render_feed(items, options={})
+ @items = items || []
+ @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
+ @title = options[:title] || Setting.app_title
+ render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
+ end
+
+ def self.accept_key_auth(*actions)
+ actions = actions.flatten.map(&:to_s)
+ write_inheritable_attribute('accept_key_auth_actions', actions)
+ end
+
+ def accept_key_auth_actions
+ self.class.read_inheritable_attribute('accept_key_auth_actions') || []
+ end
+
+ # TODO: move to model
+ def attach_files(obj, files)
+ attachments = []
+ if files && files.is_a?(Array)
+ files.each do |file|
+ next unless file.size > 0
+ a = Attachment.create(:container => obj, :file => file, :author => User.current)
+ attachments << a unless a.new_record?
+ end
+ end
+ attachments
+ end
+
+ # qvalues http header parser
+ # code taken from webrick
+ def parse_qvalues(value)
+ tmp = []
+ if value
+ parts = value.split(/,\s*/)
+ parts.each {|part|
+ if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
+ val = m[1]
+ q = (m[2] or 1).to_f
+ tmp.push([val, q])
+ end
+ }
+ tmp = tmp.sort_by{|val, q| -q}
+ tmp.collect!{|val, q| val}
+ end
+ return tmp
+ end
+end
diff --git a/rest_sys/app/controllers/attachments_controller.rb b/rest_sys/app/controllers/attachments_controller.rb
new file mode 100644
index 000000000..0913de529
--- /dev/null
+++ b/rest_sys/app/controllers/attachments_controller.rb
@@ -0,0 +1,39 @@
+# 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
+
+ def download
+ # images are sent inline
+ send_file @attachment.diskfile, :filename => @attachment.filename,
+ :type => @attachment.content_type,
+ :disposition => (@attachment.image? ? 'inline' : 'attachment')
+ rescue
+ # in case the disk file was deleted
+ render_404
+ end
+
+private
+ def find_project
+ @attachment = Attachment.find(params[:id])
+ @project = @attachment.project
+ rescue
+ render_404
+ end
+end
diff --git a/rest_sys/app/controllers/auth_sources_controller.rb b/rest_sys/app/controllers/auth_sources_controller.rb
new file mode 100644
index 000000000..b830f1970
--- /dev/null
+++ b/rest_sys/app/controllers/auth_sources_controller.rb
@@ -0,0 +1,83 @@
+# 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 AuthSourcesController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+
+ def index
+ list
+ render :action => 'list' unless request.xhr?
+ end
+
+ # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
+ verify :method => :post, :only => [ :destroy, :create, :update ],
+ :redirect_to => { :action => :list }
+
+ def list
+ @auth_source_pages, @auth_sources = paginate :auth_sources, :per_page => 10
+ render :action => "list", :layout => false if request.xhr?
+ end
+
+ def new
+ @auth_source = AuthSourceLdap.new
+ end
+
+ def create
+ @auth_source = AuthSourceLdap.new(params[:auth_source])
+ if @auth_source.save
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :action => 'list'
+ else
+ render :action => 'new'
+ end
+ end
+
+ def edit
+ @auth_source = AuthSource.find(params[:id])
+ end
+
+ def update
+ @auth_source = AuthSource.find(params[:id])
+ if @auth_source.update_attributes(params[:auth_source])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'list'
+ else
+ render :action => 'edit'
+ end
+ end
+
+ def test_connection
+ @auth_method = AuthSource.find(params[:id])
+ begin
+ @auth_method.test_connection
+ flash[:notice] = l(:notice_successful_connection)
+ rescue => text
+ flash[:error] = "Unable to connect (#{text})"
+ end
+ redirect_to :action => 'list'
+ end
+
+ def destroy
+ @auth_source = AuthSource.find(params[:id])
+ unless @auth_source.users.find(:first)
+ @auth_source.destroy
+ flash[:notice] = l(:notice_successful_delete)
+ end
+ redirect_to :action => 'list'
+ end
+end
diff --git a/rest_sys/app/controllers/boards_controller.rb b/rest_sys/app/controllers/boards_controller.rb
new file mode 100644
index 000000000..200792370
--- /dev/null
+++ b/rest_sys/app/controllers/boards_controller.rb
@@ -0,0 +1,86 @@
+# 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, :authorize
+
+ 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
+ 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 => "#{Message.table_name}.sticky DESC, #{sort_clause}",
+ :include => [:author, {:last_reply => :author}],
+ :limit => @topic_pages.items_per_page,
+ :offset => @topic_pages.current.offset
+ render :action => 'show', :layout => !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
diff --git a/rest_sys/app/controllers/custom_fields_controller.rb b/rest_sys/app/controllers/custom_fields_controller.rb
new file mode 100644
index 000000000..1e1c988d9
--- /dev/null
+++ b/rest_sys/app/controllers/custom_fields_controller.rb
@@ -0,0 +1,87 @@
+# 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 CustomFieldsController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+
+ def index
+ list
+ render :action => 'list' unless request.xhr?
+ end
+
+ def list
+ @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
+ @tab = params[:tab] || 'IssueCustomField'
+ render :action => "list", :layout => false if request.xhr?
+ end
+
+ def new
+ case params[:type]
+ when "IssueCustomField"
+ @custom_field = IssueCustomField.new(params[:custom_field])
+ @custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids]
+ when "UserCustomField"
+ @custom_field = UserCustomField.new(params[:custom_field])
+ when "ProjectCustomField"
+ @custom_field = ProjectCustomField.new(params[:custom_field])
+ else
+ redirect_to :action => 'list'
+ return
+ end
+ if request.post? and @custom_field.save
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :action => 'list', :tab => @custom_field.class.name
+ end
+ @trackers = Tracker.find(:all, :order => 'position')
+ end
+
+ def edit
+ @custom_field = CustomField.find(params[:id])
+ if request.post? and @custom_field.update_attributes(params[:custom_field])
+ if @custom_field.is_a? IssueCustomField
+ @custom_field.trackers = params[:tracker_ids] ? Tracker.find(params[:tracker_ids]) : []
+ end
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'list', :tab => @custom_field.class.name
+ end
+ @trackers = Tracker.find(:all, :order => 'position')
+ end
+
+ def move
+ @custom_field = CustomField.find(params[:id])
+ case params[:position]
+ when 'highest'
+ @custom_field.move_to_top
+ when 'higher'
+ @custom_field.move_higher
+ when 'lower'
+ @custom_field.move_lower
+ when 'lowest'
+ @custom_field.move_to_bottom
+ end if params[:position]
+ redirect_to :action => 'list', :tab => @custom_field.class.name
+ end
+
+ def destroy
+ @custom_field = CustomField.find(params[:id]).destroy
+ redirect_to :action => 'list', :tab => @custom_field.class.name
+ rescue
+ flash[:error] = "Unable to delete custom field"
+ redirect_to :action => 'list'
+ end
+end
diff --git a/rest_sys/app/controllers/documents_controller.rb b/rest_sys/app/controllers/documents_controller.rb
new file mode 100644
index 000000000..104cca10c
--- /dev/null
+++ b/rest_sys/app/controllers/documents_controller.rb
@@ -0,0 +1,65 @@
+# 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 DocumentsController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize
+
+ def show
+ @attachments = @document.attachments.find(:all, :order => "created_on DESC")
+ end
+
+ def edit
+ @categories = Enumeration::get_values('DCAT')
+ if request.post? and @document.update_attributes(params[:document])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'show', :id => @document
+ end
+ end
+
+ def destroy
+ @document.destroy
+ redirect_to :controller => 'projects', :action => 'list_documents', :id => @project
+ end
+
+ def download
+ @attachment = @document.attachments.find(params[:attachment_id])
+ @attachment.increment_download
+ send_file @attachment.diskfile, :filename => @attachment.filename, :type => @attachment.content_type
+ rescue
+ render_404
+ end
+
+ def add_attachment
+ attachments = attach_files(@document, params[:attachments])
+ Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('document_added')
+ redirect_to :action => 'show', :id => @document
+ end
+
+ def destroy_attachment
+ @document.attachments.find(params[:attachment_id]).destroy
+ redirect_to :action => 'show', :id => @document
+ end
+
+private
+ def find_project
+ @document = Document.find(params[:id])
+ @project = @document.project
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+end
diff --git a/rest_sys/app/controllers/enumerations_controller.rb b/rest_sys/app/controllers/enumerations_controller.rb
new file mode 100644
index 000000000..7a7f1685a
--- /dev/null
+++ b/rest_sys/app/controllers/enumerations_controller.rb
@@ -0,0 +1,85 @@
+# 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 EnumerationsController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+
+ def index
+ list
+ render :action => 'list'
+ end
+
+ # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
+ verify :method => :post, :only => [ :destroy, :create, :update ],
+ :redirect_to => { :action => :list }
+
+ def list
+ end
+
+ def new
+ @enumeration = Enumeration.new(:opt => params[:opt])
+ end
+
+ def create
+ @enumeration = Enumeration.new(params[:enumeration])
+ if @enumeration.save
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :action => 'list', :opt => @enumeration.opt
+ else
+ render :action => 'new'
+ end
+ end
+
+ def edit
+ @enumeration = Enumeration.find(params[:id])
+ end
+
+ def update
+ @enumeration = Enumeration.find(params[:id])
+ if @enumeration.update_attributes(params[:enumeration])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'list', :opt => @enumeration.opt
+ else
+ render :action => 'edit'
+ end
+ end
+
+ def move
+ @enumeration = Enumeration.find(params[:id])
+ case params[:position]
+ when 'highest'
+ @enumeration.move_to_top
+ when 'higher'
+ @enumeration.move_higher
+ when 'lower'
+ @enumeration.move_lower
+ when 'lowest'
+ @enumeration.move_to_bottom
+ end if params[:position]
+ redirect_to :action => 'index'
+ end
+
+ def destroy
+ Enumeration.find(params[:id]).destroy
+ flash[:notice] = l(:notice_successful_delete)
+ redirect_to :action => 'list'
+ rescue
+ flash[:error] = "Unable to delete enumeration"
+ redirect_to :action => 'list'
+ end
+end
diff --git a/rest_sys/app/controllers/issue_categories_controller.rb b/rest_sys/app/controllers/issue_categories_controller.rb
new file mode 100644
index 000000000..2c1c6657b
--- /dev/null
+++ b/rest_sys/app/controllers/issue_categories_controller.rb
@@ -0,0 +1,52 @@
+# 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 IssueCategoriesController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize
+
+ verify :method => :post, :only => :destroy
+
+ def edit
+ if request.post? and @category.update_attributes(params[:category])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
+ end
+ end
+
+ def destroy
+ @issue_count = @category.issues.size
+ if @issue_count == 0
+ # No issue assigned to this category
+ @category.destroy
+ redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
+ elsif params[:todo]
+ reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) if params[:todo] == 'reassign'
+ @category.destroy(reassign_to)
+ redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
+ end
+ @categories = @project.issue_categories - [@category]
+ end
+
+private
+ def find_project
+ @category = IssueCategory.find(params[:id])
+ @project = @category.project
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+end
diff --git a/rest_sys/app/controllers/issue_relations_controller.rb b/rest_sys/app/controllers/issue_relations_controller.rb
new file mode 100644
index 000000000..cb0ad552a
--- /dev/null
+++ b/rest_sys/app/controllers/issue_relations_controller.rb
@@ -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
diff --git a/rest_sys/app/controllers/issue_statuses_controller.rb b/rest_sys/app/controllers/issue_statuses_controller.rb
new file mode 100644
index 000000000..d0712e7c3
--- /dev/null
+++ b/rest_sys/app/controllers/issue_statuses_controller.rb
@@ -0,0 +1,85 @@
+# 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 IssueStatusesController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+
+ verify :method => :post, :only => [ :destroy, :create, :update, :move ],
+ :redirect_to => { :action => :list }
+
+ def index
+ list
+ render :action => 'list' unless request.xhr?
+ end
+
+ def list
+ @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
+ render :action => "list", :layout => false if request.xhr?
+ end
+
+ def new
+ @issue_status = IssueStatus.new
+ end
+
+ def create
+ @issue_status = IssueStatus.new(params[:issue_status])
+ if @issue_status.save
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :action => 'list'
+ else
+ render :action => 'new'
+ end
+ end
+
+ def edit
+ @issue_status = IssueStatus.find(params[:id])
+ end
+
+ def update
+ @issue_status = IssueStatus.find(params[:id])
+ if @issue_status.update_attributes(params[:issue_status])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'list'
+ else
+ render :action => 'edit'
+ end
+ end
+
+ def move
+ @issue_status = IssueStatus.find(params[:id])
+ case params[:position]
+ when 'highest'
+ @issue_status.move_to_top
+ when 'higher'
+ @issue_status.move_higher
+ when 'lower'
+ @issue_status.move_lower
+ when 'lowest'
+ @issue_status.move_to_bottom
+ end if params[:position]
+ redirect_to :action => 'list'
+ end
+
+ def destroy
+ IssueStatus.find(params[:id]).destroy
+ redirect_to :action => 'list'
+ rescue
+ flash[:error] = "Unable to delete issue status"
+ redirect_to :action => 'list'
+ end
+end
diff --git a/rest_sys/app/controllers/issues_controller.rb b/rest_sys/app/controllers/issues_controller.rb
new file mode 100644
index 000000000..78bcf76a7
--- /dev/null
+++ b/rest_sys/app/controllers/issues_controller.rb
@@ -0,0 +1,239 @@
+# 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 IssuesController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize, :except => [:index, :changes, :preview]
+ before_filter :find_optional_project, :only => [:index, :changes]
+ accept_key_auth :index, :changes
+
+ cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
+
+ helper :projects
+ include ProjectsHelper
+ helper :custom_fields
+ include CustomFieldsHelper
+ helper :ifpdf
+ include IfpdfHelper
+ helper :issue_relations
+ include IssueRelationsHelper
+ helper :watchers
+ include WatchersHelper
+ helper :attachments
+ include AttachmentsHelper
+ helper :queries
+ helper :sort
+ include SortHelper
+ include IssuesHelper
+
+ def index
+ sort_init "#{Issue.table_name}.id", "desc"
+ sort_update
+ retrieve_query
+ if @query.valid?
+ limit = %w(pdf csv).include?(params[:format]) ? Setting.issues_export_limit.to_i : 25
+ @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
+ @issue_pages = Paginator.new self, @issue_count, limit, params['page']
+ @issues = Issue.find :all, :order => sort_clause,
+ :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
+ :conditions => @query.statement,
+ :limit => limit,
+ :offset => @issue_pages.current.offset
+ respond_to do |format|
+ format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
+ format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
+ format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
+ format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
+ end
+ else
+ # Send html if the query is not valid
+ render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
+ end
+ end
+
+ def changes
+ sort_init "#{Issue.table_name}.id", "desc"
+ sort_update
+ retrieve_query
+ if @query.valid?
+ @changes = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
+ :conditions => @query.statement,
+ :limit => 25,
+ :order => "#{Journal.table_name}.created_on DESC"
+ end
+ @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
+ render :layout => false, :content_type => 'application/atom+xml'
+ end
+
+ def show
+ @custom_values = @issue.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
+ @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
+ @status_options = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)
+ respond_to do |format|
+ format.html { render :template => 'issues/show.rhtml' }
+ format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
+ end
+ end
+
+ def edit
+ @priorities = Enumeration::get_values('IPRI')
+ @custom_values = []
+ if request.get?
+ @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
+ else
+ begin
+ @issue.init_journal(User.current)
+ # Retrieve custom fields and values
+ if params["custom_fields"]
+ @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
+ @issue.custom_values = @custom_values
+ end
+ @issue.attributes = params[:issue]
+ if @issue.save
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
+ end
+ rescue ActiveRecord::StaleObjectError
+ # Optimistic locking exception
+ flash[:error] = l(:notice_locking_conflict)
+ end
+ end
+ end
+
+ def add_note
+ journal = @issue.init_journal(User.current, params[:notes])
+ attachments = attach_files(@issue, params[:attachments])
+ attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
+ if journal.save
+ flash[:notice] = l(:notice_successful_update)
+ Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
+ redirect_to :action => 'show', :id => @issue
+ return
+ end
+ show
+ end
+
+ def change_status
+ @status_options = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)
+ @new_status = IssueStatus.find(params[:new_status_id])
+ if params[:confirm]
+ begin
+ journal = @issue.init_journal(User.current, params[:notes])
+ @issue.status = @new_status
+ if @issue.update_attributes(params[:issue])
+ attachments = attach_files(@issue, params[:attachments])
+ attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
+ # Log time
+ if current_role.allowed_to?(:log_time)
+ @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
+ @time_entry.attributes = params[:time_entry]
+ @time_entry.save
+ end
+
+ flash[:notice] = l(:notice_successful_update)
+ Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
+ redirect_to :action => 'show', :id => @issue
+ end
+ rescue ActiveRecord::StaleObjectError
+ # Optimistic locking exception
+ flash[:error] = l(:notice_locking_conflict)
+ end
+ end
+ @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
+ @activities = Enumeration::get_values('ACTI')
+ end
+
+ def destroy
+ @issue.destroy
+ redirect_to :action => 'index', :project_id => @project
+ end
+
+ def destroy_attachment
+ a = @issue.attachments.find(params[:attachment_id])
+ a.destroy
+ journal = @issue.init_journal(User.current)
+ journal.details << JournalDetail.new(:property => 'attachment',
+ :prop_key => a.id,
+ :old_value => a.filename)
+ journal.save
+ redirect_to :action => 'show', :id => @issue
+ end
+
+ def context_menu
+ @priorities = Enumeration.get_values('IPRI').reverse
+ @statuses = IssueStatus.find(:all, :order => 'position')
+ @allowed_statuses = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)
+ @assignables = @issue.assignable_users
+ @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
+ @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
+ :change_status => User.current.allowed_to?(:change_issue_status, @project),
+ :add => User.current.allowed_to?(:add_issues, @project),
+ :move => User.current.allowed_to?(:move_issues, @project),
+ :copy => (@project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
+ :delete => User.current.allowed_to?(:delete_issues, @project)}
+ render :layout => false
+ end
+
+ def preview
+ issue = Issue.find_by_id(params[:id])
+ @attachements = issue.attachments if issue
+ @text = params[:issue][:description]
+ render :partial => 'common/preview'
+ end
+
+private
+ def find_project
+ @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
+ @project = @issue.project
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+
+ def find_optional_project
+ return true unless params[:project_id]
+ @project = Project.find(params[:project_id])
+ authorize
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+
+ # Retrieve query from session or build a new query
+ def retrieve_query
+ if params[:query_id]
+ @query = Query.find(params[:query_id], :conditions => {:project_id => (@project ? @project.id : nil)})
+ session[:query] = @query
+ else
+ if params[:set_filter] or !session[:query] or session[:query].project != @project
+ # Give it a name, required to be valid
+ @query = Query.new(:name => "_")
+ @query.project = @project
+ if params[:fields] and params[:fields].is_a? Array
+ params[:fields].each do |field|
+ @query.add_filter(field, params[:operators][field], params[:values][field])
+ end
+ else
+ @query.available_filters.keys.each do |field|
+ @query.add_short_filter(field, params[field]) if params[field]
+ end
+ end
+ session[:query] = @query
+ else
+ @query = session[:query]
+ end
+ end
+ end
+end
diff --git a/rest_sys/app/controllers/members_controller.rb b/rest_sys/app/controllers/members_controller.rb
new file mode 100644
index 000000000..a1706e601
--- /dev/null
+++ b/rest_sys/app/controllers/members_controller.rb
@@ -0,0 +1,62 @@
+# 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 MembersController < ApplicationController
+ layout 'base'
+ before_filter :find_member, :except => :new
+ before_filter :find_project, :only => :new
+ before_filter :authorize
+
+ def new
+ @project.members << Member.new(params[:member]) if request.post?
+ respond_to do |format|
+ format.html { redirect_to :action => 'settings', :tab => 'members', :id => @project }
+ format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
+ end
+ end
+
+ def edit
+ if request.post? and @member.update_attributes(params[:member])
+ respond_to do |format|
+ format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
+ format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
+ end
+ end
+ end
+
+ def destroy
+ @member.destroy
+ respond_to do |format|
+ format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
+ format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
+ end
+ end
+
+private
+ def find_project
+ @project = Project.find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+
+ def find_member
+ @member = Member.find(params[:id])
+ @project = @member.project
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+end
diff --git a/rest_sys/app/controllers/messages_controller.rb b/rest_sys/app/controllers/messages_controller.rb
new file mode 100644
index 000000000..8078abf71
--- /dev/null
+++ b/rest_sys/app/controllers/messages_controller.rb
@@ -0,0 +1,98 @@
+# 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_board, :only => :new
+ before_filter :find_message, :except => :new
+ before_filter :authorize
+
+ verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
+
+ helper :attachments
+ include AttachmentsHelper
+
+ # Show a topic and its replies
+ def show
+ @reply = Message.new(:subject => "RE: #{@message.subject}")
+ render :action => "show", :layout => false if request.xhr?
+ end
+
+ # Create a new topic
+ def new
+ @message = Message.new(params[:message])
+ @message.author = User.current
+ @message.board = @board
+ if params[:message] && User.current.allowed_to?(:edit_messages, @project)
+ @message.locked = params[:message]['locked']
+ @message.sticky = params[:message]['sticky']
+ end
+ if request.post? && @message.save
+ attach_files(@message, params[:attachments])
+ redirect_to :action => 'show', :id => @message
+ end
+ end
+
+ # Reply to a topic
+ def reply
+ @reply = Message.new(params[:reply])
+ @reply.author = User.current
+ @reply.board = @board
+ @topic.children << @reply
+ if !@reply.new_record?
+ attach_files(@reply, params[:attachments])
+ end
+ redirect_to :action => 'show', :id => @topic
+ end
+
+ # Edit a message
+ def edit
+ if params[:message] && User.current.allowed_to?(:edit_messages, @project)
+ @message.locked = params[:message]['locked']
+ @message.sticky = params[:message]['sticky']
+ end
+ if request.post? && @message.update_attributes(params[:message])
+ attach_files(@message, params[:attachments])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'show', :id => @topic
+ end
+ end
+
+ # Delete a messages
+ def destroy
+ @message.destroy
+ redirect_to @message.parent.nil? ?
+ { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
+ { :action => 'show', :id => @message.parent }
+ end
+
+private
+ def find_message
+ find_board
+ @message = @board.messages.find(params[:id], :include => :parent)
+ @topic = @message.root
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+
+ def find_board
+ @board = Board.find(params[:board_id], :include => :project)
+ @project = @board.project
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+end
diff --git a/rest_sys/app/controllers/my_controller.rb b/rest_sys/app/controllers/my_controller.rb
new file mode 100644
index 000000000..cb326bc93
--- /dev/null
+++ b/rest_sys/app/controllers/my_controller.rb
@@ -0,0 +1,160 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class MyController < ApplicationController
+ helper :issues
+
+ layout 'base'
+ before_filter :require_login
+
+ BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
+ 'issuesreportedbyme' => :label_reported_issues,
+ 'issueswatched' => :label_watched_issues,
+ 'news' => :label_news_latest,
+ 'calendar' => :label_calendar,
+ 'documents' => :label_document_plural
+ }.freeze
+
+ DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
+ 'right' => ['issuesreportedbyme']
+ }.freeze
+
+ verify :xhr => true,
+ :session => :page_layout,
+ :only => [:add_block, :remove_block, :order_blocks]
+
+ def index
+ page
+ render :action => 'page'
+ end
+
+ # Show user's page
+ def page
+ @user = User.current
+ @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
+ end
+
+ # Edit user's account
+ def account
+ @user = User.current
+ @pref = @user.pref
+ if request.post?
+ @user.attributes = params[:user]
+ @user.mail_notification = (params[:notification_option] == 'all')
+ @user.pref.attributes = params[:pref]
+ @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
+ if @user.save
+ @user.pref.save
+ @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
+ set_language_if_valid @user.language
+ flash[:notice] = l(:notice_account_updated)
+ redirect_to :action => 'account'
+ return
+ end
+ end
+ @notification_options = [[l(:label_user_mail_option_all), 'all'],
+ [l(:label_user_mail_option_none), 'none']]
+ # Only users that belong to more than 1 project can select projects for which they are notified
+ # Note that @user.membership.size would fail since AR ignores :include association option when doing a count
+ @notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
+ @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
+ end
+
+ # Manage user's password
+ def password
+ @user = User.current
+ flash[:error] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id
+ if request.post?
+ if @user.check_password?(params[:password])
+ @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
+ if @user.save
+ flash[:notice] = l(:notice_account_password_updated)
+ redirect_to :action => 'account'
+ end
+ else
+ flash[:error] = l(:notice_account_wrong_password)
+ end
+ end
+ end
+
+ # Create a new feeds key
+ def reset_rss_key
+ if request.post? && User.current.rss_token
+ User.current.rss_token.destroy
+ flash[:notice] = l(:notice_feeds_access_key_reseted)
+ end
+ redirect_to :action => 'account'
+ end
+
+ # User's page layout configuration
+ def page_layout
+ @user = User.current
+ @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
+ session[:page_layout] = @blocks
+ %w(top left right).each {|f| session[:page_layout][f] ||= [] }
+ @block_options = []
+ BLOCKS.each {|k, v| @block_options << [l(v), k]}
+ end
+
+ # Add a block to user's page
+ # The block is added on top of the page
+ # params[:block] : id of the block to add
+ def add_block
+ block = params[:block]
+ render(:nothing => true) and return unless block && (BLOCKS.keys.include? block)
+ @user = User.current
+ # remove if already present in a group
+ %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
+ # add it on top
+ session[:page_layout]['top'].unshift block
+ render :partial => "block", :locals => {:user => @user, :block_name => block}
+ end
+
+ # Remove a block to user's page
+ # params[:block] : id of the block to remove
+ def remove_block
+ block = params[:block]
+ # remove block in all groups
+ %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
+ render :nothing => true
+ end
+
+ # Change blocks order on user's page
+ # params[:group] : group to order (top, left or right)
+ # params[:list-(top|left|right)] : array of block ids of the group
+ def order_blocks
+ group = params[:group]
+ group_items = params["list-#{group}"]
+ if group_items and group_items.is_a? Array
+ # remove group blocks if they are presents in other groups
+ %w(top left right).each {|f|
+ session[:page_layout][f] = (session[:page_layout][f] || []) - group_items
+ }
+ session[:page_layout][group] = group_items
+ end
+ render :nothing => true
+ end
+
+ # Save user's page layout
+ def page_layout_save
+ @user = User.current
+ @user.pref[:my_page_layout] = session[:page_layout] if session[:page_layout]
+ @user.pref.save
+ session[:page_layout] = nil
+ redirect_to :action => 'page'
+ end
+end
diff --git a/rest_sys/app/controllers/news_controller.rb b/rest_sys/app/controllers/news_controller.rb
new file mode 100644
index 000000000..109afe454
--- /dev/null
+++ b/rest_sys/app/controllers/news_controller.rb
@@ -0,0 +1,82 @@
+# 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 NewsController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize, :except => :index
+ before_filter :find_optional_project, :only => :index
+ accept_key_auth :index
+
+ def index
+ @news_pages, @newss = paginate :news,
+ :per_page => 10,
+ :conditions => (@project ? {:project_id => @project.id} : Project.visible_by(User.current)),
+ :include => [:author, :project],
+ :order => "#{News.table_name}.created_on DESC"
+ respond_to do |format|
+ format.html { render :layout => false if request.xhr? }
+ format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
+ end
+ end
+
+ def show
+ end
+
+ def edit
+ if request.post? and @news.update_attributes(params[:news])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'show', :id => @news
+ end
+ end
+
+ def add_comment
+ @comment = Comment.new(params[:comment])
+ @comment.author = User.current
+ if @news.comments << @comment
+ flash[:notice] = l(:label_comment_added)
+ redirect_to :action => 'show', :id => @news
+ else
+ render :action => 'show'
+ end
+ end
+
+ def destroy_comment
+ @news.comments.find(params[:comment_id]).destroy
+ redirect_to :action => 'show', :id => @news
+ end
+
+ def destroy
+ @news.destroy
+ redirect_to :action => 'index', :project_id => @project
+ end
+
+private
+ def find_project
+ @news = News.find(params[:id])
+ @project = @news.project
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+
+ def find_optional_project
+ return true unless params[:project_id]
+ @project = Project.find(params[:project_id])
+ authorize
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+end
diff --git a/rest_sys/app/controllers/projects_controller.rb b/rest_sys/app/controllers/projects_controller.rb
new file mode 100644
index 000000000..7b1e4ef3d
--- /dev/null
+++ b/rest_sys/app/controllers/projects_controller.rb
@@ -0,0 +1,548 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class ProjectsController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :except => [ :index, :list, :add ]
+ before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
+ before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
+ accept_key_auth :activity, :calendar
+
+ cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
+ cache_sweeper :issue_sweeper, :only => [ :add_issue ]
+ cache_sweeper :version_sweeper, :only => [ :add_version ]
+
+ helper :sort
+ include SortHelper
+ helper :custom_fields
+ include CustomFieldsHelper
+ helper :ifpdf
+ include IfpdfHelper
+ helper :issues
+ helper IssuesHelper
+ helper :queries
+ include QueriesHelper
+ helper :repositories
+ include RepositoriesHelper
+ include ProjectsHelper
+
+ def index
+ list
+ render :action => 'list' unless request.xhr?
+ end
+
+ # Lists visible projects
+ def list
+ projects = Project.find :all,
+ :conditions => Project.visible_by(User.current),
+ :include => :parent
+ @project_tree = projects.group_by {|p| p.parent || p}
+ @project_tree.each_key {|p| @project_tree[p] -= [p]}
+ end
+
+ # Add a new project
+ def add
+ @custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
+ @trackers = Tracker.all
+ @root_projects = Project.find(:all,
+ :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
+ :order => 'name')
+ @project = Project.new(params[:project])
+ @project.enabled_module_names = Redmine::AccessControl.available_project_modules
+ if request.get?
+ @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
+ @project.trackers = Tracker.all
+ else
+ @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
+ @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
+ @project.custom_values = @custom_values
+ if @project.save
+ @project.enabled_module_names = params[:enabled_modules]
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :controller => 'admin', :action => 'projects'
+ end
+ end
+ end
+
+ # Show @project
+ def show
+ @custom_values = @project.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
+ @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
+ @subprojects = @project.active_children
+ @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
+ @trackers = @project.trackers
+ @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])
+ @total_hours = @project.time_entries.sum(:hours)
+ @key = User.current.rss_key
+ end
+
+ def settings
+ @root_projects = Project.find(:all,
+ :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
+ :order => 'name')
+ @custom_fields = IssueCustomField.find(:all)
+ @issue_category ||= IssueCategory.new
+ @member ||= @project.members.new
+ @trackers = Tracker.all
+ @custom_values ||= ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
+ @repository ||= @project.repository
+ @wiki ||= @project.wiki
+ end
+
+ # Edit @project
+ def edit
+ if request.post?
+ @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
+ if params[:custom_fields]
+ @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
+ @project.custom_values = @custom_values
+ end
+ @project.attributes = params[:project]
+ if @project.save
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'settings', :id => @project
+ else
+ settings
+ render :action => 'settings'
+ end
+ end
+ end
+
+ def modules
+ @project.enabled_module_names = params[:enabled_modules]
+ redirect_to :action => 'settings', :id => @project, :tab => 'modules'
+ 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
+ def destroy
+ @project_to_destroy = @project
+ if request.post? and params[:confirm]
+ @project_to_destroy.destroy
+ redirect_to :controller => 'admin', :action => 'projects'
+ end
+ # hide project in layout
+ @project = nil
+ end
+
+ # Add a new issue category to @project
+ def add_issue_category
+ @category = @project.issue_categories.build(params[:category])
+ if request.post? and @category.save
+ respond_to do |format|
+ format.html do
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :action => 'settings', :tab => 'categories', :id => @project
+ end
+ format.js do
+ # IE doesn't support the replace_html rjs method for select box options
+ render(:update) {|page| page.replace "issue_category_id",
+ content_tag('select', '' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
+ }
+ end
+ end
+ end
+ end
+
+ # Add a new version to @project
+ def add_version
+ @version = @project.versions.build(params[:version])
+ if request.post? and @version.save
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :action => 'settings', :tab => 'versions', :id => @project
+ end
+ end
+
+ # Add a new document to @project
+ def add_document
+ @document = @project.documents.build(params[:document])
+ if request.post? and @document.save
+ attach_files(@document, params[:attachments])
+ flash[:notice] = l(:notice_successful_create)
+ Mailer.deliver_document_added(@document) if Setting.notified_events.include?('document_added')
+ redirect_to :action => 'list_documents', :id => @project
+ end
+ end
+
+ # Show documents list of @project
+ def list_documents
+ @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
+ documents = @project.documents.find :all, :include => [:attachments, :category]
+ case @sort_by
+ when 'date'
+ @grouped = documents.group_by {|d| d.created_on.to_date }
+ when 'title'
+ @grouped = documents.group_by {|d| d.title.first.upcase}
+ when 'author'
+ @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
+ else
+ @grouped = documents.group_by(&:category)
+ end
+ render :layout => false if request.xhr?
+ end
+
+ # Add a new issue to @project
+ # The new issue will be created from an existing one if copy_from parameter is given
+ def add_issue
+ @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
+ @issue.project = @project
+ @issue.author = User.current
+ @issue.tracker ||= @project.trackers.find(params[:tracker_id])
+
+ default_status = IssueStatus.default
+ unless default_status
+ flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
+ render :nothing => true, :layout => true
+ return
+ end
+ @issue.status = default_status
+ @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker))
+
+ if request.get?
+ @issue.start_date ||= Date.today
+ @custom_values = @issue.custom_values.empty? ?
+ @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
+ @issue.custom_values
+ else
+ requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
+ # Check that the user is allowed to apply the requested status
+ @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
+ @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
+ @issue.custom_values = @custom_values
+ if @issue.save
+ attach_files(@issue, params[:attachments])
+ flash[:notice] = l(:notice_successful_create)
+ Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
+ redirect_to :controller => 'issues', :action => 'index', :project_id => @project
+ return
+ end
+ end
+ @priorities = Enumeration::get_values('IPRI')
+ end
+
+ # Bulk edit issues
+ def bulk_edit_issues
+ if request.post?
+ status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
+ priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
+ assigned_to = params[:assigned_to_id].blank? ? nil : User.find_by_id(params[:assigned_to_id])
+ category = params[:category_id].blank? ? nil : @project.issue_categories.find_by_id(params[:category_id])
+ fixed_version = params[:fixed_version_id].blank? ? nil : @project.versions.find_by_id(params[:fixed_version_id])
+ issues = @project.issues.find_all_by_id(params[:issue_ids])
+ unsaved_issue_ids = []
+ issues.each do |issue|
+ journal = issue.init_journal(User.current, params[:notes])
+ issue.priority = priority if priority
+ issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
+ issue.category = category if category
+ issue.fixed_version = fixed_version if fixed_version
+ issue.start_date = params[:start_date] unless params[:start_date].blank?
+ issue.due_date = params[:due_date] unless params[:due_date].blank?
+ issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
+ # Don't save any change to the issue if the user is not authorized to apply the requested status
+ if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
+ # Send notification for each issue (if changed)
+ Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
+ else
+ # Keep unsaved issue ids to display them in flash error
+ unsaved_issue_ids << issue.id
+ end
+ end
+ if unsaved_issue_ids.empty?
+ flash[:notice] = l(:notice_successful_update) unless issues.empty?
+ else
+ flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, issues.size, '#' + unsaved_issue_ids.join(', #'))
+ end
+ redirect_to :controller => 'issues', :action => 'index', :project_id => @project
+ return
+ end
+ if current_role && User.current.allowed_to?(:change_issue_status, @project)
+ # Find potential statuses the user could be allowed to switch issues to
+ @available_statuses = Workflow.find(:all, :include => :new_status,
+ :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
+ end
+ render :update do |page|
+ page.hide 'query_form'
+ page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form'
+ end
+ end
+
+ def move_issues
+ @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
+ redirect_to :controller => 'issues', :action => 'index', :project_id => @project and return unless @issues
+
+ @projects = []
+ # find projects to which the user is allowed to move the issue
+ if User.current.admin?
+ # admin is allowed to move issues to any active (visible) project
+ @projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
+ else
+ User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:move_issues)}
+ end
+ @target_project = @projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
+ @target_project ||= @project
+ @trackers = @target_project.trackers
+ if request.post?
+ new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
+ unsaved_issue_ids = []
+ @issues.each do |issue|
+ unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
+ end
+ if unsaved_issue_ids.empty?
+ flash[:notice] = l(:notice_successful_update) unless @issues.empty?
+ else
+ flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
+ end
+ redirect_to :controller => 'issues', :action => 'index', :project_id => @project
+ return
+ end
+ render :layout => false if request.xhr?
+ end
+
+ # Add a news to @project
+ def add_news
+ @news = News.new(:project => @project, :author => User.current)
+ if request.post?
+ @news.attributes = params[:news]
+ if @news.save
+ flash[:notice] = l(:notice_successful_create)
+ Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
+ redirect_to :controller => 'news', :action => 'index', :project_id => @project
+ end
+ end
+ end
+
+ def add_file
+ if request.post?
+ @version = @project.versions.find_by_id(params[:version_id])
+ attachments = attach_files(@issue, params[:attachments])
+ Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('file_added')
+ redirect_to :controller => 'projects', :action => 'list_files', :id => @project
+ end
+ @versions = @project.versions.sort
+ end
+
+ def list_files
+ @versions = @project.versions.sort
+ end
+
+ # Show changelog for @project
+ def changelog
+ @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
+ retrieve_selected_tracker_ids(@trackers)
+ @versions = @project.versions.sort
+ end
+
+ def roadmap
+ @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
+ retrieve_selected_tracker_ids(@trackers)
+ @versions = @project.versions.sort
+ @versions = @versions.select {|v| !v.completed? } unless params[:completed]
+ end
+
+ def activity
+ if params[:year] and params[:year].to_i > 1900
+ @year = params[:year].to_i
+ if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
+ @month = params[:month].to_i
+ end
+ end
+ @year ||= Date.today.year
+ @month ||= Date.today.month
+
+ case params[:format]
+ when 'atom'
+ # 30 last days
+ @date_from = Date.today - 30
+ @date_to = Date.today + 1
+ else
+ # current month
+ @date_from = Date.civil(@year, @month, 1)
+ @date_to = @date_from >> 1
+ end
+
+ @event_types = %w(issues news files documents changesets wiki_pages messages)
+ @event_types.delete('wiki_pages') unless @project.wiki
+ @event_types.delete('changesets') unless @project.repository
+ @event_types.delete('messages') unless @project.boards.any?
+ # only show what the user is allowed to view
+ @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
+
+ @scope = @event_types.select {|t| params["show_#{t}"]}
+ # default events if none is specified in parameters
+ @scope = (@event_types - %w(wiki_pages messages))if @scope.empty?
+
+ @events = []
+
+ if @scope.include?('issues')
+ @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
+ @events += @project.issues_status_changes(@date_from, @date_to)
+ end
+
+ if @scope.include?('news')
+ @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
+ end
+
+ if @scope.include?('files')
+ @events += 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 )
+ end
+
+ if @scope.include?('documents')
+ @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
+ @events += 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 )
+ end
+
+ if @scope.include?('wiki_pages')
+ select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
+ "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
+ "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
+ "#{WikiContent.versioned_table_name}.id"
+ joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
+ "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
+ conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
+ @project.id, @date_from, @date_to]
+
+ @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
+ end
+
+ if @scope.include?('changesets')
+ @events += Changeset.find(:all, :include => :repository, :conditions => ["#{Repository.table_name}.project_id = ? AND #{Changeset.table_name}.committed_on BETWEEN ? AND ?", @project.id, @date_from, @date_to])
+ end
+
+ if @scope.include?('messages')
+ @events += Message.find(:all,
+ :include => [:board, :author],
+ :conditions => ["#{Board.table_name}.project_id=? AND #{Message.table_name}.parent_id IS NULL AND #{Message.table_name}.created_on BETWEEN ? AND ?", @project.id, @date_from, @date_to])
+ end
+
+ @events_by_day = @events.group_by(&:event_date)
+
+ respond_to do |format|
+ format.html { render :layout => false if request.xhr? }
+ format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
+ end
+ end
+
+ def calendar
+ @trackers = Tracker.find(:all, :order => 'position')
+ retrieve_selected_tracker_ids(@trackers)
+
+ if params[:year] and params[:year].to_i > 1900
+ @year = params[:year].to_i
+ if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
+ @month = params[:month].to_i
+ end
+ end
+ @year ||= Date.today.year
+ @month ||= Date.today.month
+ @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
+
+ events = []
+ @project.issues_with_subprojects(params[:with_subprojects]) do
+ events += Issue.find(:all,
+ :include => [:tracker, :status, :assigned_to, :priority, :project],
+ :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
+ ) unless @selected_tracker_ids.empty?
+ end
+ events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
+ @calendar.events = events
+
+ render :layout => false if request.xhr?
+ end
+
+ def gantt
+ @trackers = Tracker.find(:all, :order => 'position')
+ retrieve_selected_tracker_ids(@trackers)
+
+ if params[:year] and params[:year].to_i >0
+ @year_from = params[:year].to_i
+ if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
+ @month_from = params[:month].to_i
+ else
+ @month_from = 1
+ end
+ else
+ @month_from ||= Date.today.month
+ @year_from ||= Date.today.year
+ end
+
+ zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
+ @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
+ months = (params[:months] || User.current.pref[:gantt_months]).to_i
+ @months = (months > 0 && months < 25) ? months : 6
+
+ # Save gantt paramters as user preference (zoom and months count)
+ if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
+ User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
+ User.current.preference.save
+ end
+
+ @date_from = Date.civil(@year_from, @month_from, 1)
+ @date_to = (@date_from >> @months) - 1
+
+ @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[:format]=='pdf'
+ @options_for_rfpdf ||= {}
+ @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
+ render :template => "projects/gantt.rfpdf", :layout => false
+ elsif params[:format]=='png' && respond_to?('gantt_image')
+ image = gantt_image(@events, @date_from, @months, @zoom)
+ image.format = 'PNG'
+ send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
+ else
+ render :template => "projects/gantt.rhtml"
+ end
+ end
+
+private
+ # Find project of id params[:id]
+ # if not found, redirect to project list
+ # Used as a before_filter
+ def find_project
+ @project = Project.find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ 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
+end
diff --git a/rest_sys/app/controllers/queries_controller.rb b/rest_sys/app/controllers/queries_controller.rb
new file mode 100644
index 000000000..69bad345a
--- /dev/null
+++ b/rest_sys/app/controllers/queries_controller.rb
@@ -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.
+
+class QueriesController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize
+
+ def index
+ @queries = @project.queries.find(:all,
+ :order => "name ASC",
+ :conditions => ["is_public = ? or user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
+ end
+
+ def new
+ @query = Query.new(params[:query])
+ @query.project = @project
+ @query.user = User.current
+ @query.is_public = false unless current_role.allowed_to?(:manage_public_queries)
+ @query.column_names = nil if params[:default_columns]
+
+ params[:fields].each do |field|
+ @query.add_filter(field, params[:operators][field], params[:values][field])
+ end if params[:fields]
+
+ if request.post? && params[:confirm] && @query.save
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
+ return
+ end
+ render :layout => false if request.xhr?
+ end
+
+ def edit
+ if request.post?
+ @query.filters = {}
+ params[:fields].each do |field|
+ @query.add_filter(field, params[:operators][field], params[:values][field])
+ end if params[:fields]
+ @query.attributes = params[:query]
+ @query.is_public = false unless current_role.allowed_to?(:manage_public_queries)
+ @query.column_names = nil if params[:default_columns]
+
+ if @query.save
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
+ end
+ end
+ end
+
+ def destroy
+ @query.destroy if request.post?
+ redirect_to :controller => 'queries', :project_id => @project
+ end
+
+private
+ def find_project
+ if params[:id]
+ @query = Query.find(params[:id])
+ @project = @query.project
+ render_403 unless @query.editable_by?(User.current)
+ else
+ @project = Project.find(params[:project_id])
+ end
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+end
diff --git a/rest_sys/app/controllers/reports_controller.rb b/rest_sys/app/controllers/reports_controller.rb
new file mode 100644
index 000000000..e18e117a6
--- /dev/null
+++ b/rest_sys/app/controllers/reports_controller.rb
@@ -0,0 +1,213 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class ReportsController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize
+
+ def issue_report
+ @statuses = IssueStatus.find(:all, :order => 'position')
+
+ case params[:detail]
+ when "tracker"
+ @field = "tracker_id"
+ @rows = @project.trackers
+ @data = issues_by_tracker
+ @report_title = l(:field_tracker)
+ 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"
+ @field = "priority_id"
+ @rows = Enumeration::get_values('IPRI')
+ @data = issues_by_priority
+ @report_title = l(:field_priority)
+ render :template => "reports/issue_report_details"
+ when "category"
+ @field = "category_id"
+ @rows = @project.issue_categories
+ @data = issues_by_category
+ @report_title = l(:field_category)
+ render :template => "reports/issue_report_details"
+ when "author"
+ @field = "author_id"
+ @rows = @project.members.collect { |m| m.user }
+ @data = issues_by_author
+ @report_title = l(:field_author)
+ render :template => "reports/issue_report_details"
+ 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
+ @trackers = @project.trackers
+ @versions = @project.versions.sort
+ @priorities = Enumeration::get_values('IPRI')
+ @categories = @project.issue_categories
+ @authors = @project.members.collect { |m| m.user }
+ @subprojects = @project.active_children
+ issues_by_tracker
+ issues_by_version
+ issues_by_priority
+ issues_by_category
+ issues_by_author
+ issues_by_subproject
+
+ render :template => "reports/issue_report"
+ end
+ end
+
+ def delays
+ @trackers = Tracker.find(:all)
+ if request.get?
+ @selected_tracker_ids = @trackers.collect {|t| t.id.to_s }
+ else
+ @selected_tracker_ids = params[:tracker_ids].collect { |id| id.to_i.to_s } if params[:tracker_ids] and params[:tracker_ids].is_a? Array
+ end
+ @selected_tracker_ids ||= []
+ @raw =
+ ActiveRecord::Base.connection.select_all("SELECT datediff( a.created_on, b.created_on ) as delay, count(a.id) as total
+ FROM issue_histories a, issue_histories b, issues i
+ WHERE a.status_id =5
+ AND a.issue_id = b.issue_id
+ AND a.issue_id = i.id
+ AND i.tracker_id in (#{@selected_tracker_ids.join(',')})
+ AND b.id = (
+ SELECT min( c.id )
+ FROM issue_histories c
+ WHERE b.issue_id = c.issue_id )
+ GROUP BY delay") unless @selected_tracker_ids.empty?
+ @raw ||=[]
+
+ @x_from = 0
+ @x_to = 0
+ @y_from = 0
+ @y_to = 0
+ @sum_total = 0
+ @sum_delay = 0
+ @raw.each do |r|
+ @x_to = [r['delay'].to_i, @x_to].max
+ @y_to = [r['total'].to_i, @y_to].max
+ @sum_total = @sum_total + r['total'].to_i
+ @sum_delay = @sum_delay + r['total'].to_i * r['delay'].to_i
+ end
+ end
+
+private
+ # Find project of id params[:id]
+ def find_project
+ @project = Project.find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+
+ def issues_by_tracker
+ @issues_by_tracker ||=
+ ActiveRecord::Base.connection.select_all("select s.id as status_id,
+ s.is_closed as closed,
+ t.id as tracker_id,
+ count(i.id) as total
+ from
+ #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Tracker.table_name} t
+ where
+ i.status_id=s.id
+ and i.tracker_id=t.id
+ and i.project_id=#{@project.id}
+ group by s.id, s.is_closed, t.id")
+ end
+
+ def issues_by_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 ||=
+ ActiveRecord::Base.connection.select_all("select s.id as status_id,
+ s.is_closed as closed,
+ p.id as priority_id,
+ count(i.id) as total
+ from
+ #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Enumeration.table_name} p
+ where
+ i.status_id=s.id
+ and i.priority_id=p.id
+ and i.project_id=#{@project.id}
+ group by s.id, s.is_closed, p.id")
+ end
+
+ def issues_by_category
+ @issues_by_category ||=
+ ActiveRecord::Base.connection.select_all("select s.id as status_id,
+ s.is_closed as closed,
+ c.id as category_id,
+ count(i.id) as total
+ from
+ #{Issue.table_name} i, #{IssueStatus.table_name} s, #{IssueCategory.table_name} c
+ where
+ i.status_id=s.id
+ and i.category_id=c.id
+ and i.project_id=#{@project.id}
+ group by s.id, s.is_closed, c.id")
+ end
+
+ def issues_by_author
+ @issues_by_author ||=
+ ActiveRecord::Base.connection.select_all("select s.id as status_id,
+ s.is_closed as closed,
+ a.id as author_id,
+ count(i.id) as total
+ from
+ #{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a
+ where
+ i.status_id=s.id
+ and i.author_id=a.id
+ and i.project_id=#{@project.id}
+ group by s.id, s.is_closed, a.id")
+ end
+
+ 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
diff --git a/rest_sys/app/controllers/repositories_controller.rb b/rest_sys/app/controllers/repositories_controller.rb
new file mode 100644
index 000000000..ef332eb37
--- /dev/null
+++ b/rest_sys/app/controllers/repositories_controller.rb
@@ -0,0 +1,280 @@
+# 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 'SVG/Graph/Bar'
+require 'SVG/Graph/BarHorizontal'
+require 'digest/sha1'
+
+class ChangesetNotFound < Exception
+end
+
+class RepositoriesController < ApplicationController
+ layout 'base'
+ before_filter :find_repository, :except => :edit
+ before_filter :find_project, :only => :edit
+ before_filter :authorize
+ accept_key_auth :revisions
+
+ def edit
+ @repository = @project.repository
+ if !@repository
+ @repository = Repository.factory(params[:repository_scm])
+ @repository.project = @project
+ end
+ if request.post?
+ @repository.attributes = params[:repository]
+ @repository.save
+ end
+ render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
+ end
+
+ def destroy
+ @repository.destroy
+ redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
+ end
+
+ def show
+ # check if new revisions have been committed in the repository
+ @repository.fetch_changesets if Setting.autofetch_changesets?
+ # 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
+
+ def browse
+ @entries = @repository.entries(@path, @rev)
+ if request.xhr?
+ @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
+ else
+ show_error unless @entries
+ end
+ end
+
+ def changes
+ @entry = @repository.scm.entry(@path, @rev)
+ show_error and return unless @entry
+ @changesets = @repository.changesets_for_path(@path)
+ 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)
+
+ respond_to do |format|
+ format.html { render :layout => false if request.xhr? }
+ format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
+ end
+ end
+
+ def entry
+ @content = @repository.scm.cat(@path, @rev)
+ show_error and return unless @content
+ if 'raw' == params[:format]
+ send_data @content, :filename => @path.split('/').last
+ else
+ # Prevent empty lines when displaying a file with Windows style eol
+ @content.gsub!("\r\n", "\n")
+ end
+ end
+
+ def annotate
+ @annotate = @repository.scm.annotate(@path, @rev)
+ show_error and return if @annotate.nil? || @annotate.empty?
+ end
+
+ def revision
+ @changeset = @repository.changesets.find_by_revision(@rev)
+ raise ChangesetNotFound unless @changeset
+ @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)
+
+ respond_to do |format|
+ format.html
+ format.js {render :layout => false}
+ end
+ rescue ChangesetNotFound
+ show_error
+ end
+
+ def diff
+ @rev_to = params[:rev_to] ? params[:rev_to].to_i : (@rev - 1)
+ @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
+ @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
+
+ # Save diff type as user preference
+ if User.current.logged? && @diff_type != User.current.pref[:diff_type]
+ User.current.pref[:diff_type] = @diff_type
+ User.current.preference.save
+ end
+
+ @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
+ unless read_fragment(@cache_key)
+ @diff = @repository.diff(@path, @rev, @rev_to, @diff_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
+
+private
+ def find_project
+ @project = Project.find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+
+ def find_repository
+ @project = Project.find(params[:id])
+ @repository = @project.repository
+ render_404 and return false unless @repository
+ @path = params[:path].join('/') unless params[:path].nil?
+ @path ||= ''
+ @rev = params[:rev].to_i if params[:rev]
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+
+ def show_error
+ flash.now[:error] = l(:notice_scm_error)
+ render :nothing => true, :layout => true
+ end
+
+ def graph_commits_per_month(repository)
+ @date_to = Date.today
+ @date_from = @date_to << 11
+ @date_from = Date.civil(@date_from.year, @date_from.month, 1)
+ 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, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
+ 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
diff --git a/rest_sys/app/controllers/roles_controller.rb b/rest_sys/app/controllers/roles_controller.rb
new file mode 100644
index 000000000..a8a31cd4d
--- /dev/null
+++ b/rest_sys/app/controllers/roles_controller.rb
@@ -0,0 +1,112 @@
+# 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 RolesController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+
+ verify :method => :post, :only => [ :destroy, :move ],
+ :redirect_to => { :action => :list }
+
+ def index
+ list
+ render :action => 'list' unless request.xhr?
+ end
+
+ def list
+ @role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
+ render :action => "list", :layout => false if request.xhr?
+ end
+
+ def new
+ # Prefills the form with 'Non member' role permissions
+ @role = Role.new(params[:role] || {:permissions => Role.non_member.permissions})
+ if request.post? && @role.save
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :action => 'list'
+ end
+ @permissions = @role.setable_permissions
+ end
+
+ def edit
+ @role = Role.find(params[:id])
+ if request.post? and @role.update_attributes(params[:role])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'list'
+ end
+ @permissions = @role.setable_permissions
+ end
+
+ def destroy
+ @role = Role.find(params[:id])
+ #unless @role.members.empty?
+ # flash[:error] = 'Some members have this role. Can\'t delete it.'
+ #else
+ @role.destroy
+ #end
+ redirect_to :action => 'list'
+ 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
+ @role = Role.find_by_id(params[:role_id])
+ @tracker = Tracker.find_by_id(params[:tracker_id])
+
+ if request.post?
+ Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
+ (params[:issue_status] || []).each { |old, news|
+ news.each { |new|
+ @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new)
+ }
+ }
+ if @role.save
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'workflow', :role_id => @role, :tracker_id => @tracker
+ end
+ end
+ @roles = Role.find(:all, :order => 'builtin, position')
+ @trackers = Tracker.find(:all, :order => 'position')
+ @statuses = IssueStatus.find(:all, :order => 'position')
+ end
+
+ def report
+ @roles = Role.find(:all, :order => 'builtin, position')
+ @permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
+ if request.post?
+ @roles.each do |role|
+ role.permissions = params[:permissions][role.id.to_s]
+ role.save
+ end
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'list'
+ end
+ end
+end
diff --git a/rest_sys/app/controllers/search_controller.rb b/rest_sys/app/controllers/search_controller.rb
new file mode 100644
index 000000000..69e1ee503
--- /dev/null
+++ b/rest_sys/app/controllers/search_controller.rb
@@ -0,0 +1,112 @@
+# 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)
+ @titles_only = !params[:titles_only].nil?
+
+ offset = nil
+ begin; offset = params[:offset].to_time if params[:offset]; rescue; end
+
+ # quick jump to an issue
+ if @question.match(/^#?(\d+)$/) && Issue.find_by_id($1, :include => :project, :conditions => Project.visible_by(User.current))
+ redirect_to :controller => "issues", :action => "show", :id => $1
+ return
+ end
+
+ if params[:id]
+ find_project
+ return unless check_project_privacy
+ end
+
+ if @project
+ # only show what the user is allowed to view
+ @object_types = %w(issues news documents changesets wiki_pages messages)
+ @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
+
+ @scope = @object_types.select {|t| params[t]}
+ @scope = @object_types if @scope.empty?
+ else
+ @object_types = @scope = %w(projects)
+ end
+
+ # extract tokens from the question
+ # eg. hello "bye bye" => ["hello", "bye bye"]
+ @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
+ # tokens must be at least 3 character long
+ @tokens = @tokens.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}%"}
+ @results = []
+ limit = 10
+ if @project
+ @scope.each do |s|
+ @results += s.singularize.camelcase.constantize.search(like_tokens, @project,
+ :all_words => @all_words,
+ :titles_only => @titles_only,
+ :limit => (limit+1),
+ :offset => offset,
+ :before => params[:previous].nil?)
+ end
+ @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime}
+ if params[:previous].nil?
+ @pagination_previous_date = @results[0].event_datetime if offset && @results[0]
+ if @results.size > limit
+ @pagination_next_date = @results[limit-1].event_datetime
+ @results = @results[0, limit]
+ end
+ else
+ @pagination_next_date = @results[-1].event_datetime if offset && @results[-1]
+ if @results.size > limit
+ @pagination_previous_date = @results[-(limit)].event_datetime
+ @results = @results[-(limit), limit]
+ end
+ end
+ else
+ operator = @all_words ? ' AND ' : ' OR '
+ @results += Project.find(:all,
+ :limit => limit,
+ :conditions => [ (["(#{Project.visible_by(User.current)}) AND (LOWER(name) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort]
+ ) if @scope.include? 'projects'
+ # if only one project is found, user is redirected to its overview
+ redirect_to :controller => 'projects', :action => 'show', :id => @results.first and return if @results.size == 1
+ end
+ else
+ @question = ""
+ end
+ render :layout => false if request.xhr?
+ end
+
+private
+ def find_project
+ @project = Project.find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+end
diff --git a/rest_sys/app/controllers/settings_controller.rb b/rest_sys/app/controllers/settings_controller.rb
new file mode 100644
index 000000000..09af63176
--- /dev/null
+++ b/rest_sys/app/controllers/settings_controller.rb
@@ -0,0 +1,45 @@
+# 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
+ end
+
+ def plugin
+ plugin_id = params[:id].to_sym
+ @plugin = Redmine::Plugin.registered_plugins[plugin_id]
+ if request.post?
+ Setting["plugin_#{plugin_id}"] = params[:settings]
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'plugin', :id => params[:id]
+ end
+ @partial = "../../vendor/plugins/#{plugin_id}/app/views/" + @plugin.settings[:partial]
+ @settings = Setting["plugin_#{plugin_id}"]
+ end
+end
diff --git a/rest_sys/app/controllers/sys_controller.rb b/rest_sys/app/controllers/sys_controller.rb
new file mode 100644
index 000000000..6065c2833
--- /dev/null
+++ b/rest_sys/app/controllers/sys_controller.rb
@@ -0,0 +1,47 @@
+# 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
+
+ # Returns the projects list, with their repositories
+ def projects
+ Project.find(:all, :include => :repository)
+ end
+
+ # Registers a repository for the given project identifier
+ # (Subversion specific)
+ def repository_created(identifier, url)
+ project = Project.find_by_identifier(identifier)
+ # Do not create the repository if the project has already one
+ return 0 unless project && project.repository.nil?
+ logger.debug "Repository for #{project.name} was created"
+ repository = Repository.factory('Subversion', :project => project, :url => url)
+ repository.save
+ repository.id || 0
+ end
+
+protected
+
+ def check_enabled(name, args)
+ Setting.sys_api_enabled?
+ end
+end
diff --git a/rest_sys/app/controllers/timelog_controller.rb b/rest_sys/app/controllers/timelog_controller.rb
new file mode 100644
index 000000000..f90c4527e
--- /dev/null
+++ b/rest_sys/app/controllers/timelog_controller.rb
@@ -0,0 +1,172 @@
+# 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, :authorize
+
+ helper :sort
+ include SortHelper
+ helper :issues
+
+ 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 = User.current.id
+
+ 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 != User.current
+ @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :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
diff --git a/rest_sys/app/controllers/trackers_controller.rb b/rest_sys/app/controllers/trackers_controller.rb
new file mode 100644
index 000000000..46edea548
--- /dev/null
+++ b/rest_sys/app/controllers/trackers_controller.rb
@@ -0,0 +1,84 @@
+# 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 TrackersController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+
+ def index
+ list
+ render :action => 'list' unless request.xhr?
+ end
+
+ # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
+ verify :method => :post, :only => [ :destroy, :move ], :redirect_to => { :action => :list }
+
+ def list
+ @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
+ render :action => "list", :layout => false if request.xhr?
+ end
+
+ def new
+ @tracker = Tracker.new(params[:tracker])
+ if request.post? and @tracker.save
+ # workflow copy
+ if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
+ Workflow.transaction do
+ copy_from.workflows.find(:all, :include => [:role, :old_status, :new_status]).each do |w|
+ Workflow.create(:tracker_id => @tracker.id, :role => w.role, :old_status => w.old_status, :new_status => w.new_status)
+ end
+ end
+ end
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :action => 'list'
+ end
+ @trackers = Tracker.find :all
+ end
+
+ def edit
+ @tracker = Tracker.find(params[:id])
+ if request.post? and @tracker.update_attributes(params[:tracker])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'list'
+ 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
+ @tracker = Tracker.find(params[:id])
+ unless @tracker.issues.empty?
+ flash[:error] = "This tracker contains issues and can\'t be deleted."
+ else
+ @tracker.destroy
+ end
+ redirect_to :action => 'list'
+ end
+end
diff --git a/rest_sys/app/controllers/users_controller.rb b/rest_sys/app/controllers/users_controller.rb
new file mode 100644
index 000000000..3f3adb57d
--- /dev/null
+++ b/rest_sys/app/controllers/users_controller.rb
@@ -0,0 +1,112 @@
+# 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 UsersController < ApplicationController
+ layout 'base'
+ before_filter :require_admin
+
+ helper :sort
+ include SortHelper
+ helper :custom_fields
+ include CustomFieldsHelper
+
+ def index
+ list
+ render :action => 'list' unless request.xhr?
+ end
+
+ def list
+ sort_init 'login', 'asc'
+ sort_update
+
+ @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,
+ 15,
+ params['page']
+ @users = User.find :all,:order => sort_clause,
+ :conditions => conditions,
+ :limit => @user_pages.items_per_page,
+ :offset => @user_pages.current.offset
+
+ render :action => "list", :layout => false if request.xhr?
+ end
+
+ def add
+ if request.get?
+ @user = User.new(:language => Setting.default_language)
+ @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
+ else
+ @user = User.new(params[:user])
+ @user.admin = params[:user][:admin] || false
+ @user.login = params[:user][:login]
+ @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
+ @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
+ @user.custom_values = @custom_values
+ if @user.save
+ Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :action => 'list'
+ end
+ end
+ @auth_sources = AuthSource.find(:all)
+ end
+
+ def edit
+ @user = User.find(params[:id])
+ if request.get?
+ @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @user.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
+ else
+ @user.admin = params[:user][:admin] if params[:user][:admin]
+ @user.login = params[:user][:login] if params[:user][:login]
+ @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
+ if params[:custom_fields]
+ @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
+ @user.custom_values = @custom_values
+ end
+ if @user.update_attributes(params[:user])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'list'
+ end
+ end
+ @auth_sources = AuthSource.find(:all)
+ @roles = Role.find_all_givable
+ @projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects
+ @membership ||= Member.new
+ end
+
+ def edit_membership
+ @user = User.find(params[:id])
+ @membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:user => @user)
+ @membership.attributes = params[:membership]
+ if request.post? and @membership.save
+ flash[:notice] = l(:notice_successful_update)
+ end
+ redirect_to :action => 'edit', :id => @user and return
+ end
+
+ def destroy_membership
+ @user = User.find(params[:id])
+ if request.post? and Member.find(params[:membership_id]).destroy
+ flash[:notice] = l(:notice_successful_update)
+ end
+ redirect_to :action => 'edit', :id => @user and return
+ end
+end
diff --git a/rest_sys/app/controllers/versions_controller.rb b/rest_sys/app/controllers/versions_controller.rb
new file mode 100644
index 000000000..1365c97e8
--- /dev/null
+++ b/rest_sys/app/controllers/versions_controller.rb
@@ -0,0 +1,70 @@
+# 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 VersionsController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize
+
+ cache_sweeper :version_sweeper, :only => [ :edit, :destroy ]
+
+ def show
+ end
+
+ def edit
+ if request.post? and @version.update_attributes(params[:version])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
+ end
+ end
+
+ def destroy
+ @version.destroy
+ redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
+ rescue
+ flash[:error] = "Unable to delete version"
+ redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
+ end
+
+ def download
+ @attachment = @version.attachments.find(params[:attachment_id])
+ @attachment.increment_download
+ send_file @attachment.diskfile, :filename => @attachment.filename, :type => @attachment.content_type
+ rescue
+ render_404
+ end
+
+ def destroy_file
+ @version.attachments.find(params[:attachment_id]).destroy
+ flash[:notice] = l(:notice_successful_delete)
+ redirect_to :controller => 'projects', :action => 'list_files', :id => @project
+ end
+
+ def status_by
+ respond_to do |format|
+ format.html { render :action => 'show' }
+ format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
+ end
+ end
+
+private
+ def find_project
+ @version = Version.find(params[:id])
+ @project = @version.project
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+end
diff --git a/rest_sys/app/controllers/watchers_controller.rb b/rest_sys/app/controllers/watchers_controller.rb
new file mode 100644
index 000000000..206dc0843
--- /dev/null
+++ b/rest_sys/app/controllers/watchers_controller.rb
@@ -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 = User.current
+ @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 = User.current
+ @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
diff --git a/rest_sys/app/controllers/welcome_controller.rb b/rest_sys/app/controllers/welcome_controller.rb
new file mode 100644
index 000000000..b4be7fb1c
--- /dev/null
+++ b/rest_sys/app/controllers/welcome_controller.rb
@@ -0,0 +1,25 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class WelcomeController < ApplicationController
+ layout 'base'
+
+ def index
+ @news = News.latest User.current
+ @projects = Project.latest User.current
+ end
+end
diff --git a/rest_sys/app/controllers/wiki_controller.rb b/rest_sys/app/controllers/wiki_controller.rb
new file mode 100644
index 000000000..2ee22167d
--- /dev/null
+++ b/rest_sys/app/controllers/wiki_controller.rb
@@ -0,0 +1,176 @@
+# 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, :authorize
+
+ 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?
+ if User.current.allowed_to?(:edit_wiki_pages, @project)
+ edit
+ render :action => 'edit'
+ else
+ render_404
+ end
+ 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 !@page.new_record? && @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 = User.current
+ # if page is new @page.save will also save content, but not if page isn't a new record
+ if (@page.new_record? ? @page.save : @content.save)
+ redirect_to :action => 'index', :id => @project, :page => @page.title
+ end
+ end
+ rescue ActiveRecord::StaleObjectError
+ # Optimistic locking exception
+ flash[:error] = l(:notice_locking_conflict)
+ end
+
+ # rename a page
+ def rename
+ @page = @wiki.find_page(params[:page])
+ @page.redirect_existing_links = true
+ # used to display the *original* title if some AR validation errors occur
+ @original_title = @page.pretty_title
+ if request.post? && @page.update_attributes(params[:wiki_page])
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'index', :id => @project, :page => @page.title
+ end
+ 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', 'date_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'
+ @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
+ # 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 => 'common/preview'
+ end
+
+ def add_attachment
+ @page = @wiki.find_page(params[:page])
+ attach_files(@page, params[:attachments])
+ 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
diff --git a/rest_sys/app/controllers/wikis_controller.rb b/rest_sys/app/controllers/wikis_controller.rb
new file mode 100644
index 000000000..a222570ef
--- /dev/null
+++ b/rest_sys/app/controllers/wikis_controller.rb
@@ -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 WikisController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :authorize
+
+ # Create or update a project's wiki
+ def edit
+ @wiki = @project.wiki || Wiki.new(:project => @project)
+ @wiki.attributes = params[:wiki]
+ @wiki.save if request.post?
+ render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
+ end
+
+ # Delete a project's wiki
+ def destroy
+ if request.post? && params[:confirm] && @project.wiki
+ @project.wiki.destroy
+ redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
+ end
+ end
+
+private
+ def find_project
+ @project = Project.find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+end
diff --git a/rest_sys/app/helpers/account_helper.rb b/rest_sys/app/helpers/account_helper.rb
new file mode 100644
index 000000000..ff18b76a4
--- /dev/null
+++ b/rest_sys/app/helpers/account_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module AccountHelper
+end
diff --git a/rest_sys/app/helpers/admin_helper.rb b/rest_sys/app/helpers/admin_helper.rb
new file mode 100644
index 000000000..1b41d8374
--- /dev/null
+++ b/rest_sys/app/helpers/admin_helper.rb
@@ -0,0 +1,23 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module AdminHelper
+ def project_status_options_for_select(selected)
+ options_for_select([[l(:label_all), "*"],
+ [l(:status_active), 1]], selected)
+ end
+end
diff --git a/rest_sys/app/helpers/application_helper.rb b/rest_sys/app/helpers/application_helper.rb
new file mode 100644
index 000000000..8deed9000
--- /dev/null
+++ b/rest_sys/app/helpers/application_helper.rb
@@ -0,0 +1,407 @@
+# 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 ApplicationHelper
+ include Redmine::WikiFormatting::Macros::Definitions
+
+ def current_role
+ @current_role ||= User.current.role_for_project(@project)
+ end
+
+ # Return true if user is authorized for controller/action, otherwise false
+ def authorize_for(controller, action)
+ User.current.allowed_to?({:controller => controller, :action => action}, @project)
+ end
+
+ # Display a link if user is authorized
+ def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
+ link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
+ end
+
+ # Display a link to user's account page
+ def link_to_user(user)
+ user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
+ 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 show_and_goto_link(name, id, options={})
+ onclick = "Element.show('#{id}'); "
+ onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
+ onclick << "location.href='##{id}-anchor'; "
+ onclick << "return false;"
+ link_to(name, "#", options.merge(:onclick => onclick))
+ end
+
+ def image_to_function(name, function, html_options = {})
+ html_options.symbolize_keys!
+ tag(:input, html_options.merge({
+ :type => "image", :src => image_path(name),
+ :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
+ }))
+ end
+
+ def prompt_to_remote(name, text, param, url, html_options = {})
+ html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
+ link_to name, {}, html_options
+ end
+
+ def format_date(date)
+ return nil unless date
+ # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
+ @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
+ date.strftime(@date_format)
+ end
+
+ def format_time(time, include_date = true)
+ return nil unless time
+ time = time.to_time if time.is_a?(String)
+ zone = User.current.time_zone
+ if time.utc?
+ local = zone ? zone.adjust(time) : time.getlocal
+ else
+ local = zone ? zone.adjust(time.getutc) : time
+ end
+ @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
+ @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
+ include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
+ end
+
+ def authoring(created, author)
+ time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
+ l(:label_added_time_by, author || 'Anonymous', time_tag)
+ end
+
+ def day_name(day)
+ l(:general_day_names).split(',')[day-1]
+ end
+
+ def month_name(month)
+ l(:actionview_datehelper_select_month_names).split(',')[month-1]
+ end
+
+ def pagination_links_full(paginator, options={}, html_options={})
+ page_param = options.delete(:page_param) || :page
+
+ html = ''
+ html << link_to_remote(('« ' + l(:label_previous)),
+ {:update => "content", :url => options.merge(page_param => 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|
+ link_to_remote(n.to_s,
+ {:url => {:params => options.merge(page_param => n)}, :update => 'content'},
+ {:href => url_for(:params => options.merge(page_param => n))})
+ end || '')
+
+ html << ' ' + link_to_remote((l(:label_next) + ' »'),
+ {:update => "content", :url => options.merge(page_param => paginator.current.next)},
+ {:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next
+ html
+ end
+
+ def set_html_title(text)
+ @html_header_title = text
+ end
+
+ def html_title
+ title = []
+ title << @project.name if @project
+ title << @html_header_title
+ title << Setting.app_title
+ title.compact.join(' - ')
+ end
+
+ ACCESSKEYS = {:edit => 'e',
+ :preview => 'r',
+ :quick_search => 'f',
+ :search => '4',
+ }.freeze unless const_defined?(:ACCESSKEYS)
+
+ def accesskey(s)
+ ACCESSKEYS[s]
+ end
+
+ # Formats text according to system settings.
+ # 2 ways to call this method:
+ # * with a String: textilizable(text, options)
+ # * with an object and one of its attribute: textilizable(issue, :description, options)
+ def textilizable(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ case args.size
+ when 1
+ obj = nil
+ text = args.shift || ''
+ when 2
+ obj = args.shift
+ text = obj.send(args.shift)
+ else
+ raise ArgumentError, 'invalid arguments to textilizable'
+ end
+
+ # 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 => 'download', :id => found.id
+ "!#{align}#{image_url}!"
+ else
+ "!#{align}#{filename}!"
+ end
+ end
+ end
+
+ text = (Setting.text_formatting == 'textile') ?
+ Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
+ simple_format(auto_link(h(text)))
+
+ # different methods for formatting wiki links
+ case options[:wiki_links]
+ when :local
+ # used for local links to html files
+ format_wiki_link = Proc.new {|project, title| "#{title}.html" }
+ when :anchor
+ # used for single-file wiki export
+ format_wiki_link = Proc.new {|project, title| "##{title}" }
+ else
+ format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
+ end
+
+ project = options[:project] || @project
+
+ # turn wiki links into html links
+ # example:
+ # [[mypage]]
+ # [[mypage|mytext]]
+ # wiki links can refer other project wikis, using project name or identifier:
+ # [[project:]] -> wiki starting page
+ # [[project:|mytext]]
+ # [[project:mypage]]
+ # [[project:mypage|mytext]]
+ text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) do |m|
+ link_project = project
+ page = $1
+ title = $3
+ if page =~ /^([^\:]+)\:(.*)$/
+ link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
+ page = title || $2
+ title = $1 if page.blank?
+ end
+
+ if link_project && link_project.wiki
+ # check if page exists
+ wiki_page = link_project.wiki.find_page(page)
+ link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
+ :class => ('wiki-page' + (wiki_page ? '' : ' new')))
+ else
+ # project or wiki doesn't exist
+ title || page
+ end
+ end
+
+ # turn issue and revision ids into links
+ # example:
+ # #52 -> #52
+ # r52 -> r52 (project.id is 6)
+ text = text.gsub(%r{([\s\(,-^])(#|r)(\d+)(?=[[:punct:]]|\s|<|$)}) do |m|
+ leading, otype, oid = $1, $2, $3
+ link = nil
+ if otype == 'r'
+ if project && (changeset = project.changesets.find_by_revision(oid))
+ link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
+ :title => truncate(changeset.comments, 100))
+ end
+ else
+ if issue = Issue.find_by_id(oid.to_i, :include => [:project, :status], :conditions => Project.visible_by(User.current))
+ link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
+ :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
+ link = content_tag('del', link) if issue.closed?
+ end
+ end
+ leading + (link || "#{otype}#{oid}")
+ end
+
+ 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+/, "
"). # 2+ newline -> 2 br
+ gsub(/([^\n]\n)(?=[^\n])/, '\1 ') # 1 newline -> br
+ end
+
+ def error_messages_for(object_name, options = {})
+ options = options.symbolize_keys
+ object = instance_variable_get("@#{object_name}")
+ if object && !object.errors.empty?
+ # build full_messages here with controller current language
+ full_messages = []
+ object.errors.each do |attr, msg|
+ next if msg.nil?
+ msg = msg.first if msg.is_a? Array
+ if attr == "base"
+ full_messages << l(msg)
+ else
+ full_messages << "« " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " » " + l(msg) unless attr == "custom_values"
+ end
+ end
+ # retrieve custom values error messages
+ if object.errors[:custom_values]
+ object.custom_values.each do |v|
+ v.errors.each do |attr, msg|
+ next if msg.nil?
+ msg = msg.first if msg.is_a? Array
+ full_messages << "« " + v.custom_field.name + " » " + l(msg)
+ end
+ end
+ end
+ content_tag("div",
+ content_tag(
+ options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
+ ) +
+ content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
+ "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
+ )
+ else
+ ""
+ end
+ end
+
+ def lang_options_for_select(blank=true)
+ (blank ? [["(auto)", ""]] : []) +
+ GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
+ end
+
+ def label_tag_for(name, option_tags = nil, options = {})
+ label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
+ content_tag("label", label_text)
+ end
+
+ def labelled_tabular_form_for(name, object, options, &proc)
+ options[:html] ||= {}
+ options[:html].store :class, "tabular"
+ form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
+ end
+
+ def check_all_links(form_name)
+ link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
+ " | " +
+ link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
+ end
+
+ def progress_bar(pcts, options={})
+ pcts = [pcts, pcts] unless pcts.is_a?(Array)
+ pcts[1] = pcts[1] - pcts[0]
+ pcts << (100 - pcts[1] - pcts[0])
+ width = options[:width] || '100px;'
+ legend = options[:legend] || ''
+ content_tag('table',
+ content_tag('tr',
+ (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
+ (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
+ (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
+ ), :class => 'progress', :style => "width: #{width};") +
+ content_tag('p', legend, :class => 'pourcent')
+ end
+
+ def context_menu_link(name, url, options={})
+ options[:class] ||= ''
+ if options.delete(:selected)
+ options[:class] << ' icon-checked disabled'
+ options[:disabled] = true
+ end
+ if options.delete(:disabled)
+ options.delete(:method)
+ options.delete(:confirm)
+ options.delete(:onclick)
+ options[:class] << ' disabled'
+ url = '#'
+ end
+ link_to name, url, options
+ end
+
+ def calendar_for(field_id)
+ 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' });")
+ 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
+
+ def content_for(name, content = nil, &block)
+ @has_content ||= {}
+ @has_content[name] = true
+ super(name, content, &block)
+ end
+
+ def has_content?(name)
+ (@has_content && @has_content[name]) || false
+ end
+end
+
+class TabularFormBuilder < ActionView::Helpers::FormBuilder
+ include GLoc
+
+ def initialize(object_name, object, template, options, proc)
+ set_language_if_valid options.delete(:lang)
+ @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
+ end
+
+ (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
+ src = <<-END_SRC
+ def #{selector}(field, options = {})
+ return super if options.delete :no_label
+ label_text = l(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,
+ :class => (@object && @object.errors[field] ? "error" : nil),
+ :for => (@object_name.to_s + "_" + field.to_s))
+ label + super
+ end
+ END_SRC
+ class_eval src, __FILE__, __LINE__
+ end
+
+ def select(field, choices, options = {}, html_options = {})
+ label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
+ label = @template.content_tag("label", label_text,
+ :class => (@object && @object.errors[field] ? "error" : nil),
+ :for => (@object_name.to_s + "_" + field.to_s))
+ label + super
+ end
+
+end
+
diff --git a/rest_sys/app/helpers/attachments_helper.rb b/rest_sys/app/helpers/attachments_helper.rb
new file mode 100644
index 000000000..989cd3e66
--- /dev/null
+++ b/rest_sys/app/helpers/attachments_helper.rb
@@ -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
diff --git a/rest_sys/app/helpers/auth_sources_helper.rb b/rest_sys/app/helpers/auth_sources_helper.rb
new file mode 100644
index 000000000..d47e9856a
--- /dev/null
+++ b/rest_sys/app/helpers/auth_sources_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module AuthSourcesHelper
+end
diff --git a/rest_sys/app/helpers/boards_helper.rb b/rest_sys/app/helpers/boards_helper.rb
new file mode 100644
index 000000000..3719e0fe8
--- /dev/null
+++ b/rest_sys/app/helpers/boards_helper.rb
@@ -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
diff --git a/rest_sys/app/helpers/custom_fields_helper.rb b/rest_sys/app/helpers/custom_fields_helper.rb
new file mode 100644
index 000000000..b1b176107
--- /dev/null
+++ b/rest_sys/app/helpers/custom_fields_helper.rb
@@ -0,0 +1,77 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module CustomFieldsHelper
+
+ # Return custom field html tag corresponding to its format
+ def custom_field_tag(custom_value)
+ custom_field = custom_value.custom_field
+ field_name = "custom_fields[#{custom_field.id}]"
+ field_id = "custom_fields_#{custom_field.id}"
+
+ case custom_field.field_format
+ when "date"
+ text_field('custom_value', 'value', :name => field_name, :id => field_id, :size => 10) +
+ calendar_for(field_id)
+ when "text"
+ text_area 'custom_value', 'value', :name => field_name, :id => field_id, :cols => 60, :rows => 3
+ when "bool"
+ check_box 'custom_value', 'value', :name => field_name, :id => field_id
+ when "list"
+ select 'custom_value', 'value', custom_field.possible_values, { :include_blank => true }, :name => field_name, :id => field_id
+ else
+ text_field 'custom_value', 'value', :name => field_name, :id => field_id
+ end
+ end
+
+ # Return custom field label tag
+ def custom_field_label_tag(custom_value)
+ content_tag "label", custom_value.custom_field.name +
+ (custom_value.custom_field.is_required? ? " *" : ""),
+ :for => "custom_fields_#{custom_value.custom_field.id}",
+ :class => (custom_value.errors.empty? ? nil : "error" )
+ end
+
+ # Return custom field tag with its label tag
+ def custom_field_tag_with_label(custom_value)
+ custom_field_label_tag(custom_value) + custom_field_tag(custom_value)
+ end
+
+ # Return a string used to display a custom value
+ def show_value(custom_value)
+ return "" unless custom_value
+ format_value(custom_value.value, custom_value.custom_field.field_format)
+ end
+
+ # Return a string used to display a custom value
+ def format_value(value, field_format)
+ return "" unless value && !value.empty?
+ case field_format
+ when "date"
+ begin; l_date(value.to_date); rescue; value end
+ when "bool"
+ l_YesNo(value == "1")
+ else
+ value
+ end
+ end
+
+ # Return an array of custom field formats which can be used in select_tag
+ def custom_field_formats_for_select
+ CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] }
+ end
+end
diff --git a/rest_sys/app/helpers/documents_helper.rb b/rest_sys/app/helpers/documents_helper.rb
new file mode 100644
index 000000000..7e96a6db3
--- /dev/null
+++ b/rest_sys/app/helpers/documents_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module DocumentsHelper
+end
diff --git a/rest_sys/app/helpers/enumerations_helper.rb b/rest_sys/app/helpers/enumerations_helper.rb
new file mode 100644
index 000000000..c0daf64d2
--- /dev/null
+++ b/rest_sys/app/helpers/enumerations_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module EnumerationsHelper
+end
diff --git a/rest_sys/app/helpers/ifpdf_helper.rb b/rest_sys/app/helpers/ifpdf_helper.rb
new file mode 100644
index 000000000..585dbeeca
--- /dev/null
+++ b/rest_sys/app/helpers/ifpdf_helper.rb
@@ -0,0 +1,80 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require 'iconv'
+require 'rfpdf/chinese'
+
+module IfpdfHelper
+
+ class IFPDF < FPDF
+ include GLoc
+ 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='')
+ @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
+ # these quotation marks are not correctly rendered in the pdf
+ txt = txt.gsub(/[“â€]/, '"') if txt
+ txt = begin
+ # 0x5c char handling
+ txtar = txt.split('\\')
+ txtar << '' if txt[-1] == ?\\
+ txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\")
+ rescue
+ txt
+ end || ''
+ super w,h,txt,border,ln,align,fill,link
+ end
+
+ def Footer
+ SetFont(@font_for_footer, 'I', 8)
+ SetY(-15)
+ SetX(15)
+ Cell(0, 5, @footer_date, 0, 0, 'L')
+ SetY(-15)
+ SetX(-30)
+ Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
+ end
+
+ end
+
+end
diff --git a/rest_sys/app/helpers/issue_categories_helper.rb b/rest_sys/app/helpers/issue_categories_helper.rb
new file mode 100644
index 000000000..0109e7fae
--- /dev/null
+++ b/rest_sys/app/helpers/issue_categories_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module IssueCategoriesHelper
+end
diff --git a/rest_sys/app/helpers/issue_relations_helper.rb b/rest_sys/app/helpers/issue_relations_helper.rb
new file mode 100644
index 000000000..377059d5f
--- /dev/null
+++ b/rest_sys/app/helpers/issue_relations_helper.rb
@@ -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
diff --git a/rest_sys/app/helpers/issue_statuses_helper.rb b/rest_sys/app/helpers/issue_statuses_helper.rb
new file mode 100644
index 000000000..859b09911
--- /dev/null
+++ b/rest_sys/app/helpers/issue_statuses_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module IssueStatusesHelper
+end
diff --git a/rest_sys/app/helpers/issues_helper.rb b/rest_sys/app/helpers/issues_helper.rb
new file mode 100644
index 000000000..197760b53
--- /dev/null
+++ b/rest_sys/app/helpers/issues_helper.rb
@@ -0,0 +1,159 @@
+# 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 'csv'
+
+module IssuesHelper
+ include ApplicationHelper
+
+ def render_issue_tooltip(issue)
+ @cached_label_start_date ||= l(:field_start_date)
+ @cached_label_due_date ||= l(:field_due_date)
+ @cached_label_assigned_to ||= l(:field_assigned_to)
+ @cached_label_priority ||= l(:field_priority)
+
+ link_to_issue(issue) + ": #{h(issue.subject)}
" +
+ "#{@cached_label_start_date}: #{format_date(issue.start_date)} " +
+ "#{@cached_label_due_date}: #{format_date(issue.due_date)} " +
+ "#{@cached_label_assigned_to}: #{issue.assigned_to} " +
+ "#{@cached_label_priority}: #{issue.priority.name}"
+ end
+
+ def show_detail(detail, no_html=false)
+ case detail.property
+ when 'attr'
+ label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
+ case detail.prop_key
+ when 'due_date', 'start_date'
+ value = format_date(detail.value.to_date) if detail.value
+ old_value = format_date(detail.old_value.to_date) if detail.old_value
+ when 'status_id'
+ s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
+ s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
+ when 'assigned_to_id'
+ u = User.find_by_id(detail.value) and value = u.name if detail.value
+ u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
+ when 'priority_id'
+ e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
+ e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
+ when 'category_id'
+ c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
+ c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
+ when 'fixed_version_id'
+ v = Version.find_by_id(detail.value) and value = v.name if detail.value
+ v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
+ end
+ when 'cf'
+ custom_field = CustomField.find_by_id(detail.prop_key)
+ if custom_field
+ label = custom_field.name
+ value = format_value(detail.value, custom_field.field_format) if detail.value
+ old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
+ end
+ when 'attachment'
+ label = l(:label_attachment)
+ end
+
+ label ||= detail.prop_key
+ value ||= detail.value
+ old_value ||= detail.old_value
+
+ unless no_html
+ label = content_tag('strong', label)
+ old_value = content_tag("i", h(old_value)) if detail.old_value
+ old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
+ if detail.property == 'attachment' && !value.blank? && Attachment.find_by_id(detail.prop_key)
+ # Link to the attachment if it has not been removed
+ value = link_to(value, :controller => 'attachments', :action => 'download', :id => detail.prop_key)
+ else
+ value = content_tag("i", h(value)) if value
+ end
+ end
+
+ if !detail.value.blank?
+ case detail.property
+ when 'attr', 'cf'
+ if !detail.old_value.blank?
+ 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
+ else
+ case detail.property
+ when 'attr', 'cf'
+ label + " " + l(:text_journal_deleted) + " (#{old_value})"
+ when 'attachment'
+ "#{label} #{old_value} #{l(:label_deleted)}"
+ end
+ end
+ end
+
+ def issues_to_csv(issues, project = nil)
+ 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_status),
+ l(:field_project),
+ l(:field_tracker),
+ l(:field_priority),
+ l(:field_subject),
+ l(:field_assigned_to),
+ l(:field_category),
+ l(:field_fixed_version),
+ l(:field_author),
+ l(:field_start_date),
+ l(:field_due_date),
+ l(:field_done_ratio),
+ l(:field_created_on),
+ l(:field_updated_on)
+ ]
+ # Export project custom fields if project is given
+ # otherwise export custom fields marked as "For all projects"
+ custom_fields = project.nil? ? IssueCustomField.for_all : project.all_custom_fields
+ custom_fields.each {|f| headers << f.name}
+ csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
+ # csv lines
+ issues.each do |issue|
+ fields = [issue.id,
+ issue.status.name,
+ issue.project.name,
+ issue.tracker.name,
+ issue.priority.name,
+ issue.subject,
+ issue.assigned_to,
+ issue.category,
+ issue.fixed_version,
+ issue.author.name,
+ format_date(issue.start_date),
+ format_date(issue.due_date),
+ issue.done_ratio,
+ format_time(issue.created_on),
+ format_time(issue.updated_on)
+ ]
+ custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) }
+ csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
+ end
+ end
+ export.rewind
+ export
+ end
+end
diff --git a/rest_sys/app/helpers/members_helper.rb b/rest_sys/app/helpers/members_helper.rb
new file mode 100644
index 000000000..fcf9c92e6
--- /dev/null
+++ b/rest_sys/app/helpers/members_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module MembersHelper
+end
diff --git a/rest_sys/app/helpers/messages_helper.rb b/rest_sys/app/helpers/messages_helper.rb
new file mode 100644
index 000000000..bf23275c3
--- /dev/null
+++ b/rest_sys/app/helpers/messages_helper.rb
@@ -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
diff --git a/rest_sys/app/helpers/my_helper.rb b/rest_sys/app/helpers/my_helper.rb
new file mode 100644
index 000000000..9098f67bc
--- /dev/null
+++ b/rest_sys/app/helpers/my_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module MyHelper
+end
diff --git a/rest_sys/app/helpers/news_helper.rb b/rest_sys/app/helpers/news_helper.rb
new file mode 100644
index 000000000..28d36f31a
--- /dev/null
+++ b/rest_sys/app/helpers/news_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module NewsHelper
+end
diff --git a/rest_sys/app/helpers/projects_helper.rb b/rest_sys/app/helpers/projects_helper.rb
new file mode 100644
index 000000000..df4b9c334
--- /dev/null
+++ b/rest_sys/app/helpers/projects_helper.rb
@@ -0,0 +1,199 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module 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
+
+ def project_settings_tabs
+ tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
+ {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
+ {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
+ {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
+ {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
+ {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
+ {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
+ {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}
+ ]
+ tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
+ end
+
+ # Generates a gantt image
+ # Only defined if RMagick is avalaible
+ def gantt_image(events, date_from, months, zoom)
+ date_to = (date_from >> months)-1
+ show_weeks = zoom > 1
+ show_days = zoom > 2
+
+ subject_width = 320
+ header_heigth = 18
+ # width of one day in pixels
+ zoom = zoom*2
+ g_width = (date_to - date_from + 1)*zoom
+ g_height = 20 * events.length + 20
+ headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
+ height = g_height + headers_heigth
+
+ imgl = Magick::ImageList.new
+ imgl.new_image(subject_width+g_width+1, height)
+ gc = Magick::Draw.new
+
+ # Subjects
+ top = headers_heigth + 20
+ gc.fill('black')
+ gc.stroke('transparent')
+ gc.stroke_width(1)
+ events.each do |i|
+ gc.text(4, top + 2, (i.is_a?(Issue) ? i.subject : i.name))
+ top = top + 20
+ end
+
+ # Months headers
+ month_f = date_from
+ left = subject_width
+ months.times do
+ width = ((month_f >> 1) - month_f) * zoom
+ gc.fill('white')
+ gc.stroke('grey')
+ gc.stroke_width(1)
+ gc.rectangle(left, 0, left + width, height)
+ gc.fill('black')
+ gc.stroke('transparent')
+ gc.stroke_width(1)
+ gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
+ left = left + width
+ month_f = month_f >> 1
+ end
+
+ # Weeks headers
+ if show_weeks
+ left = subject_width
+ height = header_heigth
+ if date_from.cwday == 1
+ # date_from is monday
+ week_f = date_from
+ else
+ # find next monday after date_from
+ week_f = date_from + (7 - date_from.cwday + 1)
+ width = (7 - date_from.cwday + 1) * zoom
+ gc.fill('white')
+ gc.stroke('grey')
+ gc.stroke_width(1)
+ gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
+ left = left + width
+ end
+ while week_f <= date_to
+ width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
+ gc.fill('white')
+ gc.stroke('grey')
+ gc.stroke_width(1)
+ gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
+ gc.fill('black')
+ gc.stroke('transparent')
+ gc.stroke_width(1)
+ gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
+ left = left + width
+ week_f = week_f+7
+ end
+ end
+
+ # Days details (week-end in grey)
+ if show_days
+ left = subject_width
+ height = g_height + header_heigth - 1
+ wday = date_from.cwday
+ (date_to - date_from + 1).to_i.times do
+ width = zoom
+ gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
+ gc.stroke('grey')
+ gc.stroke_width(1)
+ gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
+ left = left + width
+ wday = wday + 1
+ wday = 1 if wday > 7
+ end
+ end
+
+ # border
+ gc.fill('transparent')
+ gc.stroke('grey')
+ gc.stroke_width(1)
+ gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
+ gc.stroke('black')
+ gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
+
+ # content
+ top = headers_heigth + 20
+ gc.stroke('transparent')
+ events.each do |i|
+ if i.is_a?(Issue)
+ i_start_date = (i.start_date >= date_from ? i.start_date : date_from )
+ i_end_date = (i.due_date <= date_to ? i.due_date : date_to )
+ i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor
+ i_done_date = (i_done_date <= date_from ? date_from : i_done_date )
+ i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
+ i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
+
+ i_left = subject_width + ((i_start_date - date_from)*zoom).floor
+ i_width = ((i_end_date - i_start_date + 1)*zoom).floor # total width of the issue
+ d_width = ((i_done_date - i_start_date)*zoom).floor # done width
+ l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor : 0 # delay width
+
+ gc.fill('grey')
+ gc.rectangle(i_left, top, i_left + i_width, top - 6)
+ gc.fill('red')
+ gc.rectangle(i_left, top, i_left + l_width, top - 6) if l_width > 0
+ gc.fill('blue')
+ gc.rectangle(i_left, top, i_left + d_width, top - 6) if d_width > 0
+ gc.fill('black')
+ gc.text(i_left + i_width + 5,top + 1, "#{i.status.name} #{i.done_ratio}%")
+ else
+ i_left = subject_width + ((i.start_date - date_from)*zoom).floor
+ gc.fill('green')
+ gc.rectangle(i_left, top, i_left + 6, top - 6)
+ gc.fill('black')
+ gc.text(i_left + 11, top + 1, i.name)
+ end
+ top = top + 20
+ end
+
+ # today red line
+ if Date.today >= date_from and Date.today <= date_to
+ gc.stroke('red')
+ x = (Date.today-date_from+1)*zoom + subject_width
+ gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
+ end
+
+ gc.draw(imgl)
+ imgl
+ end if Object.const_defined?(:Magick)
+
+ def new_issue_selector
+ trackers = @project.trackers
+ # can't use form tag inside helper
+ content_tag('form',
+ select_tag('tracker_id', '' + options_from_collection_for_select(trackers, 'id', 'name'), :onchange => "if (this.value != '') {this.form.submit()}"),
+ :action => url_for(:controller => 'projects', :action => 'add_issue', :id => @project), :method => 'get')
+ end
+end
diff --git a/rest_sys/app/helpers/queries_helper.rb b/rest_sys/app/helpers/queries_helper.rb
new file mode 100644
index 000000000..3011d3aec
--- /dev/null
+++ b/rest_sys/app/helpers/queries_helper.rb
@@ -0,0 +1,51 @@
+# 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 QueriesHelper
+
+ def operators_for_select(filter_type)
+ Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
+ end
+
+ def column_header(column)
+ column.sortable ? sort_header_tag(column.sortable, :caption => column.caption) : content_tag('th', column.caption)
+ end
+
+ def column_content(column, issue)
+ if column.is_a?(QueryCustomFieldColumn)
+ cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
+ show_value(cv)
+ else
+ value = issue.send(column.name)
+ if value.is_a?(Date)
+ format_date(value)
+ elsif value.is_a?(Time)
+ format_time(value)
+ else
+ case column.name
+ when :subject
+ h((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') +
+ link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
+ when :done_ratio
+ progress_bar(value, :width => '80px')
+ else
+ h(value)
+ end
+ end
+ end
+ end
+end
diff --git a/rest_sys/app/helpers/reports_helper.rb b/rest_sys/app/helpers/reports_helper.rb
new file mode 100644
index 000000000..c733a0634
--- /dev/null
+++ b/rest_sys/app/helpers/reports_helper.rb
@@ -0,0 +1,36 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module ReportsHelper
+
+ def aggregate(data, criteria)
+ a = 0
+ data.each { |row|
+ match = 1
+ criteria.each { |k, v|
+ match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && row[k] == (v == 0 ? "f" : "t"))
+ } unless criteria.nil?
+ a = a + row["total"].to_i if match == 1
+ } unless data.nil?
+ a
+ end
+
+ def aggregate_link(data, criteria, *args)
+ a = aggregate data, criteria
+ a > 0 ? link_to(a, *args) : '-'
+ end
+end
diff --git a/rest_sys/app/helpers/repositories_helper.rb b/rest_sys/app/helpers/repositories_helper.rb
new file mode 100644
index 000000000..d2d04604d
--- /dev/null
+++ b/rest_sys/app/helpers/repositories_helper.rb
@@ -0,0 +1,87 @@
+# 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 'coderay'
+require 'coderay/helpers/file_type'
+require 'iconv'
+
+module RepositoriesHelper
+ def syntax_highlight(name, content)
+ type = CodeRay::FileType[name]
+ type ? CodeRay.scan(content, type).html : h(content)
+ end
+
+ def to_utf8(str)
+ return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
+ @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
+ @encodings.each do |encoding|
+ begin
+ return Iconv.conv('UTF-8', encoding, str)
+ rescue Iconv::Failure
+ # do nothing here and try the next encoding
+ end
+ end
+ str
+ end
+
+ 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(repository)
+ container = [[]]
+ REDMINE_SUPPORTED_SCM.each {|scm| container << ["Repository::#{scm}".constantize.scm_name, scm]}
+ select_tag('repository_scm',
+ options_for_select(container, repository.class.name.demodulize),
+ :disabled => (repository && !repository.new_record?),
+ :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
+ )
+ 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?)) +
+ ' (http://, https://, svn://, file:///)') +
+ content_tag('p', form.text_field(:login, :size => 30)) +
+ content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
+ :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
+ :onfocus => "this.value=''; this.name='repository[password]';",
+ :onchange => "this.name='repository[password]';"))
+ 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
+
+ def bazaar_field_tags(form, repository)
+ content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
+ end
+end
diff --git a/rest_sys/app/helpers/roles_helper.rb b/rest_sys/app/helpers/roles_helper.rb
new file mode 100644
index 000000000..ab3a7ff03
--- /dev/null
+++ b/rest_sys/app/helpers/roles_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module RolesHelper
+end
diff --git a/rest_sys/app/helpers/search_helper.rb b/rest_sys/app/helpers/search_helper.rb
new file mode 100644
index 000000000..ed2f40b69
--- /dev/null
+++ b/rest_sys/app/helpers/search_helper.rb
@@ -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.
+
+module SearchHelper
+ def highlight_tokens(text, tokens)
+ return text unless text && tokens && !tokens.empty?
+ regexp = Regexp.new "(#{tokens.join('|')})", Regexp::IGNORECASE
+ result = ''
+ text.split(regexp).each_with_index do |words, i|
+ if result.length > 1200
+ # maximum length of the preview reached
+ result << '...'
+ break
+ end
+ if i.even?
+ result << h(words.length > 100 ? "#{words[0..44]} ... #{words[-45..-1]}" : words)
+ else
+ t = (tokens.index(words.downcase) || 0) % 4
+ result << content_tag('span', h(words), :class => "highlight token-#{t}")
+ end
+ end
+ result
+ end
+end
diff --git a/rest_sys/app/helpers/settings_helper.rb b/rest_sys/app/helpers/settings_helper.rb
new file mode 100644
index 000000000..f53314c40
--- /dev/null
+++ b/rest_sys/app/helpers/settings_helper.rb
@@ -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
diff --git a/rest_sys/app/helpers/sort_helper.rb b/rest_sys/app/helpers/sort_helper.rb
new file mode 100644
index 000000000..dfd681fff
--- /dev/null
+++ b/rest_sys/app/helpers/sort_helper.rb
@@ -0,0 +1,157 @@
+# Helpers to sort tables using clickable column headers.
+#
+# Author: Stuart Rackham , March 2005.
+# License: This source code is released under the MIT license.
+#
+# - Consecutive clicks toggle the column's sort order.
+# - Sort state is maintained by a session hash entry.
+# - Icon image identifies sort column and state.
+# - Typically used in conjunction with the Pagination module.
+#
+# Example code snippets:
+#
+# Controller:
+#
+# helper :sort
+# include SortHelper
+#
+# def list
+# sort_init 'last_name'
+# sort_update
+# @items = Contact.find_all nil, sort_clause
+# end
+#
+# Controller (using Pagination module):
+#
+# helper :sort
+# include SortHelper
+#
+# def list
+# sort_init 'last_name'
+# sort_update
+# @contact_pages, @items = paginate :contacts,
+# :order_by => sort_clause,
+# :per_page => 10
+# end
+#
+# View (table header in list.rhtml):
+#
+#
+#
+#
+#
+# - The ascending and descending sort icon images are sort_asc.png and
+# sort_desc.png and reside in the application's images directory.
+# - Introduces instance variables: @sort_name, @sort_default.
+# - Introduces params :sort_key and :sort_order.
+#
+module SortHelper
+
+ # Initializes the default sort column (default_key) and sort order
+ # (default_order).
+ #
+ # - default_key is a column attribute name.
+ # - default_order is 'asc' or 'desc'.
+ # - name is the name of the session hash entry that stores the sort state,
+ # defaults to '_sort'.
+ #
+ def sort_init(default_key, default_order='asc', name=nil)
+ @sort_name = name || params[:controller] + params[:action] + '_sort'
+ @sort_default = {:key => default_key, :order => default_order}
+ end
+
+ # Updates the sort state. Call this in the controller prior to calling
+ # sort_clause.
+ #
+ def sort_update()
+ if params[:sort_key]
+ sort = {:key => params[:sort_key], :order => params[:sort_order]}
+ elsif session[@sort_name]
+ sort = session[@sort_name] # Previous sort.
+ else
+ sort = @sort_default
+ end
+ session[@sort_name] = sort
+ end
+
+ # Returns an SQL sort clause corresponding to the current sort state.
+ # Use this to sort the controller's table items collection.
+ #
+ def sort_clause()
+ session[@sort_name][:key] + ' ' + session[@sort_name][:order]
+ end
+
+ # Returns a link which sorts by the named column.
+ #
+ # - column is the name of an attribute in the sorted record collection.
+ # - The optional caption explicitly specifies the displayed link text.
+ # - A sort icon image is positioned to the right of the sort link.
+ #
+ def sort_link(column, caption=nil)
+ key, order = session[@sort_name][:key], session[@sort_name][:order]
+ if key == column
+ if order.downcase == 'asc'
+ icon = 'sort_asc.png'
+ order = 'desc'
+ else
+ icon = 'sort_desc.png'
+ order = 'asc'
+ end
+ else
+ icon = nil
+ order = 'desc' # changed for desc order by default
+ end
+ caption = titleize(Inflector::humanize(column)) unless caption
+
+ url = {:sort_key => column, :sort_order => order, :issue_id => params[:issue_id], :project_id => params[:project_id]}
+
+ link_to_remote(caption,
+ {:update => "content", :url => url},
+ {:href => url_for(url)}) +
+ (icon ? nbsp(2) + image_tag(icon) : '')
+ end
+
+ # Returns a table header
tag with a sort link for the named column
+ # attribute.
+ #
+ # Options:
+ # :caption The displayed link name (defaults to titleized column name).
+ # :title The tag's 'title' attribute (defaults to 'Sort by :caption').
+ #
+ # Other options hash entries generate additional table header tag attributes.
+ #
+ # Example:
+ #
+ # <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %>
+ #
+ # Renders:
+ #
+ #
+ #
+ def sort_header_tag(column, options = {})
+ caption = options.delete(:caption) || titleize(Inflector::humanize(column))
+ options[:title]= l(:label_sort_by, "\"#{caption}\"") unless options[:title]
+ content_tag('th', sort_link(column, caption), options)
+ end
+
+ private
+
+ # Return n non-breaking spaces.
+ def nbsp(n)
+ ' ' * n
+ end
+
+ # Return capitalized title.
+ def titleize(title)
+ title.split.map {|w| w.capitalize }.join(' ')
+ end
+
+end
diff --git a/rest_sys/app/helpers/timelog_helper.rb b/rest_sys/app/helpers/timelog_helper.rb
new file mode 100644
index 000000000..22e4eba0b
--- /dev/null
+++ b/rest_sys/app/helpers/timelog_helper.rb
@@ -0,0 +1,30 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module TimelogHelper
+ def select_hours(data, criteria, value)
+ data.select {|row| row[criteria] == value.to_s}
+ end
+
+ def sum_hours(data)
+ sum = 0
+ data.each do |row|
+ sum += row['hours'].to_f
+ end
+ sum
+ end
+end
diff --git a/rest_sys/app/helpers/trackers_helper.rb b/rest_sys/app/helpers/trackers_helper.rb
new file mode 100644
index 000000000..89f92e333
--- /dev/null
+++ b/rest_sys/app/helpers/trackers_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module TrackersHelper
+end
diff --git a/rest_sys/app/helpers/users_helper.rb b/rest_sys/app/helpers/users_helper.rb
new file mode 100644
index 000000000..9dc87c5cc
--- /dev/null
+++ b/rest_sys/app/helpers/users_helper.rb
@@ -0,0 +1,25 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+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
diff --git a/rest_sys/app/helpers/versions_helper.rb b/rest_sys/app/helpers/versions_helper.rb
new file mode 100644
index 000000000..0fcc6407c
--- /dev/null
+++ b/rest_sys/app/helpers/versions_helper.rb
@@ -0,0 +1,47 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module VersionsHelper
+
+ STATUS_BY_CRITERIAS = %w(category tracker priority author assigned_to)
+
+ def render_issue_status_by(version, criteria)
+ criteria ||= 'category'
+ raise 'Unknown criteria' unless STATUS_BY_CRITERIAS.include?(criteria)
+
+ h = Hash.new {|k,v| k[v] = [0, 0]}
+ begin
+ # Total issue count
+ Issue.count(:group => criteria,
+ :conditions => ["#{Issue.table_name}.fixed_version_id = ?", version.id]).each {|c,s| h[c][0] = s}
+ # Open issues count
+ Issue.count(:group => criteria,
+ :include => :status,
+ :conditions => ["#{Issue.table_name}.fixed_version_id = ? AND #{IssueStatus.table_name}.is_closed = ?", version.id, false]).each {|c,s| h[c][1] = s}
+ rescue ActiveRecord::RecordNotFound
+ # When grouping by an association, Rails throws this exception if there's no result (bug)
+ end
+ counts = h.keys.compact.sort.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
+ max = counts.collect {|c| c[:total]}.max
+
+ render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max}
+ end
+
+ def status_by_options_for_select(value)
+ options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value)
+ end
+end
diff --git a/rest_sys/app/helpers/watchers_helper.rb b/rest_sys/app/helpers/watchers_helper.rb
new file mode 100644
index 000000000..c83c785fc
--- /dev/null
+++ b/rest_sys/app/helpers/watchers_helper.rb
@@ -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 && user.logged? && 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
diff --git a/rest_sys/app/helpers/welcome_helper.rb b/rest_sys/app/helpers/welcome_helper.rb
new file mode 100644
index 000000000..753e1f127
--- /dev/null
+++ b/rest_sys/app/helpers/welcome_helper.rb
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module WelcomeHelper
+end
diff --git a/rest_sys/app/helpers/wiki_helper.rb b/rest_sys/app/helpers/wiki_helper.rb
new file mode 100644
index 000000000..980035bd4
--- /dev/null
+++ b/rest_sys/app/helpers/wiki_helper.rb
@@ -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] = '' + words[add_at]
+ words[add_to] = words[add_to] + ''
+ end
+ if del_at
+ words.insert del_at - del_off + dels + words_add, '' + deleted + ''
+ dels += 1
+ del_off += words_del
+ words_del = 0
+ end
+ end
+ simple_format_without_paragraph(words.join(' '))
+ end
+end
diff --git a/rest_sys/app/models/attachment.rb b/rest_sys/app/models/attachment.rb
new file mode 100644
index 000000000..927aa1735
--- /dev/null
+++ b/rest_sys/app/models/attachment.rb
@@ -0,0 +1,105 @@
+# 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 "digest/md5"
+
+class Attachment < ActiveRecord::Base
+ belongs_to :container, :polymorphic => true
+ belongs_to :author, :class_name => "User", :foreign_key => "author_id"
+
+ validates_presence_of :container, :filename, :author
+ validates_length_of :filename, :maximum => 255
+ validates_length_of :disk_filename, :maximum => 255
+
+ acts_as_event :title => :filename,
+ :description => :filename,
+ :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id}}
+
+ 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)
+ unless incomming_file.nil?
+ @temp_file = incomming_file
+ if @temp_file.size > 0
+ self.filename = sanitize_filename(@temp_file.original_filename)
+ self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
+ self.content_type = @temp_file.content_type.chomp
+ self.filesize = @temp_file.size
+ end
+ end
+ end
+
+ def file
+ nil
+ end
+
+ # Copy temp file to its final location
+ def before_save
+ if @temp_file && (@temp_file.size > 0)
+ logger.debug("saving '#{self.diskfile}'")
+ File.open(diskfile, "wb") do |f|
+ f.write(@temp_file.read)
+ end
+ self.digest = Digest::MD5.hexdigest(File.read(diskfile))
+ end
+ # Don't save the content type if it's longer than the authorized length
+ if self.content_type && self.content_type.length > 255
+ self.content_type = nil
+ end
+ end
+
+ # Deletes file on the disk
+ def after_destroy
+ if self.filename?
+ File.delete(diskfile) if File.exist?(diskfile)
+ end
+ end
+
+ # Returns file's location on disk
+ def diskfile
+ "#{@@storage_path}/#{self.disk_filename}"
+ end
+
+ def increment_download
+ increment!(:downloads)
+ end
+
+ def project
+ container.project
+ end
+
+ def image?
+ self.filename =~ /\.(jpeg|jpg|gif|png)$/i
+ end
+
+private
+ def sanitize_filename(value)
+ # get only the filename, not the whole path
+ just_filename = value.gsub(/^.*(\\|\/)/, '')
+ # NOTE: File.basename doesn't work right with Windows paths on Unix
+ # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
+
+ # Finally, replace all non alphanumeric, underscore or periods with underscore
+ @filename = just_filename.gsub(/[^\w\.\-]/,'_')
+ end
+
+end
diff --git a/rest_sys/app/models/auth_source.rb b/rest_sys/app/models/auth_source.rb
new file mode 100644
index 000000000..47c121a13
--- /dev/null
+++ b/rest_sys/app/models/auth_source.rb
@@ -0,0 +1,51 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class AuthSource < ActiveRecord::Base
+ has_many :users
+
+ validates_presence_of :name
+ validates_uniqueness_of :name
+ validates_length_of :name, :host, :maximum => 60
+ validates_length_of :account_password, :maximum => 60, :allow_nil => true
+ validates_length_of :account, :base_dn, :maximum => 255
+ validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30
+
+ def authenticate(login, password)
+ end
+
+ def test_connection
+ end
+
+ def auth_method_name
+ "Abstract"
+ end
+
+ # Try to authenticate a user not yet registered against available sources
+ def self.authenticate(login, password)
+ AuthSource.find(:all, :conditions => ["onthefly_register=?", true]).each do |source|
+ begin
+ logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
+ attrs = source.authenticate(login, password)
+ rescue
+ attrs = nil
+ end
+ return attrs if attrs
+ end
+ return nil
+ end
+end
diff --git a/rest_sys/app/models/auth_source_ldap.rb b/rest_sys/app/models/auth_source_ldap.rb
new file mode 100644
index 000000000..b79b3ced0
--- /dev/null
+++ b/rest_sys/app/models/auth_source_ldap.rb
@@ -0,0 +1,82 @@
+# 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 'net/ldap'
+require 'iconv'
+
+class AuthSourceLdap < AuthSource
+ validates_presence_of :host, :port, :attr_login
+ validates_presence_of :attr_firstname, :attr_lastname, :attr_mail, :if => Proc.new { |a| a.onthefly_register? }
+
+ def after_initialize
+ self.port = 389 if self.port == 0
+ end
+
+ def authenticate(login, password)
+ attrs = []
+ # get user's DN
+ ldap_con = initialize_ldap_con(self.account, self.account_password)
+ login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
+ object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
+ dn = String.new
+ ldap_con.search( :base => self.base_dn,
+ :filter => object_filter & login_filter,
+ # only ask for the DN if on-the-fly registration is disabled
+ :attributes=> (onthefly_register? ? ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] : ['dn'])) do |entry|
+ dn = entry.dn
+ attrs = [:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
+ :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
+ :mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
+ :auth_source_id => self.id ] if onthefly_register?
+ end
+ return nil if dn.empty?
+ logger.debug "DN found for #{login}: #{dn}" if logger && logger.debug?
+ # authenticate user
+ ldap_con = initialize_ldap_con(dn, password)
+ return nil unless ldap_con.bind
+ # return user's attributes
+ logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
+ attrs
+ rescue Net::LDAP::LdapError => text
+ raise "LdapError: " + text
+ end
+
+ # test the connection to the LDAP
+ def test_connection
+ ldap_con = initialize_ldap_con(self.account, self.account_password)
+ ldap_con.open { }
+ rescue Net::LDAP::LdapError => text
+ raise "LdapError: " + text
+ end
+
+ def auth_method_name
+ "LDAP"
+ end
+
+private
+ def initialize_ldap_con(ldap_user, ldap_password)
+ Net::LDAP.new( {:host => self.host,
+ :port => self.port,
+ :auth => { :method => :simple, :username => ldap_user, :password => ldap_password },
+ :encryption => (self.tls ? :simple_tls : nil)}
+ )
+ end
+
+ def self.get_attr(entry, attr_name)
+ entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
+ end
+end
diff --git a/rest_sys/app/models/board.rb b/rest_sys/app/models/board.rb
new file mode 100644
index 000000000..26e2004d3
--- /dev/null
+++ b/rest_sys/app/models/board.rb
@@ -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
diff --git a/rest_sys/app/models/change.rb b/rest_sys/app/models/change.rb
new file mode 100644
index 000000000..d14f435a4
--- /dev/null
+++ b/rest_sys/app/models/change.rb
@@ -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
diff --git a/rest_sys/app/models/changeset.rb b/rest_sys/app/models/changeset.rb
new file mode 100644
index 000000000..1b79104c4
--- /dev/null
+++ b/rest_sys/app/models/changeset.rb
@@ -0,0 +1,104 @@
+# 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
+
+ acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.comments.blank? ? '' : (': ' + o.comments))},
+ :description => :comments,
+ :datetime => :committed_on,
+ :author => :committer,
+ :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
+
+ acts_as_searchable :columns => 'comments',
+ :include => :repository,
+ :project_key => "#{Repository.table_name}.project_id",
+ :date_column => 'committed_on'
+
+ 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 comments=(comment)
+ write_attribute(:comments, comment.strip)
+ end
+
+ 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(",").collect(&:strip)
+ # keywords used to fix issues
+ fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
+ # status and optional done ratio applied
+ fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
+ done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
+
+ kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
+ return if kw_regexp.blank?
+
+ referenced_issues = []
+
+ if ref_keywords.delete('*')
+ # find any issue ID in the comments
+ target_issue_ids = []
+ comments.scan(%r{([\s\(,-^])#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
+ referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
+ end
+
+ 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.done_ratio = done_ratio if done_ratio
+ issue.save
+ end
+ end
+ referenced_issues += target_issues
+ end
+
+ self.issues = referenced_issues.uniq
+ end
+
+ # Returns the previous changeset
+ def previous
+ @previous ||= Changeset.find(:first, :conditions => ['revision < ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision DESC')
+ end
+
+ # Returns the next changeset
+ def next
+ @next ||= Changeset.find(:first, :conditions => ['revision > ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision ASC')
+ end
+end
diff --git a/rest_sys/app/models/comment.rb b/rest_sys/app/models/comment.rb
new file mode 100644
index 000000000..88d5348da
--- /dev/null
+++ b/rest_sys/app/models/comment.rb
@@ -0,0 +1,23 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Comment < ActiveRecord::Base
+ belongs_to :commented, :polymorphic => true, :counter_cache => true
+ belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
+
+ validates_presence_of :commented, :author, :comments
+end
diff --git a/rest_sys/app/models/custom_field.rb b/rest_sys/app/models/custom_field.rb
new file mode 100644
index 000000000..5a134c4ec
--- /dev/null
+++ b/rest_sys/app/models/custom_field.rb
@@ -0,0 +1,70 @@
+# 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 CustomField < ActiveRecord::Base
+ has_many :custom_values, :dependent => :delete_all
+ acts_as_list :scope => 'type = \'#{self.class}\''
+ serialize :possible_values
+
+ FIELD_FORMATS = { "string" => { :name => :label_string, :order => 1 },
+ "text" => { :name => :label_text, :order => 2 },
+ "int" => { :name => :label_integer, :order => 3 },
+ "float" => { :name => :label_float, :order => 4 },
+ "list" => { :name => :label_list, :order => 5 },
+ "date" => { :name => :label_date, :order => 6 },
+ "bool" => { :name => :label_boolean, :order => 7 }
+ }.freeze
+
+ validates_presence_of :name, :field_format
+ validates_uniqueness_of :name
+ validates_length_of :name, :maximum => 30
+ validates_format_of :name, :with => /^[\w\s\'\-]*$/i
+ validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys
+
+ 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
+ # make sure these fields are not searchable
+ self.searchable = false if %w(int float date bool).include?(field_format)
+ true
+ 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
+
+ def <=>(field)
+ position <=> field.position
+ end
+
+ # to move in project_custom_field
+ def self.for_all
+ find(:all, :conditions => ["is_for_all=?", true])
+ end
+
+ def type_name
+ nil
+ end
+end
diff --git a/rest_sys/app/models/custom_value.rb b/rest_sys/app/models/custom_value.rb
new file mode 100644
index 000000000..c3d6b7bb9
--- /dev/null
+++ b/rest_sys/app/models/custom_value.rb
@@ -0,0 +1,39 @@
+# 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 CustomValue < ActiveRecord::Base
+ belongs_to :custom_field
+ belongs_to :customized, :polymorphic => true
+
+protected
+ def validate
+ errors.add(:value, :activerecord_error_blank) and return if custom_field.is_required? and value.blank?
+ 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_long) if custom_field.max_length > 0 and value.length > custom_field.max_length
+ case custom_field.field_format
+ when 'int'
+ errors.add(:value, :activerecord_error_not_a_number) unless value.blank? || value =~ /^[+-]?\d+$/
+ when 'float'
+ begin; !value.blank? && Kernel.Float(value); rescue; errors.add(:value, :activerecord_error_invalid) end
+ when 'date'
+ errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ or value.blank?
+ when 'list'
+ errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.include?(value) or value.blank?
+ end
+ end
+end
diff --git a/rest_sys/app/models/document.rb b/rest_sys/app/models/document.rb
new file mode 100644
index 000000000..7a432b46b
--- /dev/null
+++ b/rest_sys/app/models/document.rb
@@ -0,0 +1,30 @@
+# 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 Document < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
+ has_many :attachments, :as => :container, :dependent => :destroy
+
+ acts_as_searchable :columns => ['title', 'description']
+ acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"},
+ :author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC")) ? a.author : nil },
+ :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}}
+
+ validates_presence_of :project, :title, :category
+ validates_length_of :title, :maximum => 60
+end
diff --git a/rest_sys/app/models/enabled_module.rb b/rest_sys/app/models/enabled_module.rb
new file mode 100644
index 000000000..3c05c76c1
--- /dev/null
+++ b/rest_sys/app/models/enabled_module.rb
@@ -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 EnabledModule < ActiveRecord::Base
+ belongs_to :project
+
+ validates_presence_of :name
+ validates_uniqueness_of :name, :scope => :project_id
+end
diff --git a/rest_sys/app/models/enumeration.rb b/rest_sys/app/models/enumeration.rb
new file mode 100644
index 000000000..400681a43
--- /dev/null
+++ b/rest_sys/app/models/enumeration.rb
@@ -0,0 +1,67 @@
+# 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 Enumeration < ActiveRecord::Base
+ acts_as_list :scope => 'opt = \'#{opt}\''
+
+ before_destroy :check_integrity
+
+ validates_presence_of :opt, :name
+ validates_uniqueness_of :name, :scope => [:opt]
+ validates_length_of :name, :maximum => 30
+ validates_format_of :name, :with => /^[\w\s\'\-]*$/i
+
+ OPTIONS = {
+ "IPRI" => :enumeration_issue_priorities,
+ "DCAT" => :enumeration_doc_categories,
+ "ACTI" => :enumeration_activities
+ }.freeze
+
+ def self.get_values(option)
+ find(:all, :conditions => {:opt => option}, :order => 'position')
+ end
+
+ def self.default(option)
+ find(:first, :conditions => {:opt => option, :is_default => true}, :order => 'position')
+ end
+
+ def option_name
+ OPTIONS[self.opt]
+ end
+
+ def before_save
+ Enumeration.update_all("is_default = #{connection.quoted_false}", {:opt => opt}) if is_default?
+ end
+
+ def <=>(enumeration)
+ position <=> enumeration.position
+ end
+
+ def to_s; name end
+
+private
+ def check_integrity
+ case self.opt
+ when "IPRI"
+ raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id])
+ when "DCAT"
+ raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id])
+ when "ACTI"
+ raise "Can't delete enumeration" if TimeEntry.find(:first, :conditions => ["activity_id=?", self.id])
+ end
+ end
+end
diff --git a/rest_sys/app/models/issue.rb b/rest_sys/app/models/issue.rb
new file mode 100644
index 000000000..f7b01ea6a
--- /dev/null
+++ b/rest_sys/app/models/issue.rb
@@ -0,0 +1,221 @@
+# 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 Issue < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :tracker
+ belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
+ belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
+ belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
+ belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
+ belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
+ belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
+
+ has_many :journals, :as => :journalized, :dependent => :destroy
+ has_many :attachments, :as => :container, :dependent => :destroy
+ has_many :time_entries, :dependent => :nullify
+ has_many :custom_values, :dependent => :delete_all, :as => :customized
+ has_many :custom_fields, :through => :custom_values
+ has_and_belongs_to_many :changesets, :order => "revision ASC"
+
+ has_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
+ acts_as_searchable :columns => ['subject', 'description'], :with => {:journal => :issue}
+ acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
+ :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}
+
+ validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status
+ validates_length_of :subject, :maximum => 255
+ validates_inclusion_of :done_ratio, :in => 0..100
+ validates_numericality_of :estimated_hours, :allow_nil => true
+ validates_associated :custom_values, :on => :update
+
+ def after_initialize
+ if new_record?
+ # set default values for new records only
+ self.status ||= IssueStatus.default
+ self.priority ||= Enumeration.default('IPRI')
+ end
+ end
+
+ def copy_from(arg)
+ issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
+ self.attributes = issue.attributes.dup
+ self.custom_values = issue.custom_values.collect {|v| v.clone}
+ self
+ end
+
+ # Move an issue to a new project and tracker
+ def move_to(new_project, new_tracker = nil)
+ transaction do
+ if new_project && project_id != new_project.id
+ # delete issue relations
+ self.relations_from.clear
+ self.relations_to.clear
+ # issue is moved to another project
+ self.category = nil
+ self.fixed_version = nil
+ self.project = new_project
+ end
+ if new_tracker
+ self.tracker = new_tracker
+ end
+ if save
+ # Manually update project_id on related time entries
+ TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
+ else
+ rollback_db_transaction
+ return false
+ end
+ end
+ return true
+ end
+
+ def priority_id=(pid)
+ self.priority = nil
+ write_attribute(:priority_id, pid)
+ end
+
+ def validate
+ if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
+ errors.add :due_date, :activerecord_error_not_a_date
+ end
+
+ if self.due_date and self.start_date and self.due_date < self.start_date
+ errors.add :due_date, :activerecord_error_greater_than_start_date
+ end
+
+ if start_date && soonest_start && start_date < soonest_start
+ errors.add :start_date, :activerecord_error_invalid
+ end
+ end
+
+ def validate_on_create
+ errors.add :tracker_id, :activerecord_error_invalid unless project.trackers.include?(tracker)
+ end
+
+ def before_create
+ # default assignment based on category
+ if assigned_to.nil? && category && category.assigned_to
+ self.assigned_to = category.assigned_to
+ end
+ end
+
+ def before_save
+ if @current_journal
+ # attributes changes
+ (Issue.column_names - %w(id description)).each {|c|
+ @current_journal.details << JournalDetail.new(:property => 'attr',
+ :prop_key => c,
+ :old_value => @issue_before_change.send(c),
+ :value => send(c)) unless send(c)==@issue_before_change.send(c)
+ }
+ # custom fields changes
+ custom_values.each {|c|
+ next if (@custom_values_before_change[c.custom_field_id]==c.value ||
+ (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
+ @current_journal.details << JournalDetail.new(:property => 'cf',
+ :prop_key => c.custom_field_id,
+ :old_value => @custom_values_before_change[c.custom_field_id],
+ :value => c.value)
+ }
+ @current_journal.save
+ end
+ # Save the issue even if the journal is not saved (because empty)
+ true
+ end
+
+ def after_save
+ # Update start/due dates of following issues
+ relations_from.each(&:set_issue_to_dates)
+
+ # Close duplicates if the issue was closed
+ if @issue_before_change && !@issue_before_change.closed? && self.closed?
+ duplicates.each do |duplicate|
+ # Don't re-close it if it's already closed
+ next if duplicate.closed?
+ # Same user and notes
+ duplicate.init_journal(@current_journal.user, @current_journal.notes)
+ duplicate.update_attribute :status, self.status
+ end
+ end
+ end
+
+ def custom_value_for(custom_field)
+ self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
+ return nil
+ end
+
+ def init_journal(user, notes = "")
+ @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
+ @issue_before_change = self.clone
+ @custom_values_before_change = {}
+ self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
+ @current_journal
+ end
+
+ # Return true if the issue is closed, otherwise false
+ def closed?
+ self.status.is_closed?
+ end
+
+ # Users the issue can be assigned to
+ def assignable_users
+ project.assignable_users
+ end
+
+ # Returns the mail adresses of users that should be notified for the issue
+ def recipients
+ recipients = project.recipients
+ # Author and assignee are always notified
+ recipients << author.mail if author
+ recipients << assigned_to.mail if assigned_to
+ recipients.compact.uniq
+ end
+
+ def spent_hours
+ @spent_hours ||= time_entries.sum(:hours) || 0
+ end
+
+ def relations
+ (relations_from + relations_to).sort
+ 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
+
+ # Returns an array of the duplicate issues
+ def duplicates
+ relations.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.other_issue(self)}
+ 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
diff --git a/rest_sys/app/models/issue_category.rb b/rest_sys/app/models/issue_category.rb
new file mode 100644
index 000000000..51baeb419
--- /dev/null
+++ b/rest_sys/app/models/issue_category.rb
@@ -0,0 +1,43 @@
+# 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 IssueCategory < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
+ has_many :issues, :foreign_key => 'category_id', :dependent => :nullify
+
+ validates_presence_of :name
+ validates_uniqueness_of :name, :scope => [:project_id]
+ validates_length_of :name, :maximum => 30
+
+ alias :destroy_without_reassign :destroy
+
+ # Destroy the category
+ # If a category is specified, issues are reassigned to this category
+ def destroy(reassign_to = nil)
+ if reassign_to && reassign_to.is_a?(IssueCategory) && reassign_to.project == self.project
+ Issue.update_all("category_id = #{reassign_to.id}", "category_id = #{id}")
+ end
+ destroy_without_reassign
+ end
+
+ def <=>(category)
+ name <=> category.name
+ end
+
+ def to_s; name end
+end
diff --git a/rest_sys/app/models/issue_custom_field.rb b/rest_sys/app/models/issue_custom_field.rb
new file mode 100644
index 000000000..d087768a4
--- /dev/null
+++ b/rest_sys/app/models/issue_custom_field.rb
@@ -0,0 +1,27 @@
+# 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 IssueCustomField < CustomField
+ 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 => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :foreign_key => "custom_field_id"
+ has_many :issues, :through => :issue_custom_values
+
+ def type_name
+ :label_issue_plural
+ end
+end
+
diff --git a/rest_sys/app/models/issue_relation.rb b/rest_sys/app/models/issue_relation.rb
new file mode 100644
index 000000000..07e940b85
--- /dev/null
+++ b/rest_sys/app/models/issue_relation.rb
@@ -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 || Setting.cross_project_issue_relations?
+ 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
diff --git a/rest_sys/app/models/issue_status.rb b/rest_sys/app/models/issue_status.rb
new file mode 100644
index 000000000..a5d228405
--- /dev/null
+++ b/rest_sys/app/models/issue_status.rb
@@ -0,0 +1,65 @@
+# 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 IssueStatus < ActiveRecord::Base
+ before_destroy :check_integrity
+ has_many :workflows, :foreign_key => "old_status_id", :dependent => :delete_all
+ acts_as_list
+
+ validates_presence_of :name
+ validates_uniqueness_of :name
+ validates_length_of :name, :maximum => 30
+ validates_format_of :name, :with => /^[\w\s\'\-]*$/i
+
+ def before_save
+ IssueStatus.update_all "is_default=#{connection.quoted_false}" if self.is_default?
+ end
+
+ # Returns the default status for new issues
+ def self.default
+ find(:first, :conditions =>["is_default=?", true])
+ end
+
+ # Returns an array of all statuses the given role can switch to
+ # Uses association cache when called more than one time
+ def new_statuses_allowed_to(role, tracker)
+ new_statuses = workflows.select {|w| w.role_id == role.id && w.tracker_id == tracker.id}.collect{|w| w.new_status} if role && tracker
+ new_statuses ? new_statuses.compact.sort{|x, y| x.position <=> y.position } : []
+ end
+
+ # Same thing as above but uses a database query
+ # More efficient than the previous method if called just once
+ def find_new_statuses_allowed_to(role, tracker)
+ new_statuses = workflows.find(:all,
+ :include => :new_status,
+ :conditions => ["role_id=? and tracker_id=?", role.id, tracker.id]).collect{ |w| w.new_status }.compact if role && tracker
+ new_statuses ? new_statuses.sort{|x, y| x.position <=> y.position } : []
+ end
+
+ def new_status_allowed_to?(status, role, tracker)
+ status && role && tracker ?
+ !workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => role.id, :tracker_id => tracker.id}).nil? :
+ false
+ end
+
+ def to_s; name end
+
+private
+ def check_integrity
+ raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
+ end
+end
diff --git a/rest_sys/app/models/journal.rb b/rest_sys/app/models/journal.rb
new file mode 100644
index 000000000..64483d21d
--- /dev/null
+++ b/rest_sys/app/models/journal.rb
@@ -0,0 +1,47 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Journal < ActiveRecord::Base
+ belongs_to :journalized, :polymorphic => true
+ # 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
+ has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
+
+ acts_as_searchable :columns => 'notes',
+ :include => :issue,
+ :project_key => "#{Issue.table_name}.project_id",
+ :date_column => "#{Issue.table_name}.created_on"
+
+ acts_as_event :title => Proc.new {|o| "#{o.issue.tracker.name} ##{o.issue.id}: #{o.issue.subject}" + ((s = o.new_status) ? " (#{s})" : '') },
+ :description => :notes,
+ :author => :user,
+ :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id}}
+
+ def save
+ # Do not save an empty journal
+ (details.empty? && notes.blank?) ? false : super
+ end
+
+ # Returns the new status if the journal contains a status change, otherwise nil
+ def new_status
+ c = details.detect {|detail| detail.prop_key == 'status_id'}
+ (c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil
+ end
+end
diff --git a/rest_sys/app/models/journal_detail.rb b/rest_sys/app/models/journal_detail.rb
new file mode 100644
index 000000000..58239006b
--- /dev/null
+++ b/rest_sys/app/models/journal_detail.rb
@@ -0,0 +1,25 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class JournalDetail < ActiveRecord::Base
+ 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
diff --git a/rest_sys/app/models/mail_handler.rb b/rest_sys/app/models/mail_handler.rb
new file mode 100644
index 000000000..7a1d73244
--- /dev/null
+++ b/rest_sys/app/models/mail_handler.rb
@@ -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 user.allowed_to?(:add_issue_notes, issue.project)
+
+ # add the note
+ issue.init_journal(user, email.body.chomp)
+ issue.save
+ end
+end
diff --git a/rest_sys/app/models/mailer.rb b/rest_sys/app/models/mailer.rb
new file mode 100644
index 000000000..257f57726
--- /dev/null
+++ b/rest_sys/app/models/mailer.rb
@@ -0,0 +1,160 @@
+# 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 Mailer < ActionMailer::Base
+ helper ApplicationHelper
+ helper IssuesHelper
+ helper CustomFieldsHelper
+
+ include ActionController::UrlWriter
+
+ def issue_add(issue)
+ recipients issue.recipients
+ subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
+ body :issue => issue,
+ :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
+ end
+
+ def issue_edit(journal)
+ issue = journal.journalized
+ recipients issue.recipients
+ # Watchers in cc
+ cc(issue.watcher_recipients - @recipients)
+ subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
+ body :issue => issue,
+ :journal => journal,
+ :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
+ end
+
+ def document_added(document)
+ recipients document.project.recipients
+ subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
+ body :document => document,
+ :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
+ end
+
+ def attachments_added(attachments)
+ container = attachments.first.container
+ added_to = ''
+ added_to_url = ''
+ case container.class.name
+ when 'Version'
+ added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
+ added_to = "#{l(:label_version)}: #{container.name}"
+ when 'Document'
+ added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
+ added_to = "#{l(:label_document)}: #{container.title}"
+ end
+ recipients container.project.recipients
+ subject "[#{container.project.name}] #{l(:label_attachment_new)}"
+ body :attachments => attachments,
+ :added_to => added_to,
+ :added_to_url => added_to_url
+ end
+
+ def news_added(news)
+ recipients news.project.recipients
+ subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
+ body :news => news,
+ :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
+ end
+
+ def message_posted(message, recipients)
+ recipients(recipients)
+ subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}"
+ body :message => message,
+ :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
+ end
+
+ def account_information(user, password)
+ set_language_if_valid user.language
+ recipients user.mail
+ subject l(:mail_subject_register)
+ body :user => user,
+ :password => password,
+ :login_url => url_for(:controller => 'account', :action => 'login')
+ end
+
+ def account_activation_request(user)
+ # Send the email to all active administrators
+ recipients User.find_active(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
+ subject l(:mail_subject_account_activation_request)
+ body :user => user,
+ :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
+ end
+
+ def lost_password(token)
+ set_language_if_valid(token.user.language)
+ recipients token.user.mail
+ subject l(:mail_subject_lost_password)
+ body :token => token,
+ :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
+ end
+
+ def register(token)
+ set_language_if_valid(token.user.language)
+ recipients token.user.mail
+ subject l(:mail_subject_register)
+ body :token => token,
+ :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
+ end
+
+ def test(user)
+ set_language_if_valid(user.language)
+ recipients user.mail
+ subject 'Redmine test'
+ body :url => url_for(:controller => 'welcome')
+ end
+
+ private
+ def initialize_defaults(method_name)
+ super
+ set_language_if_valid Setting.default_language
+ from Setting.mail_from
+ default_url_options[:host] = Setting.host_name
+ default_url_options[:protocol] = Setting.protocol
+ end
+
+ # Overrides the create_mail method
+ def create_mail
+ # Removes the current user from the recipients and cc
+ # if he doesn't want to receive notifications about what he does
+ if User.current.pref[:no_self_notified]
+ recipients.delete(User.current.mail) if recipients
+ cc.delete(User.current.mail) if cc
+ end
+ # Blind carbon copy recipients
+ if Setting.bcc_recipients?
+ bcc([recipients, cc].flatten.compact.uniq)
+ recipients []
+ cc []
+ end
+ super
+ end
+
+ # Renders a message with the corresponding layout
+ def render_message(method_name, body)
+ layout = method_name.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
+ body[:content_for_layout] = render(:file => method_name, :body => body)
+ ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}")
+ end
+
+ # Makes partial rendering work with Rails 1.2 (retro-compatibility)
+ def self.controller_path
+ ''
+ end unless respond_to?('controller_path')
+end
diff --git a/rest_sys/app/models/member.rb b/rest_sys/app/models/member.rb
new file mode 100644
index 000000000..39703147d
--- /dev/null
+++ b/rest_sys/app/models/member.rb
@@ -0,0 +1,38 @@
+# 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 Member < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :role
+ belongs_to :project
+
+ validates_presence_of :role, :user, :project
+ validates_uniqueness_of :user_id, :scope => :project_id
+
+ def validate
+ errors.add :role_id, :activerecord_error_invalid if role && !role.member?
+ end
+
+ def 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
diff --git a/rest_sys/app/models/message.rb b/rest_sys/app/models/message.rb
new file mode 100644
index 000000000..038665cce
--- /dev/null
+++ b/rest_sys/app/models/message.rb
@@ -0,0 +1,67 @@
+# 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'
+
+ acts_as_searchable :columns => ['subject', 'content'],
+ :include => :board,
+ :project_key => 'project_id',
+ :date_column => 'created_on'
+ acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
+ :description => :content,
+ :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id, :id => o.id}}
+
+ attr_protected :locked, :sticky
+ validates_presence_of :subject, :content
+ validates_length_of :subject, :maximum => 255
+
+ def validate_on_create
+ # Can not reply to a locked topic
+ errors.add_to_base 'Topic is locked' if root.locked?
+ end
+
+ def after_create
+ board.update_attribute(:last_message_id, self.id)
+ board.increment! :messages_count
+ if parent
+ parent.reload.update_attribute(:last_reply_id, self.id)
+ else
+ board.increment! :topics_count
+ end
+ end
+
+ def after_destroy
+ # The following line is required so that the previous counter
+ # updates (due to children removal) are not overwritten
+ board.reload
+ board.decrement! :messages_count
+ board.decrement! :topics_count unless parent
+ end
+
+ def sticky?
+ sticky == 1
+ end
+
+ def project
+ board.project
+ end
+end
diff --git a/rest_sys/app/models/message_observer.rb b/rest_sys/app/models/message_observer.rb
new file mode 100644
index 000000000..1c311e25f
--- /dev/null
+++ b/rest_sys/app/models/message_observer.rb
@@ -0,0 +1,27 @@
+# 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 the authors of the thread
+ recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
+ # send notification to the board watchers
+ recipients += message.board.watcher_recipients
+ recipients = recipients.compact.uniq
+ Mailer.deliver_message_posted(message, recipients) if !recipients.empty? && Setting.notified_events.include?('message_posted')
+ end
+end
diff --git a/rest_sys/app/models/news.rb b/rest_sys/app/models/news.rb
new file mode 100644
index 000000000..3d8c4d661
--- /dev/null
+++ b/rest_sys/app/models/news.rb
@@ -0,0 +1,34 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class News < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
+ has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on"
+
+ validates_presence_of :title, :description
+ validates_length_of :title, :maximum => 60
+ validates_length_of :summary, :maximum => 255
+
+ acts_as_searchable :columns => ['title', 'description']
+ acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
+
+ # returns latest news for projects visible by user
+ def self.latest(user=nil, count=5)
+ find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
+ end
+end
diff --git a/rest_sys/app/models/project.rb b/rest_sys/app/models/project.rb
new file mode 100644
index 000000000..84eeefee4
--- /dev/null
+++ b/rest_sys/app/models/project.rb
@@ -0,0 +1,202 @@
+# 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 Project < ActiveRecord::Base
+ # Project statuses
+ STATUS_ACTIVE = 1
+ STATUS_ARCHIVED = 9
+
+ has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
+ has_many :users, :through => :members
+ has_many :custom_values, :dependent => :delete_all, :as => :customized
+ has_many :enabled_modules, :dependent => :delete_all
+ has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
+ has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
+ has_many :issue_changes, :through => :issues, :source => :journals
+ 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 :documents, :dependent => :destroy
+ has_many :news, :dependent => :delete_all, :include => :author
+ has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
+ has_many :boards, :order => "position ASC"
+ has_one :repository, :dependent => :destroy
+ has_many :changesets, :through => :repository
+ has_one :wiki, :dependent => :destroy
+ # Custom field for the project issues
+ has_and_belongs_to_many :custom_fields,
+ :class_name => 'IssueCustomField',
+ :order => "#{CustomField.table_name}.position",
+ :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_searchable :columns => ['name', 'description'], :project_key => 'id'
+ acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
+ :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}}
+
+ attr_protected :status, :enabled_module_names
+
+ validates_presence_of :name, :description, :identifier
+ validates_uniqueness_of :name, :identifier
+ validates_associated :custom_values, :on => :update
+ validates_associated :repository, :wiki
+ validates_length_of :name, :maximum => 30
+ validates_length_of :description, :maximum => 255
+ validates_length_of :homepage, :maximum => 60
+ validates_length_of :identifier, :in => 3..20
+ validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
+
+ before_destroy :delete_all_members
+
+ 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]
+ # Quick and dirty fix for Rails 2 compatibility
+ Issue.send(:with_scope, :find => { :conditions => conditions }) do
+ yield
+ end
+ end
+
+ # Return all issues status changes for the project between the 2 given dates
+ def issues_status_changes(from, to)
+ Journal.find(:all, :include => [:issue, :details, :user],
+ :conditions => ["#{Journal.table_name}.journalized_type = 'Issue'" +
+ " AND #{Issue.table_name}.project_id = ?" +
+ " AND #{JournalDetail.table_name}.prop_key = 'status_id'" +
+ " AND #{Journal.table_name}.created_on BETWEEN ? AND ?",
+ id, from, to+1])
+ end
+
+ # returns latest created projects
+ # non public projects will be returned only if user is a member of those
+ def self.latest(user=nil, count=5)
+ find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
+ end
+
+ def self.visible_by(user=nil)
+ if user && user.admin?
+ 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 = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
+ else
+ return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
+ 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
+
+ # Deletes all project's members
+ def delete_all_members
+ Member.delete_all(['project_id = ?', id])
+ end
+
+ # Users issues can be assigned to
+ def assignable_users
+ members.select {|m| m.role.assignable?}.collect {|m| m.user}.sort
+ end
+
+ # Returns the mail adresses of users that should be always notified on project events
+ def recipients
+ members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
+ end
+
+ # Returns an array of all custom fields enabled for project issues
+ # (explictly associated custom fields and custom fields enabled for all projects)
+ def custom_fields_for_issues(tracker)
+ all_custom_fields.select {|c| tracker.custom_fields.include? c }
+ end
+
+ def all_custom_fields
+ @all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
+ end
+
+ def <=>(project)
+ name.downcase <=> project.name.downcase
+ end
+
+ def allows_to?(action)
+ if action.is_a? Hash
+ allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
+ else
+ allowed_permissions.include? action
+ end
+ end
+
+ def module_enabled?(module_name)
+ module_name = module_name.to_s
+ enabled_modules.detect {|m| m.name == module_name}
+ end
+
+ def enabled_module_names=(module_names)
+ enabled_modules.clear
+ module_names = [] unless module_names && module_names.is_a?(Array)
+ module_names.each do |name|
+ enabled_modules << EnabledModule.new(:name => name.to_s)
+ end
+ end
+
+protected
+ def validate
+ errors.add(parent_id, " must be a root project") if parent and parent.parent
+ errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
+ end
+
+private
+ def allowed_permissions
+ @allowed_permissions ||= begin
+ module_names = enabled_modules.collect {|m| m.name}
+ Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
+ end
+ end
+
+ def allowed_actions
+ @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
+ end
+end
diff --git a/rest_sys/app/models/project_custom_field.rb b/rest_sys/app/models/project_custom_field.rb
new file mode 100644
index 000000000..f0dab6913
--- /dev/null
+++ b/rest_sys/app/models/project_custom_field.rb
@@ -0,0 +1,22 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class ProjectCustomField < CustomField
+ def type_name
+ :label_project_plural
+ end
+end
diff --git a/rest_sys/app/models/query.rb b/rest_sys/app/models/query.rb
new file mode 100644
index 000000000..ded8be770
--- /dev/null
+++ b/rest_sys/app/models/query.rb
@@ -0,0 +1,342 @@
+# 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 QueryColumn
+ attr_accessor :name, :sortable
+ include GLoc
+
+ def initialize(name, options={})
+ self.name = name
+ self.sortable = options[:sortable]
+ end
+
+ def caption
+ set_language_if_valid(User.current.language)
+ l("field_#{name}")
+ end
+end
+
+class QueryCustomFieldColumn < QueryColumn
+
+ def initialize(custom_field)
+ self.name = "cf_#{custom_field.id}".to_sym
+ self.sortable = false
+ @cf = custom_field
+ end
+
+ def caption
+ @cf.name
+ end
+
+ def custom_field
+ @cf
+ end
+end
+
+class Query < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :user
+ serialize :filters
+ serialize :column_names
+
+ attr_protected :project, :user
+
+ validates_presence_of :name, :on => :save
+ validates_length_of :name, :maximum => 255
+
+ @@operators = { "=" => :label_equals,
+ "!" => :label_not_equals,
+ "o" => :label_open_issues,
+ "c" => :label_closed_issues,
+ "!*" => :label_none,
+ "*" => :label_all,
+ ">=" => '>=',
+ "<=" => '<=',
+ " :label_in_less_than,
+ ">t+" => :label_in_more_than,
+ "t+" => :label_in,
+ "t" => :label_today,
+ "w" => :label_this_week,
+ ">t-" => :label_less_than_ago,
+ " :label_more_than_ago,
+ "t-" => :label_ago,
+ "~" => :label_contains,
+ "!~" => :label_not_contains }
+
+ cattr_reader :operators
+
+ @@operators_by_filter_type = { :list => [ "=", "!" ],
+ :list_status => [ "o", "=", "!", "c", "*" ],
+ :list_optional => [ "=", "!", "!*", "*" ],
+ :list_one_or_more => [ "*", "=" ],
+ :date => [ "t+", "t+", "t", "w", ">t-", " [ ">t-", " [ "=", "~", "!", "!~" ],
+ :text => [ "~", "!~" ],
+ :integer => [ "=", ">=", "<=" ] }
+
+ cattr_reader :operators_by_filter_type
+
+ @@available_columns = [
+ QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
+ QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
+ QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position"),
+ QueryColumn.new(:subject),
+ QueryColumn.new(:assigned_to, :sortable => "#{User.table_name}.lastname"),
+ QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on"),
+ QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
+ QueryColumn.new(:fixed_version),
+ QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
+ QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
+ QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
+ QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
+ QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on"),
+ ]
+ cattr_reader :available_columns
+
+ def initialize(attributes = nil)
+ super attributes
+ self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
+ set_language_if_valid(User.current.language)
+ end
+
+ def validate
+ filters.each_key do |field|
+ errors.add label_for(field), :activerecord_error_blank unless
+ # filter requires one or more values
+ (values_for(field) and !values_for(field).first.empty?) or
+ # filter doesn't require any value
+ ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
+ end if filters
+ end
+
+ def editable_by?(user)
+ return false unless user
+ return true if !is_public && self.user_id == user.id
+ is_public && user.allowed_to?(:manage_public_queries, project)
+ end
+
+ def available_filters
+ return @available_filters if @available_filters
+ @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, :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] } },
+ "subject" => { :type => :text, :order => 8 },
+ "created_on" => { :type => :date_past, :order => 9 },
+ "updated_on" => { :type => :date_past, :order => 10 },
+ "start_date" => { :type => :date, :order => 11 },
+ "due_date" => { :type => :date, :order => 12 },
+ "done_ratio" => { :type => :integer, :order => 13 }}
+
+ user_values = []
+ user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+ if project
+ user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
+ else
+ # members of the user's projects
+ user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
+ end
+ @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
+ @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
+
+ if project
+ # project specific filters
+ @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
+ @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
+ 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 "text"
+ options = { :type => :text, :order => 20 }
+ when "list"
+ options = { :type => :list_optional, :values => field.possible_values, :order => 20}
+ when "date"
+ options = { :type => :date, :order => 20 }
+ when "bool"
+ options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
+ else
+ options = { :type => :string, :order => 20 }
+ end
+ @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
+ end
+ # remove category filter if no category defined
+ @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
+ end
+ @available_filters
+ end
+
+ def add_filter(field, operator, values)
+ # values must be an array
+ return unless values and values.is_a? Array # and !values.first.empty?
+ # check if field is defined as an available filter
+ if available_filters.has_key? field
+ filter_options = available_filters[field]
+ # check if operator is allowed for that filter
+ #if @@operators_by_filter_type[filter_options[:type]].include? operator
+ # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
+ # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
+ #end
+ filters[field] = {:operator => operator, :values => values }
+ end
+ end
+
+ def add_short_filter(field, expression)
+ return unless expression
+ parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
+ add_filter field, (parms[0] || "="), [parms[1] || ""]
+ end
+
+ def has_filter?(field)
+ filters and filters[field]
+ end
+
+ def operator_for(field)
+ has_filter?(field) ? filters[field][:operator] : nil
+ end
+
+ def values_for(field)
+ has_filter?(field) ? filters[field][:values] : nil
+ end
+
+ def label_for(field)
+ label = @available_filters[field][:name] if @available_filters.has_key?(field)
+ label ||= field.gsub(/\_id$/, "")
+ end
+
+ def available_columns
+ return @available_columns if @available_columns
+ @available_columns = Query.available_columns
+ @available_columns += (project ?
+ project.custom_fields :
+ IssueCustomField.find(:all, :conditions => {:is_for_all => true})
+ ).collect {|cf| QueryCustomFieldColumn.new(cf) }
+ end
+
+ def columns
+ if has_default_columns?
+ available_columns.select {|c| Setting.issue_list_default_columns.include?(c.name.to_s) }
+ else
+ # preserve the column_names order
+ column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
+ end
+ end
+
+ def column_names=(names)
+ names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
+ names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
+ write_attribute(:column_names, names)
+ end
+
+ def has_column?(column)
+ column_names && column_names.include?(column.name)
+ end
+
+ def has_default_columns?
+ column_names.nil? || column_names.empty?
+ end
+
+ def statement
+ # project/subprojects clause
+ clause = ''
+ if project && 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
+ clause << "#{Issue.table_name}.project_id IN (%d,%s)" % [project.id, subproject_ids.join(",")] if project
+ elsif project
+ clause << "#{Issue.table_name}.project_id=%d" % project.id
+ else
+ clause << Project.visible_by(User.current)
+ end
+
+ # filters clauses
+ filters_clauses = []
+ filters.each_key do |field|
+ next if field == "subproject_id"
+ v = values_for(field).clone
+ next unless v and !v.empty?
+
+ sql = ''
+ if field =~ /^cf_(\d+)$/
+ # custom field
+ db_table = CustomValue.table_name
+ db_field = 'value'
+ sql << "#{Issue.table_name}.id IN (SELECT #{db_table}.customized_id FROM #{db_table} where #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} AND "
+ else
+ # regular field
+ db_table = Issue.table_name
+ db_field = field
+ sql << '('
+ end
+
+ # "me" value subsitution
+ if %w(assigned_to_id author_id).include?(field)
+ v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
+ end
+
+ case operator_for field
+ when "="
+ sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
+ when "!"
+ sql = sql + "#{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
+ when "!*"
+ sql = sql + "#{db_table}.#{db_field} IS NULL"
+ when "*"
+ sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
+ when ">="
+ sql = sql + "#{db_table}.#{db_field} >= #{v.first.to_i}"
+ when "<="
+ sql = sql + "#{db_table}.#{db_field} <= #{v.first.to_i}"
+ when "o"
+ sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
+ when "c"
+ sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
+ when ">t-"
+ 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+"
+ sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
+ when " :destroy, :order => "#{Changeset.table_name}.revision DESC"
+ has_many :changes, :through => :changesets
+
+ def scm
+ @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 supports_annotate?
+ scm.supports_annotate?
+ 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
+
+ # Default behaviour: we search in cached changesets
+ def changesets_for_path(path)
+ path = "/#{path}" unless path.starts_with?('/')
+ Change.find(:all, :include => :changeset,
+ :conditions => ["repository_id = ? AND path = ?", id, path],
+ :order => "committed_on DESC, #{Changeset.table_name}.revision DESC").collect(&:changeset)
+ 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
diff --git a/rest_sys/app/models/repository/bazaar.rb b/rest_sys/app/models/repository/bazaar.rb
new file mode 100644
index 000000000..6e387f957
--- /dev/null
+++ b/rest_sys/app/models/repository/bazaar.rb
@@ -0,0 +1,86 @@
+# 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/bazaar_adapter'
+
+class Repository::Bazaar < Repository
+ attr_protected :root_url
+ validates_presence_of :url
+
+ def scm_adapter
+ Redmine::Scm::Adapters::BazaarAdapter
+ end
+
+ def self.scm_name
+ 'Bazaar'
+ end
+
+ def entries(path=nil, identifier=nil)
+ entries = scm.entries(path, identifier)
+ if entries
+ entries.each do |e|
+ next if e.lastrev.revision.blank?
+ c = Change.find(:first,
+ :include => :changeset,
+ :conditions => ["#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", e.lastrev.revision, id],
+ :order => "#{Changeset.table_name}.revision DESC")
+ if c
+ e.lastrev.identifier = c.changeset.revision
+ e.lastrev.name = c.changeset.revision
+ e.lastrev.author = c.changeset.committer
+ end
+ end
+ end
+ 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,
+ :scmid => revision.scmid,
+ :comments => revision.message)
+
+ revision.paths.each do |change|
+ Change.create(:changeset => changeset,
+ :action => change[:action],
+ :path => change[:path],
+ :revision => change[:revision])
+ end
+ end
+ end unless revisions.nil?
+ identifier_from = identifier_to + 1
+ end
+ end
+ end
+ end
+end
diff --git a/rest_sys/app/models/repository/cvs.rb b/rest_sys/app/models/repository/cvs.rb
new file mode 100644
index 000000000..16d906316
--- /dev/null
+++ b/rest_sys/app/models/repository/cvs.rb
@@ -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 = changesets.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
diff --git a/rest_sys/app/models/repository/darcs.rb b/rest_sys/app/models/repository/darcs.rb
new file mode 100644
index 000000000..48cc246fb
--- /dev/null
+++ b/rest_sys/app/models/repository/darcs.rb
@@ -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
diff --git a/rest_sys/app/models/repository/mercurial.rb b/rest_sys/app/models/repository/mercurial.rb
new file mode 100644
index 000000000..5d9ea9cd4
--- /dev/null
+++ b/rest_sys/app/models/repository/mercurial.rb
@@ -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
diff --git a/rest_sys/app/models/repository/subversion.rb b/rest_sys/app/models/repository/subversion.rb
new file mode 100644
index 000000000..a0485608d
--- /dev/null
+++ b/rest_sys/app/models/repository/subversion.rb
@@ -0,0 +1,74 @@
+# 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|svn\+ssh|file):\/\/.+/i
+
+ def scm_adapter
+ Redmine::Scm::Adapters::SubversionAdapter
+ end
+
+ def self.scm_name
+ 'Subversion'
+ end
+
+ def changesets_for_path(path)
+ revisions = scm.revisions(path)
+ revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC") : []
+ 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
diff --git a/rest_sys/app/models/role.rb b/rest_sys/app/models/role.rb
new file mode 100644
index 000000000..015146dc4
--- /dev/null
+++ b/rest_sys/app/models/role.rb
@@ -0,0 +1,108 @@
+# 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 Role < ActiveRecord::Base
+ # Built-in roles
+ BUILTIN_NON_MEMBER = 1
+ BUILTIN_ANONYMOUS = 2
+
+ before_destroy :check_deletable
+ has_many :workflows, :dependent => :delete_all
+ has_many :members
+ acts_as_list
+
+ serialize :permissions
+ attr_protected :builtin
+
+ validates_presence_of :name
+ validates_uniqueness_of :name
+ validates_length_of :name, :maximum => 30
+ validates_format_of :name, :with => /^[\w\s\'\-]*$/i
+
+ def permissions
+ read_attribute(:permissions) || []
+ end
+
+ def permissions=(perms)
+ perms = perms.collect {|p| p.to_sym unless p.blank? }.compact if perms
+ write_attribute(:permissions, perms)
+ end
+
+ def <=>(role)
+ position <=> role.position
+ end
+
+ # Return true if the role is a builtin role
+ def builtin?
+ self.builtin != 0
+ end
+
+ # Return true if the role is a project member role
+ def member?
+ !self.builtin?
+ end
+
+ # Return true if role is allowed to do the specified action
+ # action can be:
+ # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
+ # * a permission Symbol (eg. :edit_project)
+ def allowed_to?(action)
+ if action.is_a? Hash
+ allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
+ else
+ allowed_permissions.include? action
+ end
+ end
+
+ # Return all the permissions that can be given to the role
+ def setable_permissions
+ setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions
+ setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER
+ setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS
+ setable_permissions
+ end
+
+ # Find all the roles that can be given to a project member
+ def self.find_all_givable
+ find(:all, :conditions => {:builtin => 0}, :order => 'position')
+ end
+
+ # Return the builtin 'non member' role
+ def self.non_member
+ find(:first, :conditions => {:builtin => BUILTIN_NON_MEMBER}) || raise('Missing non-member builtin role.')
+ end
+
+ # Return the builtin 'anonymous' role
+ def self.anonymous
+ find(:first, :conditions => {:builtin => BUILTIN_ANONYMOUS}) || raise('Missing anonymous builtin role.')
+ end
+
+
+private
+ def allowed_permissions
+ @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
+ end
+
+ def allowed_actions
+ @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
+ end
+
+ def check_deletable
+ raise "Can't delete role" if members.any?
+ raise "Can't delete builtin role" if builtin?
+ end
+end
diff --git a/rest_sys/app/models/setting.rb b/rest_sys/app/models/setting.rb
new file mode 100644
index 000000000..4d4cf0045
--- /dev/null
+++ b/rest_sys/app/models/setting.rb
@@ -0,0 +1,119 @@
+# 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
+
+ DATE_FORMATS = [
+ '%Y-%m-%d',
+ '%d/%m/%Y',
+ '%d.%m.%Y',
+ '%d-%m-%Y',
+ '%m/%d/%Y',
+ '%d %b %Y',
+ '%d %B %Y',
+ '%b %d, %Y',
+ '%B %d, %Y'
+ ]
+
+ TIME_FORMATS = [
+ '%H:%M',
+ '%I:%M %p'
+ ]
+
+ cattr_accessor :available_settings
+ @@available_settings = YAML::load(File.open("#{RAILS_ROOT}/config/settings.yml"))
+ Redmine::Plugin.registered_plugins.each do |id, plugin|
+ next unless plugin.settings
+ @@available_settings["plugin_#{id}"] = {'default' => plugin.settings[:default], 'serialized' => true}
+ end
+
+ 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' }
+
+ # Hash used to cache setting values
+ @cached_settings = {}
+ @cached_cleared_on = Time.now
+
+ def value
+ v = read_attribute(:value)
+ # Unserialize serialized settings
+ v = YAML::load(v) if @@available_settings[name]['serialized'] && v.is_a?(String)
+ v
+ end
+
+ def value=(v)
+ v = v.to_yaml if v && @@available_settings[name]['serialized']
+ write_attribute(:value, v)
+ end
+
+ # Returns the value of the setting named name
+ def self.[](name)
+ v = @cached_settings[name]
+ v ? v : (@cached_settings[name] = find_or_default(name).value)
+ end
+
+ def self.[]=(name, v)
+ setting = find_or_default(name)
+ setting.value = (v ? v : "")
+ @cached_settings[name] = nil
+ setting.save
+ setting.value
+ end
+
+ # Defines getter and setter for each setting
+ # Then setting values can be read using: Setting.some_setting_name
+ # or set using Setting.some_setting_name = "some value"
+ @@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
+
+ # Checks if settings have changed since the values were read
+ # and clears the cache hash if it's the case
+ # Called once per request
+ def self.check_cache
+ settings_updated_on = Setting.maximum(:updated_on)
+ if settings_updated_on && @cached_cleared_on <= settings_updated_on
+ @cached_settings.clear
+ @cached_cleared_on = Time.now
+ logger.info "Settings cache cleared." if logger
+ end
+ end
+
+private
+ # Returns the Setting instance for the setting named name
+ # (record found in database or new record with default value)
+ def self.find_or_default(name)
+ name = name.to_s
+ raise "There's no setting named #{name}" unless @@available_settings.has_key?(name)
+ setting = find_by_name(name)
+ setting ||= new(:name => name, :value => @@available_settings[name]['default']) if @@available_settings.has_key? name
+ end
+end
diff --git a/rest_sys/app/models/time_entry.rb b/rest_sys/app/models/time_entry.rb
new file mode 100644
index 000000000..905857073
--- /dev/null
+++ b/rest_sys/app/models/time_entry.rb
@@ -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
diff --git a/rest_sys/app/models/token.rb b/rest_sys/app/models/token.rb
new file mode 100644
index 000000000..0e8c2c3e2
--- /dev/null
+++ b/rest_sys/app/models/token.rb
@@ -0,0 +1,44 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Token < ActiveRecord::Base
+ belongs_to :user
+
+ @@validity_time = 1.day
+
+ def before_create
+ self.value = Token.generate_token_value
+ end
+
+ # Return true if token has expired
+ def expired?
+ return Time.now > self.created_on + @@validity_time
+ end
+
+ # Delete all expired tokens
+ def self.destroy_expired
+ Token.delete_all ["action <> 'feeds' AND created_on < ?", Time.now - @@validity_time]
+ end
+
+private
+ def self.generate_token_value
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+ token_value = ''
+ 40.times { |i| token_value << chars[rand(chars.size-1)] }
+ token_value
+ end
+end
diff --git a/rest_sys/app/models/tracker.rb b/rest_sys/app/models/tracker.rb
new file mode 100644
index 000000000..8d8647747
--- /dev/null
+++ b/rest_sys/app/models/tracker.rb
@@ -0,0 +1,44 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Tracker < ActiveRecord::Base
+ before_destroy :check_integrity
+ has_many :issues
+ has_many :workflows, :dependent => :delete_all
+ 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_uniqueness_of :name
+ validates_length_of :name, :maximum => 30
+ validates_format_of :name, :with => /^[\w\s\'\-]*$/i
+
+ def to_s; name end
+
+ def <=>(tracker)
+ name <=> tracker.name
+ end
+
+ def self.all
+ find(:all, :order => 'position')
+ end
+
+private
+ def check_integrity
+ raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])
+ end
+end
diff --git a/rest_sys/app/models/user.rb b/rest_sys/app/models/user.rb
new file mode 100644
index 000000000..737a8cc8e
--- /dev/null
+++ b/rest_sys/app/models/user.rb
@@ -0,0 +1,255 @@
+# 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 "digest/sha1"
+
+class User < ActiveRecord::Base
+ # Account statuses
+ STATUS_ANONYMOUS = 0
+ 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 :custom_values, :dependent => :delete_all, :as => :customized
+ has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
+ has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
+ has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
+ belongs_to :auth_source
+
+ attr_accessor :password, :password_confirmation
+ attr_accessor :last_before_login_on
+ # Prevents unauthorized assignments
+ attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
+
+ validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
+ validates_uniqueness_of :login, :mail
+ # Login must contain lettres, numbers, underscores only
+ validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
+ validates_length_of :login, :maximum => 30
+ validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-]*$/i
+ validates_length_of :firstname, :lastname, :maximum => 30
+ validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
+ validates_length_of :mail, :maximum => 60, :allow_nil => true
+ validates_length_of :password, :minimum => 4, :allow_nil => true
+ validates_confirmation_of :password, :allow_nil => true
+ validates_associated :custom_values, :on => :update
+
+ def before_create
+ self.mail_notification = false
+ true
+ end
+
+ def before_save
+ # update hashed_password if password was set
+ self.hashed_password = User.hash_password(self.password) if self.password
+ 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
+ def self.try_to_login(login, password)
+ user = find(:first, :conditions => ["login=?", login])
+ if user
+ # user is already in local database
+ return nil if !user.active?
+ if user.auth_source
+ # user has an external authentication method
+ return nil unless user.auth_source.authenticate(login, password)
+ else
+ # authentication with local password
+ return nil unless User.hash_password(password) == user.hashed_password
+ end
+ else
+ # user is not yet registered, try to authenticate with available sources
+ attrs = AuthSource.authenticate(login, password)
+ if attrs
+ onthefly = new(*attrs)
+ onthefly.login = login
+ onthefly.language = Setting.default_language
+ if onthefly.save
+ user = find(:first, :conditions => ["login=?", login])
+ logger.info("User '#{user.login}' created on the fly.") if logger
+ end
+ end
+ end
+ user.update_attribute(:last_login_on, Time.now) if user
+ user
+
+ rescue => text
+ raise text
+ end
+
+ # Return user's full name for display
+ def name
+ "#{firstname} #{lastname}"
+ end
+
+ def active?
+ self.status == STATUS_ACTIVE
+ end
+
+ def registered?
+ self.status == STATUS_REGISTERED
+ end
+
+ def locked?
+ self.status == STATUS_LOCKED
+ end
+
+ def check_password?(clear_password)
+ User.hash_password(clear_password) == self.hashed_password
+ end
+
+ def pref
+ self.preference ||= UserPreference.new(:user => self)
+ end
+
+ def time_zone
+ self.pref.time_zone.nil? ? nil : TimeZone[self.pref.time_zone]
+ end
+
+ # Return user's RSS key (a 40 chars long string), used to access feeds
+ def rss_key
+ token = self.rss_token || Token.create(:user => self, :action => 'feeds')
+ token.value
+ end
+
+ # Return an array of project ids for which the user has explicitly turned mail notifications on
+ def notified_projects_ids
+ @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
+ end
+
+ def notified_project_ids=(ids)
+ Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
+ Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
+ @notified_projects_ids = nil
+ notified_projects_ids
+ 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)
+ user.nil? ? -1 : (lastname == user.lastname ? firstname <=> user.firstname : lastname <=> user.lastname)
+ end
+
+ def to_s
+ name
+ end
+
+ def logged?
+ true
+ end
+
+ # Return user's role for project
+ def role_for_project(project)
+ # No role on archived projects
+ return nil unless project && project.active?
+ if logged?
+ # Find project membership
+ membership = memberships.detect {|m| m.project_id == project.id}
+ if membership
+ membership.role
+ else
+ @role_non_member ||= Role.non_member
+ end
+ else
+ @role_anonymous ||= Role.anonymous
+ end
+ end
+
+ # Return true if the user is a member of project
+ def member_of?(project)
+ role_for_project(project).member?
+ end
+
+ # Return true if the user is allowed to do the specified action on project
+ # action can be:
+ # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
+ # * a permission Symbol (eg. :edit_project)
+ def allowed_to?(action, project)
+ # No action allowed on archived projects
+ return false unless project.active?
+ # No action allowed on disabled modules
+ return false unless project.allows_to?(action)
+ # Admin users are authorized for anything else
+ return true if admin?
+
+ role = role_for_project(project)
+ return false unless role
+ role.allowed_to?(action) && (project.is_public? || role.member?)
+ end
+
+ def self.current=(user)
+ @current_user = user
+ end
+
+ def self.current
+ @current_user ||= User.anonymous
+ end
+
+ def self.anonymous
+ return @anonymous_user if @anonymous_user
+ anonymous_user = AnonymousUser.find(:first)
+ if anonymous_user.nil?
+ anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
+ raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
+ end
+ @anonymous_user = anonymous_user
+ end
+
+private
+ # Return password digest
+ def self.hash_password(clear_password)
+ Digest::SHA1.hexdigest(clear_password || "")
+ end
+end
+
+class AnonymousUser < User
+
+ def validate_on_create
+ # There should be only one AnonymousUser in the database
+ errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
+ end
+
+ # Overrides a few properties
+ def logged?; false end
+ def admin; false end
+ def name; 'Anonymous' end
+ def mail; nil end
+ def time_zone; nil end
+ def rss_key; nil end
+end
diff --git a/rest_sys/app/models/user_custom_field.rb b/rest_sys/app/models/user_custom_field.rb
new file mode 100644
index 000000000..99e76eea5
--- /dev/null
+++ b/rest_sys/app/models/user_custom_field.rb
@@ -0,0 +1,23 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class UserCustomField < CustomField
+ def type_name
+ :label_user_plural
+ end
+end
+
diff --git a/rest_sys/app/models/user_preference.rb b/rest_sys/app/models/user_preference.rb
new file mode 100644
index 000000000..1ed9e0fd9
--- /dev/null
+++ b/rest_sys/app/models/user_preference.rb
@@ -0,0 +1,49 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class UserPreference < ActiveRecord::Base
+ belongs_to :user
+ serialize :others
+
+ attr_protected :others
+
+ def initialize(attributes = nil)
+ super
+ self.others ||= {}
+ end
+
+ def before_save
+ self.others ||= {}
+ end
+
+ def [](attr_name)
+ if attribute_present? attr_name
+ super
+ else
+ others ? others[attr_name] : nil
+ end
+ end
+
+ def []=(attr_name, value)
+ if attribute_present? attr_name
+ super
+ else
+ self.others ||= {}
+ self.others.store attr_name, value
+ end
+ end
+end
diff --git a/rest_sys/app/models/version.rb b/rest_sys/app/models/version.rb
new file mode 100644
index 000000000..266346c7b
--- /dev/null
+++ b/rest_sys/app/models/version.rb
@@ -0,0 +1,96 @@
+# 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 Version < ActiveRecord::Base
+ before_destroy :check_integrity
+ belongs_to :project
+ has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
+ has_many :attachments, :as => :container, :dependent => :destroy
+
+ validates_presence_of :name
+ validates_uniqueness_of :name, :scope => [:project_id]
+ validates_length_of :name, :maximum => 30
+ 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
+
+ # Returns true if the version is completed: due date reached and no open issues
+ def completed?
+ effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
+ end
+
+ def completed_pourcent
+ if fixed_issues.count == 0
+ 0
+ elsif open_issues_count == 0
+ 100
+ else
+ (closed_issues_count * 100 + Issue.sum('done_ratio', :include => 'status', :conditions => ["fixed_version_id = ? AND is_closed = ?", id, false]).to_f) / fixed_issues.count
+ end
+ end
+
+ def closed_pourcent
+ if fixed_issues.count == 0
+ 0
+ else
+ closed_issues_count * 100.0 / fixed_issues.count
+ end
+ end
+
+ # Returns true if the version is overdue: due date reached and some open issues
+ def overdue?
+ effective_date && (effective_date < Date.today) && (open_issues_count > 0)
+ end
+
+ def open_issues_count
+ @open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
+ end
+
+ def closed_issues_count
+ @closed_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, true], :include => :status)
+ end
+
+ def wiki_page
+ if project.wiki && !wiki_page_title.blank?
+ @wiki_page ||= project.wiki.find_page(wiki_page_title)
+ end
+ @wiki_page
+ end
+
+ def to_s; name 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
+ def check_integrity
+ raise "Can't delete version" if self.fixed_issues.find(:first)
+ end
+end
diff --git a/rest_sys/app/models/watcher.rb b/rest_sys/app/models/watcher.rb
new file mode 100644
index 000000000..cb6ff52ea
--- /dev/null
+++ b/rest_sys/app/models/watcher.rb
@@ -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
diff --git a/rest_sys/app/models/wiki.rb b/rest_sys/app/models/wiki.rb
new file mode 100644
index 000000000..b6d6a9b50
--- /dev/null
+++ b/rest_sys/app/models/wiki.rb
@@ -0,0 +1,54 @@
+# 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
+ has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all
+
+ 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 = start_page if title.blank?
+ find_page(title) || WikiPage.new(:wiki => self, :title => Wiki.titleize(title))
+ end
+
+ # find the page with the given title
+ def find_page(title, options = {})
+ title = start_page if title.blank?
+ title = Wiki.titleize(title)
+ page = pages.find_by_title(title)
+ if !page && !(options[:with_redirect] == false)
+ # search for a redirect
+ redirect = redirects.find_by_title(title)
+ page = find_page(redirect.redirects_to, :with_redirect => false) if redirect
+ end
+ page
+ 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.slice(0..0).upcase + (title.slice(1..-1) || '')) if title
+ title
+ end
+end
diff --git a/rest_sys/app/models/wiki_content.rb b/rest_sys/app/models/wiki_content.rb
new file mode 100644
index 000000000..d0a48467b
--- /dev/null
+++ b/rest_sys/app/models/wiki_content.rb
@@ -0,0 +1,65 @@
+# 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
+
+ acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"},
+ :description => :comments,
+ :datetime => :updated_on,
+ :url => Proc.new {|o| {:controller => 'wiki', :id => o.page.wiki.project_id, :page => o.page.title, :version => o.version}}
+
+ 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
diff --git a/rest_sys/app/models/wiki_page.rb b/rest_sys/app/models/wiki_page.rb
new file mode 100644
index 000000000..cbca4fd68
--- /dev/null
+++ b/rest_sys/app/models/wiki_page.rb
@@ -0,0 +1,115 @@
+# 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
+
+ acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
+ :description => :text,
+ :datetime => :created_on,
+ :url => Proc.new {|o| {:controller => 'wiki', :id => o.wiki.project_id, :page => o.title}}
+
+ acts_as_searchable :columns => ['title', 'text'],
+ :include => [:wiki, :content],
+ :project_key => "#{Wiki.table_name}.project_id"
+
+ attr_accessor :redirect_existing_links
+
+ validates_presence_of :title
+ validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
+ validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
+ validates_associated :content
+
+ def title=(value)
+ value = Wiki.titleize(value)
+ @previous_title = read_attribute(:title) if @previous_title.blank?
+ write_attribute(:title, value)
+ end
+
+ def before_save
+ self.title = Wiki.titleize(title)
+ # Manage redirects if the title has changed
+ if !@previous_title.blank? && (@previous_title != title) && !new_record?
+ # Update redirects that point to the old title
+ wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
+ r.redirects_to = title
+ r.title == r.redirects_to ? r.destroy : r.save
+ end
+ # Remove redirects for the new title
+ wiki.redirects.find_all_by_title(title).each(&:destroy)
+ # Create a redirect to the new title
+ wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
+ @previous_title = nil
+ end
+ end
+
+ def before_destroy
+ # Remove redirects to this page
+ wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
+ 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
+
+ def text
+ content.text if content
+ 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
diff --git a/rest_sys/app/models/wiki_redirect.rb b/rest_sys/app/models/wiki_redirect.rb
new file mode 100644
index 000000000..adc2b24c1
--- /dev/null
+++ b/rest_sys/app/models/wiki_redirect.rb
@@ -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 WikiRedirect < ActiveRecord::Base
+ belongs_to :wiki
+
+ validates_presence_of :title, :redirects_to
+ validates_length_of :title, :redirects_to, :maximum => 255
+end
diff --git a/rest_sys/app/models/workflow.rb b/rest_sys/app/models/workflow.rb
new file mode 100644
index 000000000..89322aa58
--- /dev/null
+++ b/rest_sys/app/models/workflow.rb
@@ -0,0 +1,24 @@
+# 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 Workflow < ActiveRecord::Base
+ belongs_to :role
+ belongs_to :old_status, :class_name => 'IssueStatus', :foreign_key => 'old_status_id'
+ belongs_to :new_status, :class_name => 'IssueStatus', :foreign_key => 'new_status_id'
+
+ validates_presence_of :role, :old_status, :new_status
+end
diff --git a/rest_sys/app/sweepers/issue_sweeper.rb b/rest_sys/app/sweepers/issue_sweeper.rb
new file mode 100644
index 000000000..dc9020535
--- /dev/null
+++ b/rest_sys/app/sweepers/issue_sweeper.rb
@@ -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
diff --git a/rest_sys/app/sweepers/project_sweeper.rb b/rest_sys/app/sweepers/project_sweeper.rb
new file mode 100644
index 000000000..f64f6f564
--- /dev/null
+++ b/rest_sys/app/sweepers/project_sweeper.rb
@@ -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
diff --git a/rest_sys/app/sweepers/version_sweeper.rb b/rest_sys/app/sweepers/version_sweeper.rb
new file mode 100644
index 000000000..e1323e261
--- /dev/null
+++ b/rest_sys/app/sweepers/version_sweeper.rb
@@ -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 VersionSweeper < ActionController::Caching::Sweeper
+ observe Version
+
+ def after_save(version)
+ expire_cache_for(version)
+ end
+
+ def after_destroy(version)
+ expire_cache_for(version)
+ end
+
+private
+ def expire_cache_for(version)
+ # calendar and gantt fragments of the project
+ expire_fragment(Regexp.new("projects/(calendar|gantt)/#{version.project_id}\\."))
+ end
+end
diff --git a/rest_sys/app/views/account/login.rhtml b/rest_sys/app/views/account/login.rhtml
new file mode 100644
index 000000000..5bfbfb8d6
--- /dev/null
+++ b/rest_sys/app/views/account/login.rhtml
@@ -0,0 +1,33 @@
+
\ No newline at end of file
diff --git a/rest_sys/app/views/admin/_menu.rhtml b/rest_sys/app/views/admin/_menu.rhtml
new file mode 100644
index 000000000..ef2abbc56
--- /dev/null
+++ b/rest_sys/app/views/admin/_menu.rhtml
@@ -0,0 +1,25 @@
+
\ No newline at end of file
diff --git a/rest_sys/app/views/documents/_form.rhtml b/rest_sys/app/views/documents/_form.rhtml
new file mode 100644
index 000000000..d45e295b0
--- /dev/null
+++ b/rest_sys/app/views/documents/_form.rhtml
@@ -0,0 +1,15 @@
+<%= error_messages_for 'document' %>
+
\ No newline at end of file
diff --git a/rest_sys/app/views/enumerations/edit.rhtml b/rest_sys/app/views/enumerations/edit.rhtml
new file mode 100644
index 000000000..7baea028a
--- /dev/null
+++ b/rest_sys/app/views/enumerations/edit.rhtml
@@ -0,0 +1,10 @@
+
<%=l(:label_enumerations)%>
+
+<% form_tag({:action => 'update', :id => @enumeration}, :class => "tabular") do %>
+ <%= render :partial => 'form' %>
+ <%= submit_tag l(:button_save) %>
+<% end %>
+
+<% form_tag({:action => 'destroy', :id => @enumeration}) do %>
+ <%= submit_tag l(:button_delete) %>
+<% end %>
\ No newline at end of file
diff --git a/rest_sys/app/views/enumerations/list.rhtml b/rest_sys/app/views/enumerations/list.rhtml
new file mode 100644
index 000000000..2e069f392
--- /dev/null
+++ b/rest_sys/app/views/enumerations/list.rhtml
@@ -0,0 +1,28 @@
+
<%=l(:label_enumerations)%>
+
+<% Enumeration::OPTIONS.each do |option, name| %>
+
\ No newline at end of file
diff --git a/rest_sys/app/views/issue_statuses/edit.rhtml b/rest_sys/app/views/issue_statuses/edit.rhtml
new file mode 100644
index 000000000..b81426a02
--- /dev/null
+++ b/rest_sys/app/views/issue_statuses/edit.rhtml
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/rest_sys/app/views/my/_sidebar.rhtml b/rest_sys/app/views/my/_sidebar.rhtml
new file mode 100644
index 000000000..d30eacf90
--- /dev/null
+++ b/rest_sys/app/views/my/_sidebar.rhtml
@@ -0,0 +1,8 @@
+
+<%= submit_tag l(:button_add) %>
+<% end %>
\ No newline at end of file
diff --git a/rest_sys/app/views/projects/add_issue.rhtml b/rest_sys/app/views/projects/add_issue.rhtml
new file mode 100644
index 000000000..a68922906
--- /dev/null
+++ b/rest_sys/app/views/projects/add_issue.rhtml
@@ -0,0 +1,19 @@
+
+<% end %>
+ <% top = top + 20
+end %>
+
+<% end # cache
+%>
+
+<%
+#
+# Today red line (excluded from cache)
+#
+if Date.today >= @date_from and Date.today <= @date_to %>
+
<%= l(:label_comment_plural) %>
+<% @news.comments.each do |comment| %> + <% next if comment.new_record? %> +<%= authoring comment.created_on, comment.author %>
+