From 6b7650e2f03156ea1e3985b30c1995e44c317e3d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Wed, 28 Jun 2006 18:11:03 +0000 Subject: [PATCH] Initial commit git-svn-id: http://redmine.rubyforge.org/svn/trunk@4 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- redmine/Rakefile | 10 + redmine/app/controllers/account_controller.rb | 83 + redmine/app/controllers/admin_controller.rb | 49 + redmine/app/controllers/application.rb | 86 + .../controllers/custom_fields_controller.rb | 58 + .../app/controllers/documents_controller.rb | 65 + .../controllers/enumerations_controller.rb | 69 + redmine/app/controllers/help_controller.rb | 43 + .../issue_categories_controller.rb | 42 + .../controllers/issue_statuses_controller.rb | 68 + redmine/app/controllers/issues_controller.rb | 102 + redmine/app/controllers/members_controller.rb | 41 + redmine/app/controllers/news_controller.rb | 42 + .../app/controllers/projects_controller.rb | 260 +++ redmine/app/controllers/reports_controller.rb | 71 + redmine/app/controllers/roles_controller.rb | 84 + .../app/controllers/trackers_controller.rb | 60 + redmine/app/controllers/users_controller.rb | 73 + .../app/controllers/versions_controller.rb | 53 + redmine/app/controllers/welcome_controller.rb | 26 + redmine/app/helpers/account_helper.rb | 19 + redmine/app/helpers/admin_helper.rb | 19 + redmine/app/helpers/application_helper.rb | 65 + redmine/app/helpers/custom_fields_helper.rb | 36 + redmine/app/helpers/documents_helper.rb | 19 + redmine/app/helpers/enumerations_helper.rb | 19 + redmine/app/helpers/help_helper.rb | 19 + .../app/helpers/issue_categories_helper.rb | 19 + redmine/app/helpers/issue_statuses_helper.rb | 19 + redmine/app/helpers/issues_helper.rb | 19 + redmine/app/helpers/members_helper.rb | 19 + redmine/app/helpers/news_helper.rb | 19 + redmine/app/helpers/projects_helper.rb | 19 + redmine/app/helpers/reports_helper.rb | 32 + redmine/app/helpers/roles_helper.rb | 19 + redmine/app/helpers/search_filter_helper.rb | 55 + redmine/app/helpers/sort_helper.rb | 157 ++ redmine/app/helpers/trackers_helper.rb | 19 + redmine/app/helpers/users_helper.rb | 19 + redmine/app/helpers/versions_helper.rb | 19 + redmine/app/helpers/welcome_helper.rb | 19 + redmine/app/models/attachment.rb | 81 + redmine/app/models/custom_field.rb | 38 + redmine/app/models/custom_value.rb | 41 + redmine/app/models/document.rb | 24 + redmine/app/models/enumeration.rb | 45 + redmine/app/models/issue.rb | 55 + redmine/app/models/issue_category.rb | 28 + redmine/app/models/issue_history.rb | 23 + redmine/app/models/issue_status.rb | 47 + redmine/app/models/mailer.rb | 36 + redmine/app/models/member.rb | 29 + redmine/app/models/news.rb | 28 + redmine/app/models/permission.rb | 63 + redmine/app/models/project.rb | 44 + redmine/app/models/role.rb | 31 + redmine/app/models/tracker.rb | 31 + redmine/app/models/user.rb | 89 + redmine/app/models/version.rb | 30 + redmine/app/models/workflow.rb | 25 + redmine/app/views/account/login.rhtml | 13 + redmine/app/views/account/my_account.rhtml | 54 + redmine/app/views/account/my_page.rhtml | 19 + redmine/app/views/account/show.rhtml | 19 + redmine/app/views/admin/index.rhtml | 45 + redmine/app/views/admin/info.rhtml | 4 + redmine/app/views/admin/mail_options.rhtml | 16 + redmine/app/views/admin/projects.rhtml | 35 + redmine/app/views/custom_fields/_form.rhtml | 26 + redmine/app/views/custom_fields/edit.rhtml | 6 + redmine/app/views/custom_fields/list.rhtml | 32 + redmine/app/views/custom_fields/new.rhtml | 7 + redmine/app/views/documents/_form.rhtml | 15 + redmine/app/views/documents/edit.rhtml | 8 + redmine/app/views/documents/show.rhtml | 45 + redmine/app/views/enumerations/_form.rhtml | 9 + redmine/app/views/enumerations/edit.rhtml | 10 + redmine/app/views/enumerations/list.rhtml | 22 + redmine/app/views/enumerations/new.rhtml | 6 + .../app/views/issue_categories/_form.rhtml | 7 + redmine/app/views/issue_categories/edit.rhtml | 6 + redmine/app/views/issue_statuses/_form.rhtml | 17 + redmine/app/views/issue_statuses/edit.rhtml | 6 + redmine/app/views/issue_statuses/list.rhtml | 30 + redmine/app/views/issue_statuses/new.rhtml | 6 + redmine/app/views/issues/_list_simple.rhtml | 28 + redmine/app/views/issues/change_status.rhtml | 29 + redmine/app/views/issues/edit.rhtml | 62 + redmine/app/views/issues/show.rhtml | 90 + redmine/app/views/layouts/base.rhtml | 89 + redmine/app/views/mailer/_issue.rhtml | 6 + redmine/app/views/mailer/issue_add.rhtml | 3 + .../views/mailer/issue_change_status.rhtml | 3 + redmine/app/views/news/_form.rhtml | 13 + redmine/app/views/news/edit.rhtml | 6 + redmine/app/views/news/show.rhtml | 10 + redmine/app/views/projects/_form.rhtml | 28 + redmine/app/views/projects/add.rhtml | 7 + redmine/app/views/projects/add_document.rhtml | 26 + redmine/app/views/projects/add_file.rhtml | 13 + redmine/app/views/projects/add_issue.rhtml | 62 + redmine/app/views/projects/add_news.rhtml | 7 + redmine/app/views/projects/add_version.rhtml | 7 + redmine/app/views/projects/changelog.rhtml | 12 + redmine/app/views/projects/destroy.rhtml | 12 + redmine/app/views/projects/list.rhtml | 22 + .../app/views/projects/list_documents.rhtml | 21 + redmine/app/views/projects/list_files.rhtml | 47 + redmine/app/views/projects/list_issues.rhtml | 56 + redmine/app/views/projects/list_members.rhtml | 11 + redmine/app/views/projects/list_news.rhtml | 17 + redmine/app/views/projects/settings.rhtml | 105 + redmine/app/views/projects/show.rhtml | 53 + redmine/app/views/reports/_simple.rhtml | 34 + redmine/app/views/reports/issue_report.rhtml | 13 + redmine/app/views/roles/_form.rhtml | 22 + redmine/app/views/roles/edit.rhtml | 10 + redmine/app/views/roles/list.rhtml | 23 + redmine/app/views/roles/new.rhtml | 8 + redmine/app/views/roles/workflow.rhtml | 70 + redmine/app/views/trackers/_form.rhtml | 10 + redmine/app/views/trackers/edit.rhtml | 6 + redmine/app/views/trackers/list.rhtml | 24 + redmine/app/views/trackers/new.rhtml | 7 + redmine/app/views/users/_form.rhtml | 28 + redmine/app/views/users/add.rhtml | 6 + redmine/app/views/users/edit.rhtml | 7 + redmine/app/views/users/list.rhtml | 46 + redmine/app/views/versions/_form.rhtml | 13 + redmine/app/views/versions/edit.rhtml | 8 + redmine/app/views/welcome/index.rhtml | 30 + redmine/config/boot.rb | 19 + redmine/config/database.yml | 32 + redmine/config/environment.rb | 85 + redmine/config/environments/demo.rb | 21 + redmine/config/environments/development.rb | 19 + redmine/config/environments/production.rb | 20 + redmine/config/environments/test.rb | 15 + redmine/config/help.yml | 21 + redmine/config/routes.rb | 24 + redmine/db/migrate/001_setup.rb | 254 +++ .../db/migrate/002_default_configuration.rb | 44 + redmine/db/redmine_demo.db | Bin 0 -> 40960 bytes redmine/doc/CHANGELOG | 17 + redmine/doc/COPYING | 339 ++++ redmine/doc/INSTALL | 61 + redmine/doc/README | 49 + redmine/files/delete.me | 1 + redmine/lang/en_US.rb | 4 + redmine/lang/es_ES.rb | 315 +++ redmine/lang/fr_FR.rb | 316 +++ redmine/public/.htaccess | 40 + redmine/public/404.html | 7 + redmine/public/500.html | 7 + redmine/public/dispatch.cgi | 10 + redmine/public/dispatch.fcgi | 24 + redmine/public/dispatch.rb | 10 + redmine/public/favicon.ico | 0 redmine/public/images/Copie de help.png | Bin 0 -> 379 bytes redmine/public/images/admin.png | Bin 0 -> 716 bytes redmine/public/images/bulletgreen.png | Bin 0 -> 193 bytes redmine/public/images/bulletred.png | Bin 0 -> 193 bytes redmine/public/images/delete.png | Bin 0 -> 320 bytes redmine/public/images/dir.png | Bin 0 -> 314 bytes redmine/public/images/dir_new.png | Bin 0 -> 321 bytes redmine/public/images/dir_open.png | Bin 0 -> 1030 bytes redmine/public/images/document.png | Bin 0 -> 1014 bytes redmine/public/images/edit_small.png | Bin 0 -> 238 bytes redmine/public/images/file_new.png | Bin 0 -> 253 bytes redmine/public/images/help.png | Bin 0 -> 1079 bytes redmine/public/images/home.png | Bin 0 -> 301 bytes redmine/public/images/issues.png | Bin 0 -> 356 bytes redmine/public/images/locked.png | Bin 0 -> 437 bytes redmine/public/images/logout.png | Bin 0 -> 342 bytes redmine/public/images/mailer.png | Bin 0 -> 294 bytes redmine/public/images/notes.png | Bin 0 -> 996 bytes redmine/public/images/options.png | Bin 0 -> 1005 bytes redmine/public/images/package.png | Bin 0 -> 298 bytes redmine/public/images/projects.png | Bin 0 -> 299 bytes redmine/public/images/rails.png | Bin 0 -> 1787 bytes redmine/public/images/rails_powered.png | Bin 0 -> 262 bytes redmine/public/images/rails_small.png | Bin 0 -> 1140 bytes redmine/public/images/role.png | Bin 0 -> 293 bytes redmine/public/images/rss.png | Bin 0 -> 256 bytes redmine/public/images/sort_asc.png | Bin 0 -> 215 bytes redmine/public/images/sort_desc.png | Bin 0 -> 217 bytes redmine/public/images/tracker.png | Bin 0 -> 356 bytes redmine/public/images/true.png | Bin 0 -> 183 bytes redmine/public/images/user.png | Bin 0 -> 236 bytes redmine/public/images/user_page.png | Bin 0 -> 292 bytes redmine/public/images/users.png | Bin 0 -> 242 bytes redmine/public/images/workflow.png | Bin 0 -> 285 bytes redmine/public/javascripts/application.js | 8 + redmine/public/javascripts/controls.js | 750 +++++++ redmine/public/javascripts/dragdrop.js | 584 ++++++ redmine/public/javascripts/effects.js | 854 ++++++++ redmine/public/javascripts/prototype.js | 1785 +++++++++++++++++ redmine/public/manual/administration.html | 121 ++ redmine/public/manual/images/issues_list.png | Bin 0 -> 8055 bytes redmine/public/manual/images/users_edit.png | Bin 0 -> 2700 bytes redmine/public/manual/images/users_list.png | Bin 0 -> 6502 bytes redmine/public/manual/images/workflow.png | Bin 0 -> 4967 bytes redmine/public/manual/index.html | 64 + redmine/public/manual/projects.html | 109 + redmine/public/manual/stylesheets/help.css | 70 + redmine/public/robots.txt | 1 + redmine/public/stylesheets/application.css | 322 +++ redmine/public/stylesheets/rails.css | 56 + redmine/script/about | 3 + redmine/script/breakpointer | 3 + redmine/script/console | 3 + redmine/script/destroy | 3 + redmine/script/generate | 3 + redmine/script/performance/benchmarker | 3 + redmine/script/performance/profiler | 3 + redmine/script/plugin | 3 + redmine/script/process/reaper | 3 + redmine/script/process/spawner | 3 + redmine/script/process/spinner | 3 + redmine/script/runner | 3 + redmine/script/server | 3 + redmine/test/fixtures/attachments.yml | 5 + redmine/test/fixtures/custom_fields.yml | 5 + redmine/test/fixtures/documents.yml | 5 + redmine/test/fixtures/enumerations.yml | 5 + redmine/test/fixtures/issue_categories.yml | 5 + redmine/test/fixtures/issue_custom_fields.yml | 5 + redmine/test/fixtures/issue_custom_values.yml | 5 + redmine/test/fixtures/issue_histories.yml | 5 + redmine/test/fixtures/issue_statuses.yml | 5 + redmine/test/fixtures/issues.yml | 5 + redmine/test/fixtures/mailer/issue_closed | 3 + redmine/test/fixtures/members.yml | 5 + redmine/test/fixtures/news.yml | 5 + redmine/test/fixtures/permissions.yml | 5 + redmine/test/fixtures/projects.yml | 5 + redmine/test/fixtures/roles.yml | 5 + redmine/test/fixtures/trackers.yml | 5 + redmine/test/fixtures/users.yml | 5 + redmine/test/fixtures/versions.yml | 5 + redmine/test/fixtures/workflow.yml | 5 + .../functional/account_controller_test.rb | 18 + .../test/functional/admin_controller_test.rb | 18 + .../custom_fields_controller_test.rb | 88 + .../functional/documents_controller_test.rb | 88 + .../enumerations_controller_test.rb | 88 + .../test/functional/help_controller_test.rb | 18 + .../issue_categories_controller_test.rb | 88 + .../issue_statuses_controller_test.rb | 88 + .../test/functional/issues_controller_test.rb | 88 + .../functional/members_controller_test.rb | 88 + .../test/functional/news_controller_test.rb | 88 + .../functional/projects_controller_test.rb | 88 + .../functional/reports_controller_test.rb | 18 + .../test/functional/roles_controller_test.rb | 88 + .../functional/trackers_controller_test.rb | 88 + .../test/functional/users_controller_test.rb | 88 + .../functional/versions_controller_test.rb | 88 + .../functional/welcome_controller_test.rb | 18 + redmine/test/test_helper.rb | 28 + redmine/test/unit/attachment_test.rb | 10 + redmine/test/unit/custom_field_test.rb | 10 + redmine/test/unit/document_test.rb | 10 + redmine/test/unit/enumeration_test.rb | 10 + redmine/test/unit/issue_category_test.rb | 10 + redmine/test/unit/issue_custom_field_test.rb | 10 + redmine/test/unit/issue_custom_value_test.rb | 10 + redmine/test/unit/issue_history_test.rb | 10 + redmine/test/unit/issue_status_test.rb | 10 + redmine/test/unit/issue_test.rb | 10 + redmine/test/unit/mailer_test.rb | 35 + redmine/test/unit/member_test.rb | 10 + redmine/test/unit/news_test.rb | 10 + redmine/test/unit/packages_test.rb | 10 + redmine/test/unit/permission_test.rb | 10 + redmine/test/unit/project_test.rb | 10 + redmine/test/unit/role_test.rb | 10 + redmine/test/unit/tracker_test.rb | 10 + redmine/test/unit/user_test.rb | 10 + redmine/test/unit/version_test.rb | 10 + redmine/test/unit/workflow_test.rb | 10 + redmine/vendor/plugins/localization/README | 85 + redmine/vendor/plugins/localization/init.rb | 3 + .../plugins/localization/lib/localization.rb | 57 + 284 files changed, 12752 insertions(+) create mode 100644 redmine/Rakefile create mode 100644 redmine/app/controllers/account_controller.rb create mode 100644 redmine/app/controllers/admin_controller.rb create mode 100644 redmine/app/controllers/application.rb create mode 100644 redmine/app/controllers/custom_fields_controller.rb create mode 100644 redmine/app/controllers/documents_controller.rb create mode 100644 redmine/app/controllers/enumerations_controller.rb create mode 100644 redmine/app/controllers/help_controller.rb create mode 100644 redmine/app/controllers/issue_categories_controller.rb create mode 100644 redmine/app/controllers/issue_statuses_controller.rb create mode 100644 redmine/app/controllers/issues_controller.rb create mode 100644 redmine/app/controllers/members_controller.rb create mode 100644 redmine/app/controllers/news_controller.rb create mode 100644 redmine/app/controllers/projects_controller.rb create mode 100644 redmine/app/controllers/reports_controller.rb create mode 100644 redmine/app/controllers/roles_controller.rb create mode 100644 redmine/app/controllers/trackers_controller.rb create mode 100644 redmine/app/controllers/users_controller.rb create mode 100644 redmine/app/controllers/versions_controller.rb create mode 100644 redmine/app/controllers/welcome_controller.rb create mode 100644 redmine/app/helpers/account_helper.rb create mode 100644 redmine/app/helpers/admin_helper.rb create mode 100644 redmine/app/helpers/application_helper.rb create mode 100644 redmine/app/helpers/custom_fields_helper.rb create mode 100644 redmine/app/helpers/documents_helper.rb create mode 100644 redmine/app/helpers/enumerations_helper.rb create mode 100644 redmine/app/helpers/help_helper.rb create mode 100644 redmine/app/helpers/issue_categories_helper.rb create mode 100644 redmine/app/helpers/issue_statuses_helper.rb create mode 100644 redmine/app/helpers/issues_helper.rb create mode 100644 redmine/app/helpers/members_helper.rb create mode 100644 redmine/app/helpers/news_helper.rb create mode 100644 redmine/app/helpers/projects_helper.rb create mode 100644 redmine/app/helpers/reports_helper.rb create mode 100644 redmine/app/helpers/roles_helper.rb create mode 100644 redmine/app/helpers/search_filter_helper.rb create mode 100644 redmine/app/helpers/sort_helper.rb create mode 100644 redmine/app/helpers/trackers_helper.rb create mode 100644 redmine/app/helpers/users_helper.rb create mode 100644 redmine/app/helpers/versions_helper.rb create mode 100644 redmine/app/helpers/welcome_helper.rb create mode 100644 redmine/app/models/attachment.rb create mode 100644 redmine/app/models/custom_field.rb create mode 100644 redmine/app/models/custom_value.rb create mode 100644 redmine/app/models/document.rb create mode 100644 redmine/app/models/enumeration.rb create mode 100644 redmine/app/models/issue.rb create mode 100644 redmine/app/models/issue_category.rb create mode 100644 redmine/app/models/issue_history.rb create mode 100644 redmine/app/models/issue_status.rb create mode 100644 redmine/app/models/mailer.rb create mode 100644 redmine/app/models/member.rb create mode 100644 redmine/app/models/news.rb create mode 100644 redmine/app/models/permission.rb create mode 100644 redmine/app/models/project.rb create mode 100644 redmine/app/models/role.rb create mode 100644 redmine/app/models/tracker.rb create mode 100644 redmine/app/models/user.rb create mode 100644 redmine/app/models/version.rb create mode 100644 redmine/app/models/workflow.rb create mode 100644 redmine/app/views/account/login.rhtml create mode 100644 redmine/app/views/account/my_account.rhtml create mode 100644 redmine/app/views/account/my_page.rhtml create mode 100644 redmine/app/views/account/show.rhtml create mode 100644 redmine/app/views/admin/index.rhtml create mode 100644 redmine/app/views/admin/info.rhtml create mode 100644 redmine/app/views/admin/mail_options.rhtml create mode 100644 redmine/app/views/admin/projects.rhtml create mode 100644 redmine/app/views/custom_fields/_form.rhtml create mode 100644 redmine/app/views/custom_fields/edit.rhtml create mode 100644 redmine/app/views/custom_fields/list.rhtml create mode 100644 redmine/app/views/custom_fields/new.rhtml create mode 100644 redmine/app/views/documents/_form.rhtml create mode 100644 redmine/app/views/documents/edit.rhtml create mode 100644 redmine/app/views/documents/show.rhtml create mode 100644 redmine/app/views/enumerations/_form.rhtml create mode 100644 redmine/app/views/enumerations/edit.rhtml create mode 100644 redmine/app/views/enumerations/list.rhtml create mode 100644 redmine/app/views/enumerations/new.rhtml create mode 100644 redmine/app/views/issue_categories/_form.rhtml create mode 100644 redmine/app/views/issue_categories/edit.rhtml create mode 100644 redmine/app/views/issue_statuses/_form.rhtml create mode 100644 redmine/app/views/issue_statuses/edit.rhtml create mode 100644 redmine/app/views/issue_statuses/list.rhtml create mode 100644 redmine/app/views/issue_statuses/new.rhtml create mode 100644 redmine/app/views/issues/_list_simple.rhtml create mode 100644 redmine/app/views/issues/change_status.rhtml create mode 100644 redmine/app/views/issues/edit.rhtml create mode 100644 redmine/app/views/issues/show.rhtml create mode 100644 redmine/app/views/layouts/base.rhtml create mode 100644 redmine/app/views/mailer/_issue.rhtml create mode 100644 redmine/app/views/mailer/issue_add.rhtml create mode 100644 redmine/app/views/mailer/issue_change_status.rhtml create mode 100644 redmine/app/views/news/_form.rhtml create mode 100644 redmine/app/views/news/edit.rhtml create mode 100644 redmine/app/views/news/show.rhtml create mode 100644 redmine/app/views/projects/_form.rhtml create mode 100644 redmine/app/views/projects/add.rhtml create mode 100644 redmine/app/views/projects/add_document.rhtml create mode 100644 redmine/app/views/projects/add_file.rhtml create mode 100644 redmine/app/views/projects/add_issue.rhtml create mode 100644 redmine/app/views/projects/add_news.rhtml create mode 100644 redmine/app/views/projects/add_version.rhtml create mode 100644 redmine/app/views/projects/changelog.rhtml create mode 100644 redmine/app/views/projects/destroy.rhtml create mode 100644 redmine/app/views/projects/list.rhtml create mode 100644 redmine/app/views/projects/list_documents.rhtml create mode 100644 redmine/app/views/projects/list_files.rhtml create mode 100644 redmine/app/views/projects/list_issues.rhtml create mode 100644 redmine/app/views/projects/list_members.rhtml create mode 100644 redmine/app/views/projects/list_news.rhtml create mode 100644 redmine/app/views/projects/settings.rhtml create mode 100644 redmine/app/views/projects/show.rhtml create mode 100644 redmine/app/views/reports/_simple.rhtml create mode 100644 redmine/app/views/reports/issue_report.rhtml create mode 100644 redmine/app/views/roles/_form.rhtml create mode 100644 redmine/app/views/roles/edit.rhtml create mode 100644 redmine/app/views/roles/list.rhtml create mode 100644 redmine/app/views/roles/new.rhtml create mode 100644 redmine/app/views/roles/workflow.rhtml create mode 100644 redmine/app/views/trackers/_form.rhtml create mode 100644 redmine/app/views/trackers/edit.rhtml create mode 100644 redmine/app/views/trackers/list.rhtml create mode 100644 redmine/app/views/trackers/new.rhtml create mode 100644 redmine/app/views/users/_form.rhtml create mode 100644 redmine/app/views/users/add.rhtml create mode 100644 redmine/app/views/users/edit.rhtml create mode 100644 redmine/app/views/users/list.rhtml create mode 100644 redmine/app/views/versions/_form.rhtml create mode 100644 redmine/app/views/versions/edit.rhtml create mode 100644 redmine/app/views/welcome/index.rhtml create mode 100644 redmine/config/boot.rb create mode 100644 redmine/config/database.yml create mode 100644 redmine/config/environment.rb create mode 100644 redmine/config/environments/demo.rb create mode 100644 redmine/config/environments/development.rb create mode 100644 redmine/config/environments/production.rb create mode 100644 redmine/config/environments/test.rb create mode 100644 redmine/config/help.yml create mode 100644 redmine/config/routes.rb create mode 100644 redmine/db/migrate/001_setup.rb create mode 100644 redmine/db/migrate/002_default_configuration.rb create mode 100644 redmine/db/redmine_demo.db create mode 100644 redmine/doc/CHANGELOG create mode 100644 redmine/doc/COPYING create mode 100644 redmine/doc/INSTALL create mode 100644 redmine/doc/README create mode 100644 redmine/files/delete.me create mode 100644 redmine/lang/en_US.rb create mode 100644 redmine/lang/es_ES.rb create mode 100644 redmine/lang/fr_FR.rb create mode 100644 redmine/public/.htaccess create mode 100644 redmine/public/404.html create mode 100644 redmine/public/500.html create mode 100644 redmine/public/dispatch.cgi create mode 100644 redmine/public/dispatch.fcgi create mode 100644 redmine/public/dispatch.rb create mode 100644 redmine/public/favicon.ico create mode 100644 redmine/public/images/Copie de help.png create mode 100644 redmine/public/images/admin.png create mode 100644 redmine/public/images/bulletgreen.png create mode 100644 redmine/public/images/bulletred.png create mode 100644 redmine/public/images/delete.png create mode 100644 redmine/public/images/dir.png create mode 100644 redmine/public/images/dir_new.png create mode 100644 redmine/public/images/dir_open.png create mode 100644 redmine/public/images/document.png create mode 100644 redmine/public/images/edit_small.png create mode 100644 redmine/public/images/file_new.png create mode 100644 redmine/public/images/help.png create mode 100644 redmine/public/images/home.png create mode 100644 redmine/public/images/issues.png create mode 100644 redmine/public/images/locked.png create mode 100644 redmine/public/images/logout.png create mode 100644 redmine/public/images/mailer.png create mode 100644 redmine/public/images/notes.png create mode 100644 redmine/public/images/options.png create mode 100644 redmine/public/images/package.png create mode 100644 redmine/public/images/projects.png create mode 100644 redmine/public/images/rails.png create mode 100644 redmine/public/images/rails_powered.png create mode 100644 redmine/public/images/rails_small.png create mode 100644 redmine/public/images/role.png create mode 100644 redmine/public/images/rss.png create mode 100644 redmine/public/images/sort_asc.png create mode 100644 redmine/public/images/sort_desc.png create mode 100644 redmine/public/images/tracker.png create mode 100644 redmine/public/images/true.png create mode 100644 redmine/public/images/user.png create mode 100644 redmine/public/images/user_page.png create mode 100644 redmine/public/images/users.png create mode 100644 redmine/public/images/workflow.png create mode 100644 redmine/public/javascripts/application.js create mode 100644 redmine/public/javascripts/controls.js create mode 100644 redmine/public/javascripts/dragdrop.js create mode 100644 redmine/public/javascripts/effects.js create mode 100644 redmine/public/javascripts/prototype.js create mode 100644 redmine/public/manual/administration.html create mode 100644 redmine/public/manual/images/issues_list.png create mode 100644 redmine/public/manual/images/users_edit.png create mode 100644 redmine/public/manual/images/users_list.png create mode 100644 redmine/public/manual/images/workflow.png create mode 100644 redmine/public/manual/index.html create mode 100644 redmine/public/manual/projects.html create mode 100644 redmine/public/manual/stylesheets/help.css create mode 100644 redmine/public/robots.txt create mode 100644 redmine/public/stylesheets/application.css create mode 100644 redmine/public/stylesheets/rails.css create mode 100644 redmine/script/about create mode 100644 redmine/script/breakpointer create mode 100644 redmine/script/console create mode 100644 redmine/script/destroy create mode 100644 redmine/script/generate create mode 100644 redmine/script/performance/benchmarker create mode 100644 redmine/script/performance/profiler create mode 100644 redmine/script/plugin create mode 100644 redmine/script/process/reaper create mode 100644 redmine/script/process/spawner create mode 100644 redmine/script/process/spinner create mode 100644 redmine/script/runner create mode 100644 redmine/script/server create mode 100644 redmine/test/fixtures/attachments.yml create mode 100644 redmine/test/fixtures/custom_fields.yml create mode 100644 redmine/test/fixtures/documents.yml create mode 100644 redmine/test/fixtures/enumerations.yml create mode 100644 redmine/test/fixtures/issue_categories.yml create mode 100644 redmine/test/fixtures/issue_custom_fields.yml create mode 100644 redmine/test/fixtures/issue_custom_values.yml create mode 100644 redmine/test/fixtures/issue_histories.yml create mode 100644 redmine/test/fixtures/issue_statuses.yml create mode 100644 redmine/test/fixtures/issues.yml create mode 100644 redmine/test/fixtures/mailer/issue_closed create mode 100644 redmine/test/fixtures/members.yml create mode 100644 redmine/test/fixtures/news.yml create mode 100644 redmine/test/fixtures/permissions.yml create mode 100644 redmine/test/fixtures/projects.yml create mode 100644 redmine/test/fixtures/roles.yml create mode 100644 redmine/test/fixtures/trackers.yml create mode 100644 redmine/test/fixtures/users.yml create mode 100644 redmine/test/fixtures/versions.yml create mode 100644 redmine/test/fixtures/workflow.yml create mode 100644 redmine/test/functional/account_controller_test.rb create mode 100644 redmine/test/functional/admin_controller_test.rb create mode 100644 redmine/test/functional/custom_fields_controller_test.rb create mode 100644 redmine/test/functional/documents_controller_test.rb create mode 100644 redmine/test/functional/enumerations_controller_test.rb create mode 100644 redmine/test/functional/help_controller_test.rb create mode 100644 redmine/test/functional/issue_categories_controller_test.rb create mode 100644 redmine/test/functional/issue_statuses_controller_test.rb create mode 100644 redmine/test/functional/issues_controller_test.rb create mode 100644 redmine/test/functional/members_controller_test.rb create mode 100644 redmine/test/functional/news_controller_test.rb create mode 100644 redmine/test/functional/projects_controller_test.rb create mode 100644 redmine/test/functional/reports_controller_test.rb create mode 100644 redmine/test/functional/roles_controller_test.rb create mode 100644 redmine/test/functional/trackers_controller_test.rb create mode 100644 redmine/test/functional/users_controller_test.rb create mode 100644 redmine/test/functional/versions_controller_test.rb create mode 100644 redmine/test/functional/welcome_controller_test.rb create mode 100644 redmine/test/test_helper.rb create mode 100644 redmine/test/unit/attachment_test.rb create mode 100644 redmine/test/unit/custom_field_test.rb create mode 100644 redmine/test/unit/document_test.rb create mode 100644 redmine/test/unit/enumeration_test.rb create mode 100644 redmine/test/unit/issue_category_test.rb create mode 100644 redmine/test/unit/issue_custom_field_test.rb create mode 100644 redmine/test/unit/issue_custom_value_test.rb create mode 100644 redmine/test/unit/issue_history_test.rb create mode 100644 redmine/test/unit/issue_status_test.rb create mode 100644 redmine/test/unit/issue_test.rb create mode 100644 redmine/test/unit/mailer_test.rb create mode 100644 redmine/test/unit/member_test.rb create mode 100644 redmine/test/unit/news_test.rb create mode 100644 redmine/test/unit/packages_test.rb create mode 100644 redmine/test/unit/permission_test.rb create mode 100644 redmine/test/unit/project_test.rb create mode 100644 redmine/test/unit/role_test.rb create mode 100644 redmine/test/unit/tracker_test.rb create mode 100644 redmine/test/unit/user_test.rb create mode 100644 redmine/test/unit/version_test.rb create mode 100644 redmine/test/unit/workflow_test.rb create mode 100644 redmine/vendor/plugins/localization/README create mode 100644 redmine/vendor/plugins/localization/init.rb create mode 100644 redmine/vendor/plugins/localization/lib/localization.rb diff --git a/redmine/Rakefile b/redmine/Rakefile new file mode 100644 index 000000000..cffd19f0c --- /dev/null +++ b/redmine/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/redmine/app/controllers/account_controller.rb b/redmine/app/controllers/account_controller.rb new file mode 100644 index 000000000..d5c98f58c --- /dev/null +++ b/redmine/app/controllers/account_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 AccountController < ApplicationController + layout 'base' + # prevents login action to be filtered by check_if_login_required application scope filter + skip_before_filter :check_if_login_required, :only => :login + before_filter :require_login, :except => [:show, :login] + + def show + @user = User.find(params[:id]) + end + + # Login request and validation + def login + if request.get? + session[:user] = nil + @user = User.new + else + @user = User.new(params[:user]) + logged_in_user = @user.try_to_login + if logged_in_user + session[:user] = logged_in_user + redirect_back_or_default :controller => 'account', :action => 'my_page' + else + flash[:notice] = _('Invalid user/password') + end + end + end + + # Log out current user and redirect to welcome page + def logout + session[:user] = nil + redirect_to(:controller => '') + end + + def my_page + @user = session[:user] + @reported_issues = Issue.find(:all, :conditions => ["author_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC') + @assigned_issues = Issue.find(:all, :conditions => ["assigned_to_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC') + end + + # Edit current user's account + def my_account + @user = User.find(session[:user].id) + if request.post? and @user.update_attributes(@params[:user]) + flash[:notice] = 'Account was successfully updated.' + session[:user] = @user + set_localization + end + end + + # Change current user's password + def change_password + @user = User.find(session[:user].id) + if @user.check_password?(@params[:old_password]) + if @params[:new_password] == @params[:new_password_confirmation] + if @user.change_password(@params[:old_password], @params[:new_password]) + flash[:notice] = 'Password was successfully updated.' + end + else + flash[:notice] = 'Password confirmation doesn\'t match!' + end + else + flash[:notice] = 'Wrong password' + end + render :action => 'my_account' + end +end diff --git a/redmine/app/controllers/admin_controller.rb b/redmine/app/controllers/admin_controller.rb new file mode 100644 index 000000000..fa34baff1 --- /dev/null +++ b/redmine/app/controllers/admin_controller.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 AdminController < ApplicationController + layout 'base' + before_filter :require_admin + + helper :sort + include SortHelper + + def index + end + + def projects + sort_init 'projects.name', 'asc' + sort_update + @project_pages, @projects = paginate :projects, :per_page => 15, :order => sort_clause + end + + def mail_options + @actions = Permission.find(:all, :conditions => ["mail_option=?", true]) || [] + if request.post? + @actions.each { |a| + a.mail_enabled = params[:action_ids].include? a.id.to_s + a.save + } + flash[:notice] = "Mail options were successfully updated." + end + end + + def info + @adapter_name = ActiveRecord::Base.connection.adapter_name + end + +end diff --git a/redmine/app/controllers/application.rb b/redmine/app/controllers/application.rb new file mode 100644 index 000000000..a9dd6b80e --- /dev/null +++ b/redmine/app/controllers/application.rb @@ -0,0 +1,86 @@ +# 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 ApplicationController < ActionController::Base + before_filter :check_if_login_required, :set_localization + + # check if login is globally required to access the application + def check_if_login_required + require_login if RDM_LOGIN_REQUIRED + end + + def set_localization + Localization.lang = session[:user].nil? ? RDM_DEFAULT_LANG : (session[:user].language || RDM_DEFAULT_LANG) + end + + def require_login + unless session[:user] + store_location + redirect_to(:controller => "account", :action => "login") + end + end + + def require_admin + if session[:user].nil? + store_location + redirect_to(:controller => "account", :action => "login") + else + unless session[:user].admin? + flash[:notice] = "Acces not allowed" + redirect_to(:controller => "projects", :action => "list") + end + end + end + + # authorizes the user for the requested action. + def authorize + # check if action is allowed on public projects + if @project.public? and Permission.allowed_to_public "%s/%s" % [ @params[:controller], @params[:action] ] + return true + end + # if user is not logged in, he is redirect to login form + unless session[:user] + store_location + redirect_to(:controller => "account", :action => "login") + return false + end + # check if user is authorized + if session[:user].admin? or Permission.allowed_to_role( "%s/%s" % [ @params[:controller], @params[:action] ], session[:user].role_for_project(@project.id) ) + return true + end + flash[:notice] = "Acces denied" + redirect_to(:controller => "") + return false + end + + # store current uri in the session. + # we can return to this location by calling redirect_back_or_default + def store_location + session[:return_to] = @request.request_uri + end + + # move to the last store_location call or to the passed default one + def redirect_back_or_default(default) + if session[:return_to].nil? + redirect_to default + else + redirect_to_url session[:return_to] + session[:return_to] = nil + end + end + +end \ No newline at end of file diff --git a/redmine/app/controllers/custom_fields_controller.rb b/redmine/app/controllers/custom_fields_controller.rb new file mode 100644 index 000000000..93f6353fa --- /dev/null +++ b/redmine/app/controllers/custom_fields_controller.rb @@ -0,0 +1,58 @@ +# 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' + end + + def list + @custom_field_pages, @custom_fields = paginate :custom_fields, :per_page => 10 + end + + def new + if request.get? + @custom_field = CustomField.new + else + @custom_field = CustomField.new(params[:custom_field]) + if @custom_field.save + flash[:notice] = 'CustomField was successfully created.' + redirect_to :action => 'list' + end + end + end + + def edit + @custom_field = CustomField.find(params[:id]) + if request.post? and @custom_field.update_attributes(params[:custom_field]) + flash[:notice] = 'CustomField was successfully updated.' + redirect_to :action => 'list' + end + end + + def destroy + CustomField.find(params[:id]).destroy + redirect_to :action => 'list' + rescue + flash[:notice] = "Unable to delete custom field" + redirect_to :action => 'list' + end +end diff --git a/redmine/app/controllers/documents_controller.rb b/redmine/app/controllers/documents_controller.rb new file mode 100644 index 000000000..3c76465c9 --- /dev/null +++ b/redmine/app/controllers/documents_controller.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 DocumentsController < ApplicationController + layout 'base' + before_filter :find_project, :authorize + + def show + end + + def edit + @categories = Enumeration::get_values('DCAT') + if request.post? and @document.update_attributes(params[:document]) + flash[:notice] = 'Document was successfully updated.' + 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 + end + + def add_attachment + # Save the attachment + if params[:attachment][:file].size > 0 + @attachment = @document.attachments.build(params[:attachment]) + @attachment.author_id = session[:user].id unless session[:user].nil? + @attachment.save + end + render :action => 'show' + end + + def destroy_attachment + @document.attachments.find(params[:attachment_id]).destroy + render :action => 'show' + end + +private + def find_project + @document = Document.find(params[:id]) + @project = @document.project + end + +end diff --git a/redmine/app/controllers/enumerations_controller.rb b/redmine/app/controllers/enumerations_controller.rb new file mode 100644 index 000000000..01664c85e --- /dev/null +++ b/redmine/app/controllers/enumerations_controller.rb @@ -0,0 +1,69 @@ +# 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] = 'Enumeration was successfully created.' + 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] = 'Enumeration was successfully updated.' + redirect_to :action => 'list', :opt => @enumeration.opt + else + render :action => 'edit' + end + end + + def destroy + Enumeration.find(params[:id]).destroy + redirect_to :action => 'list' + rescue + flash[:notice] = "Unable to delete enumeration" + redirect_to :action => 'list' + end +end diff --git a/redmine/app/controllers/help_controller.rb b/redmine/app/controllers/help_controller.rb new file mode 100644 index 000000000..4b555d599 --- /dev/null +++ b/redmine/app/controllers/help_controller.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 HelpController < ApplicationController + + skip_before_filter :check_if_login_required + before_filter :load_help_config + + def index + if @params[:ctrl] and @help_config[@params[:ctrl]] + if @params[:page] and @help_config[@params[:ctrl]][@params[:page]] + template = @help_config[@params[:ctrl]][@params[:page]] + else + template = @help_config[@params[:ctrl]]['index'] + end + end + + if template + redirect_to "/manual/#{template}" + else + redirect_to "/manual/" + end + end + +private + def load_help_config + @help_config = YAML::load(File.open("#{RAILS_ROOT}/config/help.yml")) + end +end diff --git a/redmine/app/controllers/issue_categories_controller.rb b/redmine/app/controllers/issue_categories_controller.rb new file mode 100644 index 000000000..cf0f9dc06 --- /dev/null +++ b/redmine/app/controllers/issue_categories_controller.rb @@ -0,0 +1,42 @@ +# 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 + + def edit + if request.post? and @category.update_attributes(params[:category]) + flash[:notice] = 'Issue category was successfully updated.' + redirect_to :controller => 'projects', :action => 'settings', :id => @project + end + end + + def destroy + @category.destroy + redirect_to :controller => 'projects', :action => 'settings', :id => @project + rescue + flash[:notice] = "Categorie can't be deleted" + redirect_to :controller => 'projects', :action => 'settings', :id => @project + end + +private + def find_project + @category = IssueCategory.find(params[:id]) + @project = @category.project + end +end diff --git a/redmine/app/controllers/issue_statuses_controller.rb b/redmine/app/controllers/issue_statuses_controller.rb new file mode 100644 index 000000000..3de8158c6 --- /dev/null +++ b/redmine/app/controllers/issue_statuses_controller.rb @@ -0,0 +1,68 @@ +# 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 + + def index + list + render :action => 'list' + end + + def list + @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 10 + end + + def new + @issue_status = IssueStatus.new + end + + def create + @issue_status = IssueStatus.new(params[:issue_status]) + if @issue_status.save + flash[:notice] = 'IssueStatus was successfully created.' + 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] = 'IssueStatus was successfully updated.' + redirect_to :action => 'list' + else + render :action => 'edit' + end + end + + def destroy + IssueStatus.find(params[:id]).destroy + redirect_to :action => 'list' + rescue + flash[:notice] = "Unable to delete issue status" + redirect_to :action => 'list' + end + + +end diff --git a/redmine/app/controllers/issues_controller.rb b/redmine/app/controllers/issues_controller.rb new file mode 100644 index 000000000..5d5872f39 --- /dev/null +++ b/redmine/app/controllers/issues_controller.rb @@ -0,0 +1,102 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class IssuesController < ApplicationController + layout 'base' + before_filter :find_project, :authorize + + helper :custom_fields + include CustomFieldsHelper + + def show + @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", session[:user].role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if session[:user] + end + + def edit + @trackers = Tracker.find(:all) + @priorities = Enumeration::get_values('IPRI') + + if request.get? + @custom_values = @project.custom_fields_for_issues.collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) } + else + # Retrieve custom fields and values + @custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x, :value => params["custom_fields"][x.id.to_s]) } + + @issue.custom_values = @custom_values + if @issue.update_attributes(params[:issue]) + flash[:notice] = 'Issue was successfully updated.' + redirect_to :action => 'show', :id => @issue + end + end + end + + def change_status + @history = @issue.histories.build(params[:history]) + @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", session[:user].role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if session[:user] + + if params[:confirm] + unless session[:user].nil? + @history.author = session[:user] + end + if @history.save + @issue.status = @history.status + @issue.fixed_version_id = (params[:issue][:fixed_version_id]) + @issue.assigned_to_id = (params[:issue][:assigned_to_id]) + if @issue.save + flash[:notice] = 'Issue was successfully updated.' + Mailer.deliver_issue_change_status(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled? + redirect_to :action => 'show', :id => @issue + end + end + end + @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user } + + end + + def destroy + @issue.destroy + redirect_to :controller => 'projects', :action => 'list_issues', :id => @project + end + + def add_attachment + # Save the attachment + if params[:attachment][:file].size > 0 + @attachment = @issue.attachments.build(params[:attachment]) + @attachment.author_id = session[:user].id unless session[:user].nil? + @attachment.save + end + redirect_to :action => 'show', :id => @issue + end + + def destroy_attachment + @issue.attachments.find(params[:attachment_id]).destroy + redirect_to :action => 'show', :id => @issue + end + + # Send the file in stream mode + def download + @attachment = @issue.attachments.find(params[:attachment_id]) + send_file @attachment.diskfile, :filename => @attachment.filename + end + +private + def find_project + @issue = Issue.find(params[:id]) + @project = @issue.project + end + +end diff --git a/redmine/app/controllers/members_controller.rb b/redmine/app/controllers/members_controller.rb new file mode 100644 index 000000000..ac6b08d26 --- /dev/null +++ b/redmine/app/controllers/members_controller.rb @@ -0,0 +1,41 @@ +# 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_project, :authorize + + def edit + if request.post? and @member.update_attributes(params[:member]) + flash[:notice] = 'Member was successfully updated.' + redirect_to :controller => 'projects', :action => 'settings', :id => @project + end + end + + def destroy + @member.destroy + flash[:notice] = 'Member was successfully removed.' + redirect_to :controller => 'projects', :action => 'settings', :id => @project + end + +private + def find_project + @member = Member.find(params[:id]) + @project = @member.project + end + +end diff --git a/redmine/app/controllers/news_controller.rb b/redmine/app/controllers/news_controller.rb new file mode 100644 index 000000000..065336f26 --- /dev/null +++ b/redmine/app/controllers/news_controller.rb @@ -0,0 +1,42 @@ +# 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 + + def show + end + + def edit + if request.post? and @news.update_attributes(params[:news]) + flash[:notice] = 'News was successfully updated.' + redirect_to :action => 'show', :id => @news + end + end + + def destroy + @news.destroy + redirect_to :controller => 'projects', :action => 'list_news', :id => @project + end + +private + def find_project + @news = News.find(params[:id]) + @project = @news.project + end +end diff --git a/redmine/app/controllers/projects_controller.rb b/redmine/app/controllers/projects_controller.rb new file mode 100644 index 000000000..2e74045e9 --- /dev/null +++ b/redmine/app/controllers/projects_controller.rb @@ -0,0 +1,260 @@ +# 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 ProjectsController < ApplicationController + layout 'base' + before_filter :find_project, :authorize, :except => [ :index, :list, :add ] + before_filter :require_admin, :only => [ :add, :destroy ] + + helper :sort + include SortHelper + helper :search_filter + include SearchFilterHelper + helper :custom_fields + include CustomFieldsHelper + + def index + list + render :action => 'list' + end + + # Lists public projects + def list + sort_init 'projects.name', 'asc' + sort_update + @project_count = Project.count(["public=?", true]) + @project_pages = Paginator.new self, @project_count, + 15, + @params['page'] + @projects = Project.find :all, :order => sort_clause, + :conditions => ["public=?", true], + :limit => @project_pages.items_per_page, + :offset => @project_pages.current.offset + end + + # Add a new project + def add + @custom_fields = CustomField::find_all + @project = Project.new(params[:project]) + if request.post? + @project.custom_fields = CustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids] + if @project.save + flash[:notice] = 'Project was successfully created.' + redirect_to :controller => 'admin', :action => 'projects' + end + end + end + + # Show @project + def show + @members = @project.members.find(:all, :include => [:user, :role]) + end + + def settings + @custom_fields = CustomField::find_all + @issue_category ||= IssueCategory.new + @member ||= @project.members.new + @roles = Role.find_all + @users = User.find_all - @project.members.find(:all, :include => :user).collect{|m| m.user } + end + + # Edit @project + def edit + if request.post? + @project.custom_fields = CustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids] + if @project.update_attributes(params[:project]) + flash[:notice] = 'Project was successfully updated.' + redirect_to :action => 'settings', :id => @project + else + settings + render :action => 'settings' + end + end + end + + # Delete @project + def destroy + if request.post? and params[:confirm] + @project.destroy + redirect_to :controller => 'admin', :action => 'projects' + end + end + + # Add a new issue category to @project + def add_issue_category + if request.post? + @issue_category = @project.issue_categories.build(params[:issue_category]) + if @issue_category.save + redirect_to :action => 'settings', :id => @project + else + settings + render :action => 'settings' + end + end + end + + # Add a new version to @project + def add_version + @version = @project.versions.build(params[:version]) + if request.post? and @version.save + redirect_to :action => 'settings', :id => @project + end + end + + # Add a new member to @project + def add_member + @member = @project.members.build(params[:member]) + if request.post? + if @member.save + flash[:notice] = 'Member was successfully added.' + redirect_to :action => 'settings', :id => @project + else + settings + render :action => 'settings' + end + end + end + + # Show members list of @project + def list_members + @members = @project.members + end + + # Add a new document to @project + def add_document + @categories = Enumeration::get_values('DCAT') + @document = @project.documents.build(params[:document]) + if request.post? + # Save the attachment + if params[:attachment][:file].size > 0 + @attachment = @document.attachments.build(params[:attachment]) + @attachment.author_id = session[:user].id unless session[:user].nil? + end + if @document.save + redirect_to :action => 'list_documents', :id => @project + end + end + end + + # Show documents list of @project + def list_documents + @documents = @project.documents + end + + # Add a new issue to @project + def add_issue + @trackers = Tracker.find(:all) + @priorities = Enumeration::get_values('IPRI') + if request.get? + @issue = @project.issues.build + @custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x) } + else + # Create the issue and set the author + @issue = @project.issues.build(params[:issue]) + @issue.author = session[:user] unless session[:user].nil? + # Create the document if a file was sent + if params[:attachment][:file].size > 0 + @attachment = @issue.attachments.build(params[:attachment]) + @attachment.author_id = session[:user].id unless session[:user].nil? + end + @custom_values = @project.custom_fields_for_issues.collect { |x| CustomValue.new(:custom_field => x, :value => params["custom_fields"][x.id.to_s]) } + @issue.custom_values = @custom_values + if @issue.save + flash[:notice] = "Issue was successfully added." + Mailer.deliver_issue_add(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled? + redirect_to :action => 'list_issues', :id => @project + end + end + end + + # Show issues list of @project + def list_issues + sort_init 'issues.id', 'desc' + sort_update + + search_filter_criteria 'issues.tracker_id', :values => "Tracker.find(:all)" + search_filter_criteria 'issues.priority_id', :values => "Enumeration.find(:all, :conditions => ['opt=?','IPRI'])" + search_filter_criteria 'issues.category_id', :values => "@project.issue_categories" + search_filter_criteria 'issues.status_id', :values => "IssueStatus.find(:all)" + search_filter_criteria 'issues.author_id', :values => "User.find(:all)", :label => "display_name" + search_filter_update if params[:set_filter] or request.post? + + @issue_count = @project.issues.count(search_filter_clause) + @issue_pages = Paginator.new self, @issue_count, + 15, + @params['page'] + @issues = @project.issues.find :all, :order => sort_clause, + :include => [ :author, :status, :tracker ], + :conditions => search_filter_clause, + :limit => @issue_pages.items_per_page, + :offset => @issue_pages.current.offset + end + + # Add a news to @project + def add_news + @news = @project.news.build(params[:news]) + if request.post? + @news.author = session[:user] unless session[:user].nil? + if @news.save + redirect_to :action => 'list_news', :id => @project + end + end + end + + # Show news list of @project + def list_news + @news_pages, @news = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "news.created_on DESC" + end + + def add_file + if request.post? + # Save the attachment + if params[:attachment][:file].size > 0 + @attachment = @project.versions.find(params[:version_id]).attachments.build(params[:attachment]) + @attachment.author_id = session[:user].id unless session[:user].nil? + if @attachment.save + redirect_to :controller => 'projects', :action => 'list_files', :id => @project + end + end + end + @versions = @project.versions + end + + def list_files + @versions = @project.versions + end + + # Show changelog of @project + def changelog + @fixed_issues = @project.issues.find(:all, + :include => [ :fixed_version, :status, :tracker ], + :conditions => [ "issue_statuses.is_closed=? and trackers.is_in_chlog=? and issues.fixed_version_id is not null", true, true] + ) + 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 + flash[:notice] = 'Project not found.' + redirect_to :action => 'list' + end + +end diff --git a/redmine/app/controllers/reports_controller.rb b/redmine/app/controllers/reports_controller.rb new file mode 100644 index 000000000..7dd57cc3b --- /dev/null +++ b/redmine/app/controllers/reports_controller.rb @@ -0,0 +1,71 @@ +# 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 + @trackers = Tracker.find_all + @issues_by_tracker = + ActiveRecord::Base.connection.select_all("select s.id as status_id, + s.is_closed as closed, + t.id as tracker_id, + count(i.id) as total + from + issues i, issue_statuses s, trackers t + where + i.status_id=s.id + and i.tracker_id=t.id + and i.project_id=#{@project.id} + group by s.id, t.id") + @priorities = Enumeration::get_values('IPRI') + @issues_by_priority = + ActiveRecord::Base.connection.select_all("select s.id as status_id, + s.is_closed as closed, + p.id as priority_id, + count(i.id) as total + from + issues i, issue_statuses s, enumerations p + where + i.status_id=s.id + and i.priority_id=p.id + and i.project_id=#{@project.id} + group by s.id, p.id") + @categories = @project.issue_categories + @issues_by_category = + ActiveRecord::Base.connection.select_all("select s.id as status_id, + s.is_closed as closed, + c.id as category_id, + count(i.id) as total + from + issues i, issue_statuses s, issue_categories c + where + i.status_id=s.id + and i.category_id=c.id + and i.project_id=#{@project.id} + group by s.id, c.id") + end + + +private + # Find project of id params[:id] + def find_project + @project = Project.find(params[:id]) + end +end diff --git a/redmine/app/controllers/roles_controller.rb b/redmine/app/controllers/roles_controller.rb new file mode 100644 index 000000000..6e4fc74e0 --- /dev/null +++ b/redmine/app/controllers/roles_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 RolesController < ApplicationController + layout 'base' + before_filter :require_admin + + def index + list + render :action => 'list' + end + + def list + @role_pages, @roles = paginate :roles, :per_page => 10 + end + + def new + @role = Role.new(params[:role]) + if request.post? + @role.permissions = Permission.find(@params[:permission_ids]) if @params[:permission_ids] + if @role.save + flash[:notice] = 'Role was successfully created.' + redirect_to :action => 'list' + end + end + @permissions = Permission.find(:all, :order => 'sort ASC') + end + + def edit + @role = Role.find(params[:id]) + if request.post? and @role.update_attributes(params[:role]) + @role.permissions = Permission.find(@params[:permission_ids] || []) + Permission.allowed_to_role_expired + flash[:notice] = 'Role was successfully updated.' + redirect_to :action => 'list' + end + @permissions = Permission.find(:all, :order => 'sort ASC') + end + + def destroy + @role = Role.find(params[:id]) + unless @role.members.empty? + flash[:notice] = 'Some members have this role. Can\'t delete it.' + else + @role.destroy + end + redirect_to :action => 'list' + end + + def workflow + @roles = Role.find_all + @trackers = Tracker.find_all + @statuses = IssueStatus.find_all + + @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] = 'Workflow was successfully updated.' + end + end + end +end diff --git a/redmine/app/controllers/trackers_controller.rb b/redmine/app/controllers/trackers_controller.rb new file mode 100644 index 000000000..38cdb6cde --- /dev/null +++ b/redmine/app/controllers/trackers_controller.rb @@ -0,0 +1,60 @@ +# 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' + end + + # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) + verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :list } + + def list + @tracker_pages, @trackers = paginate :trackers, :per_page => 10 + end + + def new + @tracker = Tracker.new(params[:tracker]) + if request.post? and @tracker.save + flash[:notice] = 'Tracker was successfully created.' + redirect_to :action => 'list' + end + end + + def edit + @tracker = Tracker.find(params[:id]) + if request.post? and @tracker.update_attributes(params[:tracker]) + flash[:notice] = 'Tracker was successfully updated.' + redirect_to :action => 'list' + end + end + + def destroy + @tracker = Tracker.find(params[:id]) + unless @tracker.issues.empty? + flash[:notice] = "This tracker contains issues and can\'t be deleted." + else + @tracker.destroy + end + redirect_to :action => 'list' + end + +end diff --git a/redmine/app/controllers/users_controller.rb b/redmine/app/controllers/users_controller.rb new file mode 100644 index 000000000..64c9fd311 --- /dev/null +++ b/redmine/app/controllers/users_controller.rb @@ -0,0 +1,73 @@ +# 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 UsersController < ApplicationController + layout 'base' + before_filter :require_admin + + helper :sort + include SortHelper + + def index + list + render :action => 'list' + end + + def list + sort_init 'users.login', 'asc' + sort_update + @user_count = User.count + @user_pages = Paginator.new self, @user_count, + 15, + @params['page'] + @users = User.find :all, :order => sort_clause, + :limit => @user_pages.items_per_page, + :offset => @user_pages.current.offset + end + + def add + if request.get? + @user = User.new + else + @user = User.new(params[:user]) + @user.admin = params[:user][:admin] + if @user.save + flash[:notice] = 'User was successfully created.' + redirect_to :action => 'list' + end + end + end + + def edit + @user = User.find(params[:id]) + if request.post? + @user.admin = params[:user][:admin] if params[:user][:admin] + if @user.update_attributes(params[:user]) + flash[:notice] = 'User was successfully updated.' + redirect_to :action => 'list' + end + end + end + + def destroy + User.find(params[:id]).destroy + redirect_to :action => 'list' + rescue + flash[:notice] = "Unable to delete user" + redirect_to :action => 'list' + end +end diff --git a/redmine/app/controllers/versions_controller.rb b/redmine/app/controllers/versions_controller.rb new file mode 100644 index 000000000..c4fd241a8 --- /dev/null +++ b/redmine/app/controllers/versions_controller.rb @@ -0,0 +1,53 @@ +# 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 + + def edit + if request.post? and @version.update_attributes(params[:version]) + flash[:notice] = 'Version was successfully updated.' + redirect_to :controller => 'projects', :action => 'settings', :id => @project + end + end + + def destroy + @version.destroy + redirect_to :controller => 'projects', :action => 'settings', :id => @project + rescue + flash[:notice] = "Unable to delete version" + redirect_to :controller => 'projects', :action => 'settings', :id => @project + end + + def download + @attachment = @version.attachments.find(params[:attachment_id]) + @attachment.increment_download + send_file @attachment.diskfile, :filename => @attachment.filename + end + + def destroy_file + @version.attachments.find(params[:attachment_id]).destroy + redirect_to :controller => 'projects', :action => 'list_files', :id => @project + end + +private + def find_project + @version = Version.find(params[:id]) + @project = @version.project + end +end diff --git a/redmine/app/controllers/welcome_controller.rb b/redmine/app/controllers/welcome_controller.rb new file mode 100644 index 000000000..b266975aa --- /dev/null +++ b/redmine/app/controllers/welcome_controller.rb @@ -0,0 +1,26 @@ +# 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 + @projects = Project.latest + end + +end diff --git a/redmine/app/helpers/account_helper.rb b/redmine/app/helpers/account_helper.rb new file mode 100644 index 000000000..e18ab6ff4 --- /dev/null +++ b/redmine/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/redmine/app/helpers/admin_helper.rb b/redmine/app/helpers/admin_helper.rb new file mode 100644 index 000000000..db2777392 --- /dev/null +++ b/redmine/app/helpers/admin_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 AdminHelper +end diff --git a/redmine/app/helpers/application_helper.rb b/redmine/app/helpers/application_helper.rb new file mode 100644 index 000000000..4a50b949a --- /dev/null +++ b/redmine/app/helpers/application_helper.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. + +module ApplicationHelper + + def loggedin? + session[:user] + end + + def admin_loggedin? + session[:user] && session[:user].admin + end + + def authorize_for(controller, action) + # check if action is allowed on public projects + if @project.public? and Permission.allowed_to_public "%s/%s" % [ controller, action ] + return true + end + # check if user is authorized + if session[:user] and (session[:user].admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], session[:user].role_for_project(@project.id) ) ) + return true + end + return false + end + + 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], options[:action]) + end + + # Display a link to user's account page + def link_to_user(user) + link_to user.display_name, :controller => 'account', :action => 'show', :id => user + end + + def format_date(date) + _('(date)', date) if date + end + + def format_time(time) + _('(time)', time) if time + end + + def pagination_links_full(paginator, options={}, html_options={}) + html ='' + html << link_to(('« ' + _('Previous') ), { :page => paginator.current.previous }) + ' ' if paginator.current.previous + html << (pagination_links(paginator, options, html_options) || '') + html << ' ' + link_to((_('Next') + ' »'), { :page => paginator.current.next }) if paginator.current.next + html + end + +end diff --git a/redmine/app/helpers/custom_fields_helper.rb b/redmine/app/helpers/custom_fields_helper.rb new file mode 100644 index 000000000..4e3aea50f --- /dev/null +++ b/redmine/app/helpers/custom_fields_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 CustomFieldsHelper + def custom_field_tag(custom_value) + + custom_field = custom_value.custom_field + + field_name = "custom_fields[#{custom_field.id}]" + + case custom_field.typ + when 0 .. 2 + text_field_tag field_name, custom_value.value + when 3 + check_box field_name + when 4 + select_tag field_name, + options_for_select(custom_field.possible_values.split('|'), + custom_value.value) + end + end +end diff --git a/redmine/app/helpers/documents_helper.rb b/redmine/app/helpers/documents_helper.rb new file mode 100644 index 000000000..c9897647d --- /dev/null +++ b/redmine/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/redmine/app/helpers/enumerations_helper.rb b/redmine/app/helpers/enumerations_helper.rb new file mode 100644 index 000000000..11a216a82 --- /dev/null +++ b/redmine/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/redmine/app/helpers/help_helper.rb b/redmine/app/helpers/help_helper.rb new file mode 100644 index 000000000..bb629316c --- /dev/null +++ b/redmine/app/helpers/help_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 HelpHelper +end diff --git a/redmine/app/helpers/issue_categories_helper.rb b/redmine/app/helpers/issue_categories_helper.rb new file mode 100644 index 000000000..997d830d7 --- /dev/null +++ b/redmine/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/redmine/app/helpers/issue_statuses_helper.rb b/redmine/app/helpers/issue_statuses_helper.rb new file mode 100644 index 000000000..17704b7ba --- /dev/null +++ b/redmine/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/redmine/app/helpers/issues_helper.rb b/redmine/app/helpers/issues_helper.rb new file mode 100644 index 000000000..40c0e40ae --- /dev/null +++ b/redmine/app/helpers/issues_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 IssuesHelper +end diff --git a/redmine/app/helpers/members_helper.rb b/redmine/app/helpers/members_helper.rb new file mode 100644 index 000000000..8bf90913d --- /dev/null +++ b/redmine/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/redmine/app/helpers/news_helper.rb b/redmine/app/helpers/news_helper.rb new file mode 100644 index 000000000..f4a633f4b --- /dev/null +++ b/redmine/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/redmine/app/helpers/projects_helper.rb b/redmine/app/helpers/projects_helper.rb new file mode 100644 index 000000000..0c85ce24c --- /dev/null +++ b/redmine/app/helpers/projects_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 ProjectsHelper +end diff --git a/redmine/app/helpers/reports_helper.rb b/redmine/app/helpers/reports_helper.rb new file mode 100644 index 000000000..ed7fd7884 --- /dev/null +++ b/redmine/app/helpers/reports_helper.rb @@ -0,0 +1,32 @@ +# 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 + } unless criteria.nil? + a = a + row["total"].to_i if match == 1 + } unless data.nil? + a + end + +end diff --git a/redmine/app/helpers/roles_helper.rb b/redmine/app/helpers/roles_helper.rb new file mode 100644 index 000000000..8ae339053 --- /dev/null +++ b/redmine/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/redmine/app/helpers/search_filter_helper.rb b/redmine/app/helpers/search_filter_helper.rb new file mode 100644 index 000000000..3a76b3f57 --- /dev/null +++ b/redmine/app/helpers/search_filter_helper.rb @@ -0,0 +1,55 @@ +# 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 SearchFilterHelper + + def search_filter_criteria(field, options = {}) + session[:search_filter] ||= {} + session[:search_filter][field] ||= options + # session[:search_filter][field][:values] = options[:values] unless options[:values].nil? + # session[:search_filter][field][:label] = options[:label] unless options[:label].nil? + end + + def search_filter_update + session[:search_filter].each_key {|field| session[:search_filter][field][:value] = params[field] } + #@search_filter[:value] = params[@search_filter[:field]] + end + + def search_filter_clause + clause = "1=1" + session[:search_filter].each {|field, criteria| clause = clause + " AND " + field + "='" + session[:search_filter][field][:value] + "'" unless session[:search_filter][field][:value].nil? || session[:search_filter][field][:value].empty? } + clause + #@search_filter[:field] + "='" + @search_filter[:value] + "'" unless @search_filter[:value].nil? || @search_filter[:value].empty? + end + + def search_filter_tag(field) + option_values = [] + #values = eval @search_filter[:values_expr] + option_values = eval session[:search_filter][field][:values] + + content_tag("select", + content_tag("option", "[All]", :value => "") + + options_from_collection_for_select(option_values, + "id", + session[:search_filter][field][:label] || "name", + session[:search_filter][field][:value].to_i + ), + :name => field + ) + end + +end \ No newline at end of file diff --git a/redmine/app/helpers/sort_helper.rb b/redmine/app/helpers/sort_helper.rb new file mode 100644 index 000000000..bec2117ec --- /dev/null +++ b/redmine/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): +# +# +# +# <%= sort_header_tag('id', :title => 'Sort by contact ID') %> +# <%= sort_header_tag('last_name', :caption => 'Name') %> +# <%= sort_header_tag('phone') %> +# <%= sort_header_tag('address', :width => 200) %> +# +# +# +# - 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' + order = 'desc' + else + icon = 'sort_desc' + order = 'asc' + end + else + icon = nil + order = 'desc' # changed for desc order by default + end + caption = titleize(Inflector::humanize(column)) unless caption + params = {:params => {:sort_key => column, :sort_order => order}} + link_to(caption, params) + (icon ? nbsp(2) + image_tag(icon) : '') + 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: + # + # + # Id + #   Sort_asc + # + # + def sort_header_tag(column, options = {}) + if options[:caption] + caption = options[:caption] + options.delete(:caption) + else + caption = titleize(Inflector::humanize(column)) + end + options[:title]= "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/redmine/app/helpers/trackers_helper.rb b/redmine/app/helpers/trackers_helper.rb new file mode 100644 index 000000000..839327efe --- /dev/null +++ b/redmine/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/redmine/app/helpers/users_helper.rb b/redmine/app/helpers/users_helper.rb new file mode 100644 index 000000000..035db3d00 --- /dev/null +++ b/redmine/app/helpers/users_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 UsersHelper +end diff --git a/redmine/app/helpers/versions_helper.rb b/redmine/app/helpers/versions_helper.rb new file mode 100644 index 000000000..e2724fe84 --- /dev/null +++ b/redmine/app/helpers/versions_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 VersionsHelper +end diff --git a/redmine/app/helpers/welcome_helper.rb b/redmine/app/helpers/welcome_helper.rb new file mode 100644 index 000000000..cace5f542 --- /dev/null +++ b/redmine/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/redmine/app/models/attachment.rb b/redmine/app/models/attachment.rb new file mode 100644 index 000000000..bc1ff5d87 --- /dev/null +++ b/redmine/app/models/attachment.rb @@ -0,0 +1,81 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +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 :filename + + 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 + self.size = @temp_file.size + end + end + 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 + 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 + "#{RDM_STORAGE_PATH}/#{self.disk_filename}" + end + + def increment_download + increment!(:downloads) + end + + # returns last created projects + def self.most_downloaded + find(:all, :limit => 5, :order => "downloads DESC") + 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/redmine/app/models/custom_field.rb b/redmine/app/models/custom_field.rb new file mode 100644 index 000000000..9e817d1ef --- /dev/null +++ b/redmine/app/models/custom_field.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 CustomField < ActiveRecord::Base + + has_and_belongs_to_many :projects + has_many :custom_values, :dependent => true + has_many :issues, :through => :issue_custom_values + + validates_presence_of :name, :typ + validates_uniqueness_of :name + + TYPES = [ + [ "Integer", 0 ], + [ "String", 1 ], + [ "Date", 2 ], + [ "Boolean", 3 ], + [ "List", 4 ] + ].freeze + + def self.for_all + find(:all, :conditions => ["is_for_all=?", true]) + end +end diff --git a/redmine/app/models/custom_value.rb b/redmine/app/models/custom_value.rb new file mode 100644 index 000000000..faaa8ff82 --- /dev/null +++ b/redmine/app/models/custom_value.rb @@ -0,0 +1,41 @@ +# 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 :issue + belongs_to :custom_field + +protected + def validate + errors.add(custom_field.name, "can't be blank") if custom_field.is_required? and value.empty? + errors.add(custom_field.name, "is not valid") unless custom_field.regexp.empty? or value =~ Regexp.new(custom_field.regexp) + + case custom_field.typ + when 0 + errors.add(custom_field.name, "must be an integer") unless value =~ /^[0-9]*$/ + when 1 + errors.add(custom_field.name, "is too short") if custom_field.min_length > 0 and value.length < custom_field.min_length and value.length > 0 + errors.add(custom_field.name, "is too long") if custom_field.max_length > 0 and value.length > custom_field.max_length + when 2 + errors.add(custom_field.name, "must be a valid date") unless value =~ /^(\d+)\/(\d+)\/(\d+)$/ or value.empty? + when 3 + + when 4 + errors.add(custom_field.name, "is not a valid value") unless custom_field.possible_values.split('|').include? value or value.empty? + end + end +end diff --git a/redmine/app/models/document.rb b/redmine/app/models/document.rb new file mode 100644 index 000000000..40c3a1656 --- /dev/null +++ b/redmine/app/models/document.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 Document < ActiveRecord::Base + belongs_to :project + belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id" + has_many :attachments, :as => :container, :dependent => true + + validates_presence_of :title +end diff --git a/redmine/app/models/enumeration.rb b/redmine/app/models/enumeration.rb new file mode 100644 index 000000000..d93db4445 --- /dev/null +++ b/redmine/app/models/enumeration.rb @@ -0,0 +1,45 @@ +# 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 + before_destroy :check_integrity + + validates_presence_of :opt, :name + + OPTIONS = [ + ["Issue priorities", "IPRI"], + ["Document categories", "DCAT"] + ].freeze + + def self.get_values(option) + find(:all, :conditions => ['opt=?', option]) + end + + def name + _ self.attributes['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]) + end + end +end diff --git a/redmine/app/models/issue.rb b/redmine/app/models/issue.rb new file mode 100644 index 000000000..4a21ac03b --- /dev/null +++ b/redmine/app/models/issue.rb @@ -0,0 +1,55 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Issue < ActiveRecord::Base + + belongs_to :project + belongs_to :tracker + belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id' + belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' + belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id' + belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' + + has_many :histories, :class_name => 'IssueHistory', :dependent => true, :order => "issue_histories.created_on DESC", :include => :status + has_many :attachments, :as => :container, :dependent => true + + has_many :custom_values, :dependent => true + has_many :custom_fields, :through => :custom_values + + validates_presence_of :subject, :descr, :priority, :tracker, :author + + # set default status for new issues + def before_create + self.status = IssueStatus.default + build_history + end + + def long_id + "%05d" % self.id + end + +private + # Creates an history for the issue + def build_history + @history = self.histories.build + @history.status = self.status + @history.author = self.author + end + +end diff --git a/redmine/app/models/issue_category.rb b/redmine/app/models/issue_category.rb new file mode 100644 index 000000000..b7d4e2623 --- /dev/null +++ b/redmine/app/models/issue_category.rb @@ -0,0 +1,28 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class IssueCategory < ActiveRecord::Base + before_destroy :check_integrity + belongs_to :project + + validates_presence_of :name + +private + def check_integrity + raise "Can't delete category" if Issue.find(:first, :conditions => ["category_id=?", self.id]) + end +end diff --git a/redmine/app/models/issue_history.rb b/redmine/app/models/issue_history.rb new file mode 100644 index 000000000..f410a39c1 --- /dev/null +++ b/redmine/app/models/issue_history.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 IssueHistory < ActiveRecord::Base + belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + + validates_presence_of :status +end diff --git a/redmine/app/models/issue_status.rb b/redmine/app/models/issue_status.rb new file mode 100644 index 000000000..ff34cb666 --- /dev/null +++ b/redmine/app/models/issue_status.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 IssueStatus < ActiveRecord::Base + before_destroy :check_integrity + has_many :workflows, :foreign_key => "old_status_id" + + validates_presence_of :name + validates_uniqueness_of :name + + # 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 + def new_statuses_allowed_to(role, tracker) + statuses = [] + for workflow in self.workflows.find(:all, :include => :new_status) + statuses << workflow.new_status if workflow.role_id == role.id and workflow.tracker_id == tracker.id + end unless role.nil? + statuses + end + + def name + _ self.attributes['name'] + end + +private + def check_integrity + raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id]) or IssueHistory.find(:first, :conditions => ["status_id=?", self.id]) + end +end diff --git a/redmine/app/models/mailer.rb b/redmine/app/models/mailer.rb new file mode 100644 index 000000000..b04ec7ebc --- /dev/null +++ b/redmine/app/models/mailer.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. + +class Mailer < ActionMailer::Base + + def issue_change_status(issue) + # Sends to all project members + @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification } + @from = 'redmine@somenet.foo' + @subject = "Issue ##{issue.id} has been updated" + @body['issue'] = issue + end + + def issue_add(issue) + # Sends to all project members + @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification } + @from = 'redmine@somenet.foo' + @subject = "Issue ##{issue.id} has been reported" + @body['issue'] = issue + end + +end diff --git a/redmine/app/models/member.rb b/redmine/app/models/member.rb new file mode 100644 index 000000000..d37936561 --- /dev/null +++ b/redmine/app/models/member.rb @@ -0,0 +1,29 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class 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 name + self.user.display_name + end +end diff --git a/redmine/app/models/news.rb b/redmine/app/models/news.rb new file mode 100644 index 000000000..0642a4bf5 --- /dev/null +++ b/redmine/app/models/news.rb @@ -0,0 +1,28 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class News < ActiveRecord::Base + belongs_to :project + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + + validates_presence_of :title, :shortdescr, :descr + + # returns last created news + def self.latest + find(:all, :limit => 5, :include => [ :author, :project ], :order => "news.created_on DESC") + end +end diff --git a/redmine/app/models/permission.rb b/redmine/app/models/permission.rb new file mode 100644 index 000000000..f66214119 --- /dev/null +++ b/redmine/app/models/permission.rb @@ -0,0 +1,63 @@ +# 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 Permission < ActiveRecord::Base + has_and_belongs_to_many :roles + + validates_presence_of :controller, :action, :descr + + GROUPS = { + 100 => "Project", + 200 => "Membres", + 300 => "Versions", + 400 => "Issue categories", + 1000 => "Issues", + 1100 => "News", + 1200 => "Documents", + 1300 => "Files", + }.freeze + + @@cached_perms_for_public = nil + @@cached_perms_for_roles = nil + + def name + self.controller + "/" + self.action + end + + def group_id + (self.sort / 100)*100 + end + + def self.allowed_to_public(action) + @@cached_perms_for_public ||= find(:all, :conditions => ["public=?", true]).collect {|p| "#{p.controller}/#{p.action}"} + @@cached_perms_for_public.include? action + end + + def self.allowed_to_role(action, role) + @@cached_perms_for_roles ||= + begin + perms = {} + find(:all, :include => :roles).each {|p| perms.store "#{p.controller}/#{p.action}", p.roles.collect {|r| r.id } } + perms + end + @@cached_perms_for_roles[action] and @@cached_perms_for_roles[action].include? role + end + + def self.allowed_to_role_expired + @@cached_perms_for_roles = nil + end +end diff --git a/redmine/app/models/project.rb b/redmine/app/models/project.rb new file mode 100644 index 000000000..7c50ee8cb --- /dev/null +++ b/redmine/app/models/project.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 Project < ActiveRecord::Base + has_many :versions, :dependent => true, :order => "versions.date DESC" + has_many :members, :dependent => true + has_many :issues, :dependent => true, :order => "issues.created_on DESC", :include => :status + has_many :documents, :dependent => true + has_many :news, :dependent => true, :order => "news.created_on DESC", :include => :author + has_many :issue_categories, :dependent => true + has_and_belongs_to_many :custom_fields + + validates_presence_of :name, :descr + + # returns 5 last created projects + def self.latest + find(:all, :limit => 5, :order => "created_on DESC") + end + + # Returns current version of the project + def current_version + versions.find(:first, :conditions => [ "date <= ?", Date.today ], :order => "date DESC, id DESC") + 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 + (CustomField.for_all + custom_fields).uniq + end +end diff --git a/redmine/app/models/role.rb b/redmine/app/models/role.rb new file mode 100644 index 000000000..ce880b5cc --- /dev/null +++ b/redmine/app/models/role.rb @@ -0,0 +1,31 @@ +# 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 + before_destroy :check_integrity + has_and_belongs_to_many :permissions + has_many :workflows, :dependent => true + has_many :members + + validates_presence_of :name + validates_uniqueness_of :name + +private + def check_integrity + raise "Can't delete role" if Member.find(:first, :conditions =>["role_id=?", self.id]) + end +end diff --git a/redmine/app/models/tracker.rb b/redmine/app/models/tracker.rb new file mode 100644 index 000000000..6b123d79c --- /dev/null +++ b/redmine/app/models/tracker.rb @@ -0,0 +1,31 @@ +# 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 => true + + def name + _ self.attributes['name'] + end + +private + def check_integrity + raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id]) + end +end diff --git a/redmine/app/models/user.rb b/redmine/app/models/user.rb new file mode 100644 index 000000000..1bc1b5802 --- /dev/null +++ b/redmine/app/models/user.rb @@ -0,0 +1,89 @@ +# 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 "digest/sha1" + +class User < ActiveRecord::Base + has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => true + + attr_accessor :password + attr_accessor :last_before_login_on + # Prevents unauthorized assignments + attr_protected :admin + + validates_presence_of :login, :firstname, :lastname, :mail + validates_uniqueness_of :login, :mail + + # Login must contain lettres, numbers, underscores only + validates_format_of :login, :with => /^[a-z0-9_]+$/i + validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i + + def before_create + self.hashed_password = User.hash_password(self.password) + end + + def after_create + @password = nil + end + + # Returns the user that matches user's login and password + def try_to_login + @user = User.login(self.login, self.password) + unless @user.nil? + @user.last_before_login_on = @user.last_login_on + @user.update_attribute(:last_login_on, DateTime.now) + end + @user + end + + # Return user's full name for display + def display_name + firstname + " " + lastname #+ (self.admin ? " (Admin)" : "" ) + end + + # Returns the user that matches the given login and password + def self.login(login, password) + hashed_password = hash_password(password || "") + find(:first, + :conditions => ["login = ? and hashed_password = ? and locked = ?", login, hashed_password, false]) + end + + def check_password?(clear_password) + User.hash_password(clear_password) == self.hashed_password + end + + def change_password(current_password, new_password) + self.hashed_password = User.hash_password(new_password) + save + end + + def role_for_project(project_id) + @role_for_projects ||= + begin + roles = {} + self.memberships.each { |m| roles.store m.project_id, m.role_id } + roles + end + @role_for_projects[project_id] + end + +private + # Return password digest + def self.hash_password(clear_password) + Digest::SHA1.hexdigest(clear_password) + end +end diff --git a/redmine/app/models/version.rb b/redmine/app/models/version.rb new file mode 100644 index 000000000..02dee15c8 --- /dev/null +++ b/redmine/app/models/version.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 Version < ActiveRecord::Base + before_destroy :check_integrity + belongs_to :project + has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id' + has_many :attachments, :as => :container, :dependent => true + + validates_presence_of :name, :descr + +private + def check_integrity + raise "Can't delete version" if self.fixed_issues.find(:first) + end +end diff --git a/redmine/app/models/workflow.rb b/redmine/app/models/workflow.rb new file mode 100644 index 000000000..212e33350 --- /dev/null +++ b/redmine/app/models/workflow.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 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/redmine/app/views/account/login.rhtml b/redmine/app/views/account/login.rhtml new file mode 100644 index 000000000..cc360ebac --- /dev/null +++ b/redmine/app/views/account/login.rhtml @@ -0,0 +1,13 @@ +
+

<%=_ 'Please login' %>

+ +<%= start_form_tag :action=> "login" %> +


+

+ +


+

+ +

+<%= end_form_tag %> +
\ No newline at end of file diff --git a/redmine/app/views/account/my_account.rhtml b/redmine/app/views/account/my_account.rhtml new file mode 100644 index 000000000..34ae4885f --- /dev/null +++ b/redmine/app/views/account/my_account.rhtml @@ -0,0 +1,54 @@ +

<%=_('My account')%>

+ +

<%=_('Login')%>: <%= @user.login %>
+<%=_('Created on')%>: <%= format_time(@user.created_on) %>, +<%=_('Last update')%>: <%= format_time(@user.updated_on) %>

+ +
+
+

<%=_('Information')%>

+   + <%= start_form_tag :action => 'my_account' %> + <%= error_messages_for 'user' %> + + +


+ <%= text_field 'user', 'firstname' %>

+ +


+ <%= text_field 'user', 'lastname' %>

+ +


+ <%= text_field 'user', 'mail' %>

+ +


+ <%= select("user", "language", Localization.langs) %>

+ + +

<%= check_box 'user', 'mail_notification' %>

+ +
<%= submit_tag _('Save') %>
+ <%= end_form_tag %> +
+
+ + +
+
+

<%=_('Password')%>

+   + <%= start_form_tag :action => 'change_password' %> + +


+ <%= password_field_tag 'old_password' %>

+ +


+ <%= password_field_tag 'new_password' %>

+ +


+ <%= password_field_tag 'new_password_confirmation' %>

+ +
<%= submit_tag _('Save') %>
+ <%= end_form_tag %> +
+
\ No newline at end of file diff --git a/redmine/app/views/account/my_page.rhtml b/redmine/app/views/account/my_page.rhtml new file mode 100644 index 000000000..7f6458262 --- /dev/null +++ b/redmine/app/views/account/my_page.rhtml @@ -0,0 +1,19 @@ +

<%=_('My page') %>

+ +

+<%=_('Welcome')%> <%= @user.firstname %>
+<% unless @user.last_before_login_on.nil? %> + <%=_('Last login')%>: <%= format_time(@user.last_before_login_on) %> +<% end %> +

+ +
+

<%=_('Reported issues')%>

+ <%= render :partial => 'issues/list_simple', :locals => { :issues => @reported_issues } %> + <%= "

(Last #{@reported_issues.length} updated)

" if @reported_issues.length > 0 %> +
+
+

<%=_('Assigned to me')%>

+ <%= render :partial => 'issues/list_simple', :locals => { :issues => @assigned_issues } %> + <%= "

(Last #{@assigned_issues.length} updated)

" if @assigned_issues.length > 0 %> +
\ No newline at end of file diff --git a/redmine/app/views/account/show.rhtml b/redmine/app/views/account/show.rhtml new file mode 100644 index 000000000..df918e5bf --- /dev/null +++ b/redmine/app/views/account/show.rhtml @@ -0,0 +1,19 @@ +

<%= @user.display_name %>

+ +

+<%= mail_to @user.mail %>
+<%=_('Registered on')%>: <%= format_date(@user.created_on) %> +

+ +

<%=_('Projects')%>

+

+<% for membership in @user.memberships %> + <%= membership.project.name %> (<%= membership.role.name %>, <%= format_date(membership.created_on) %>) +
+<% end %> +

+ +

<%=_('Activity')%>

+

+<%=_('Reported issues')%>: <%= Issue.count( [ "author_id=?", @user.id]) %> +

\ No newline at end of file diff --git a/redmine/app/views/admin/index.rhtml b/redmine/app/views/admin/index.rhtml new file mode 100644 index 000000000..b3607d813 --- /dev/null +++ b/redmine/app/views/admin/index.rhtml @@ -0,0 +1,45 @@ +

<%=_('Administration')%>

+ +

+<%= image_tag "projects" %> +<%= link_to _('Projects'), :controller => 'admin', :action => 'projects' %> | +<%= link_to _('New'), :controller => 'projects', :action => 'add' %> +

+ +

+<%= image_tag "users" %> +<%= link_to _('Users'), :controller => 'users' %> | +<%= link_to _('New'), :controller => 'users', :action => 'add' %> +

+ +

+<%= image_tag "role" %> +<%= link_to _('Roles and permissions'), :controller => 'roles' %> +

+ +

+<%= image_tag "tracker" %> +<%= link_to _('Trackers'), :controller => 'trackers' %> | +<%= link_to _('Custom fields'), :controller => 'custom_fields' %> +

+ +

+<%= image_tag "workflow" %> +<%= link_to _('Issue Statuses'), :controller => 'issue_statuses' %> | +<%= link_to _('Workflow'), :controller => 'roles', :action => 'workflow' %> +

+ +

+<%= image_tag "options" %> +<%= link_to _('Enumerations'), :controller => 'enumerations' %> +

+ +

+<%= image_tag "mailer" %> +<%= link_to _('Mail notifications'), :controller => 'admin', :action => 'mail_options' %> +

+ +

+<%= image_tag "help" %> +<%= link_to _('Information'), :controller => 'admin', :action => 'info' %> +

\ No newline at end of file diff --git a/redmine/app/views/admin/info.rhtml b/redmine/app/views/admin/info.rhtml new file mode 100644 index 000000000..c73f59c25 --- /dev/null +++ b/redmine/app/views/admin/info.rhtml @@ -0,0 +1,4 @@ +

<%=_('Information')%>

+ +<%=_('Version')%>: <%= RDM_APP_NAME %> <%= RDM_APP_VERSION %>
+<%=_('Database')%>: <%= @adapter_name %> \ No newline at end of file diff --git a/redmine/app/views/admin/mail_options.rhtml b/redmine/app/views/admin/mail_options.rhtml new file mode 100644 index 000000000..2d1d80ebb --- /dev/null +++ b/redmine/app/views/admin/mail_options.rhtml @@ -0,0 +1,16 @@ +

<%=_('Mail notifications')%>

+ +

<%=_('Select actions for which mail notification should be enabled.')%>

+ +<%= start_form_tag ({}, :id => 'mail_options_form')%> +<% for action in @actions %> + <%= check_box_tag "action_ids[]", action.id, action.mail_enabled? %> + <%= action.descr %>
+<% end %> +
+

+<%=_('Check all')%> | +<%=_('Uncheck all')%> +

+<%= submit_tag _('Save') %> +<%= end_form_tag %> \ No newline at end of file diff --git a/redmine/app/views/admin/projects.rhtml b/redmine/app/views/admin/projects.rhtml new file mode 100644 index 000000000..dd3953518 --- /dev/null +++ b/redmine/app/views/admin/projects.rhtml @@ -0,0 +1,35 @@ +

<%=_('Projects')%>

+ + + + <%= sort_header_tag('projects.name', :caption => _('Project')) %> + + + <%= sort_header_tag('projects.created_on', :caption => _('Created on')) %> + + + +<% odd_or_even = 1 + for project in @projects + odd_or_even = 1 - odd_or_even %> + + + +<% end %> +
<%=_('Description')%><%=_('Public')%>
<%= link_to project.name, :controller => 'projects', :action => 'settings', :id => project %> + <%= project.descr %> + <%= image_tag 'true' if project.public? %> + <%= format_date(project.created_on) %> + + <%= start_form_tag({:controller => 'projects', :action => 'destroy', :id => project}) %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+ +<%= link_to ('« ' + _('Previous')), { :page => @project_pages.current.previous } if @project_pages.current.previous %> +<%= pagination_links(@project_pages) %> +<%= link_to (_('Next') + ' »'), { :page => @project_pages.current.next } if @project_pages.current.next %> + +
+ +<%= link_to ('» ' + _('New project')), :controller => 'projects', :action => 'add' %> \ No newline at end of file diff --git a/redmine/app/views/custom_fields/_form.rhtml b/redmine/app/views/custom_fields/_form.rhtml new file mode 100644 index 000000000..d268461fc --- /dev/null +++ b/redmine/app/views/custom_fields/_form.rhtml @@ -0,0 +1,26 @@ +<%= error_messages_for 'custom_field' %> + + +


+<%= text_field 'custom_field', 'name' %>

+ +


+<%= select("custom_field", "typ", CustomField::TYPES) %>

+ +

<%= check_box 'custom_field', 'is_required' %> +

+ +

<%= check_box 'custom_field', 'is_for_all' %> +

+ +

(<%=_('0 means no restriction')%>)
+<%= text_field 'custom_field', 'min_length', :size => 5 %> - +<%= text_field 'custom_field', 'max_length', :size => 5 %>

+ +

(eg. ^[A-Z0-9]+$)
+<%= text_field 'custom_field', 'regexp', :size => 50 %>

+ +

(separator: |)
+<%= text_area 'custom_field', 'possible_values', :rows => 5, :cols => 60 %>

+ + diff --git a/redmine/app/views/custom_fields/edit.rhtml b/redmine/app/views/custom_fields/edit.rhtml new file mode 100644 index 000000000..ab4ea8b24 --- /dev/null +++ b/redmine/app/views/custom_fields/edit.rhtml @@ -0,0 +1,6 @@ +

<%=_('Custom field')%>

+ +<%= start_form_tag :action => 'edit', :id => @custom_field %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> diff --git a/redmine/app/views/custom_fields/list.rhtml b/redmine/app/views/custom_fields/list.rhtml new file mode 100644 index 000000000..0e29a5b37 --- /dev/null +++ b/redmine/app/views/custom_fields/list.rhtml @@ -0,0 +1,32 @@ +

<%=_('Custom fields')%>

+ + + + + + + + + + +<% for custom_field in @custom_fields %> + + + + + + + + +<% end %> +
<%=_('Name')%><%=_('Type')%><%=_('Required')%><%=_('For all projects')%><%=_('Used by')%>
<%= link_to custom_field.name, :action => 'edit', :id => custom_field %><%= CustomField::TYPES[custom_field.typ][0] %><%= image_tag 'true' if custom_field.is_required? %><%= image_tag 'true' if custom_field.is_for_all? %><%= custom_field.projects.count.to_s + ' ' + _('Project') + '(s)' unless custom_field.is_for_all? %> + <%= start_form_tag :action => 'destroy', :id => custom_field %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %>
+ +<%= link_to ('« ' + _('Previous')), { :page => @custom_field_pages.current.previous } if @custom_field_pages.current.previous %> +<%= link_to (_('Next') + ' »'), { :page => @custom_field_pages.current.next } if @custom_field_pages.current.next %> + +
+ +<%= link_to ('» ' + _('New custom field')), :action => 'new' %> diff --git a/redmine/app/views/custom_fields/new.rhtml b/redmine/app/views/custom_fields/new.rhtml new file mode 100644 index 000000000..0e6492a28 --- /dev/null +++ b/redmine/app/views/custom_fields/new.rhtml @@ -0,0 +1,7 @@ +

<%=_('New custom field')%>

+ +<%= start_form_tag :action => 'new' %> + <%= render :partial => 'form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/documents/_form.rhtml b/redmine/app/views/documents/_form.rhtml new file mode 100644 index 000000000..4440a156e --- /dev/null +++ b/redmine/app/views/documents/_form.rhtml @@ -0,0 +1,15 @@ +<%= error_messages_for 'document' %> + + +


+

+ +


+<%= text_field 'document', 'title', :size => 60 %>

+ +


+<%= text_area 'document', 'descr', :cols => 60, :rows => 5 %>

+ + diff --git a/redmine/app/views/documents/edit.rhtml b/redmine/app/views/documents/edit.rhtml new file mode 100644 index 000000000..2f1e9a94c --- /dev/null +++ b/redmine/app/views/documents/edit.rhtml @@ -0,0 +1,8 @@ +

<%=_('Document')%>

+ +<%= start_form_tag :action => 'edit', :id => @document %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> + + diff --git a/redmine/app/views/documents/show.rhtml b/redmine/app/views/documents/show.rhtml new file mode 100644 index 000000000..bc9557e55 --- /dev/null +++ b/redmine/app/views/documents/show.rhtml @@ -0,0 +1,45 @@ +

<%= @document.title %>

+ +<%=_('Description')%>: <%= @document.descr %>
+<%=_('Category')%>: <%= @document.category.name %>
+
+ +<% if authorize_for('documents', 'edit') %> + <%= start_form_tag ({ :controller => 'documents', :action => 'edit', :id => @document }, :method => 'get' ) %> + <%= submit_tag _('Edit') %> + <%= end_form_tag %> +<% end %> + +<% if authorize_for('documents', 'destroy') %> + <%= start_form_tag ({ :controller => 'documents', :action => 'destroy', :id => @document } ) %> + <%= submit_tag _('Delete') %> + <%= end_form_tag %> +<% end %> + +

+ + +<% for attachment in @document.attachments %> + + + + + + <% if authorize_for('documents', 'destroy_attachment') %> + + <% end %> +<% end %> +
<%= link_to attachment.filename, :action => 'download', :id => @document, :attachment_id => attachment %><%= format_date(attachment.created_on) %><%= attachment.author.display_name %><%= human_size(attachment.size) %>
<%= attachment.downloads %> <%=_('download')%>(s)
+ <%= start_form_tag :action => 'destroy_attachment', :id => @document, :attachment_id => attachment %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+
+ +<% if authorize_for('documents', 'add_attachment') %> + <%= start_form_tag ({ :controller => 'documents', :action => 'add_attachment', :id => @document }, :multipart => true) %> + <%=_('Add file')%>
<%= file_field 'attachment', 'file' %> + <%= submit_tag _('Add') %> + <%= end_form_tag %> +<% end %> + diff --git a/redmine/app/views/enumerations/_form.rhtml b/redmine/app/views/enumerations/_form.rhtml new file mode 100644 index 000000000..d78dc358c --- /dev/null +++ b/redmine/app/views/enumerations/_form.rhtml @@ -0,0 +1,9 @@ +<%= error_messages_for 'enumeration' %> + + +<%= hidden_field 'enumeration', 'opt' %> + +


+<%= text_field 'enumeration', 'name' %>

+ + diff --git a/redmine/app/views/enumerations/edit.rhtml b/redmine/app/views/enumerations/edit.rhtml new file mode 100644 index 000000000..16bd377f3 --- /dev/null +++ b/redmine/app/views/enumerations/edit.rhtml @@ -0,0 +1,10 @@ +

<%=_('Enumerations')%>

+ +<%= start_form_tag :action => 'update', :id => @enumeration %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> + +<%= start_form_tag :action => 'destroy', :id => @enumeration %> + <%= submit_tag _('Delete') %> +<%= end_form_tag %> \ No newline at end of file diff --git a/redmine/app/views/enumerations/list.rhtml b/redmine/app/views/enumerations/list.rhtml new file mode 100644 index 000000000..b5ce65c23 --- /dev/null +++ b/redmine/app/views/enumerations/list.rhtml @@ -0,0 +1,22 @@ +

<%=_('Enumerations')%>

+ +<% for option in Enumeration::OPTIONS %> + + <% if @params[:opt]==option[1] %> + +

<%= image_tag 'dir_open' %> <%=_ option[0] %>

+
    + <% for value in Enumeration::find(:all, :conditions => [ "opt = ?", option[1]]) %> +
  • <%= link_to value.name, :action => 'edit', :id => value %>
  • + <% end %> +
+
    +
  • <%= link_to ('» ' + _('New')), :action => 'new', :opt => option[1] %>
  • +
+ + <% else %> +

<%= image_tag 'dir' %> <%= link_to _(option[0]), :opt => option[1] %>

+ <% end %> + +<% end %> + diff --git a/redmine/app/views/enumerations/new.rhtml b/redmine/app/views/enumerations/new.rhtml new file mode 100644 index 000000000..30048f2fd --- /dev/null +++ b/redmine/app/views/enumerations/new.rhtml @@ -0,0 +1,6 @@ +

<%=_('New enumeration')%>

+ +<%= start_form_tag :action => 'create' %> + <%= render :partial => 'form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> diff --git a/redmine/app/views/issue_categories/_form.rhtml b/redmine/app/views/issue_categories/_form.rhtml new file mode 100644 index 000000000..eba2f1672 --- /dev/null +++ b/redmine/app/views/issue_categories/_form.rhtml @@ -0,0 +1,7 @@ +<%= error_messages_for 'issue_category' %> + + +


+<%= text_field 'issue_category', 'name' %>

+ + diff --git a/redmine/app/views/issue_categories/edit.rhtml b/redmine/app/views/issue_categories/edit.rhtml new file mode 100644 index 000000000..3afdd1c63 --- /dev/null +++ b/redmine/app/views/issue_categories/edit.rhtml @@ -0,0 +1,6 @@ +

Editing issue category

+ +<%= start_form_tag :action => 'edit', :id => @category %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> diff --git a/redmine/app/views/issue_statuses/_form.rhtml b/redmine/app/views/issue_statuses/_form.rhtml new file mode 100644 index 000000000..5f4b9ce3d --- /dev/null +++ b/redmine/app/views/issue_statuses/_form.rhtml @@ -0,0 +1,17 @@ +<%= error_messages_for 'issue_status' %> + + +


+<%= text_field 'issue_status', 'name' %>

+ +

<%= check_box 'issue_status', 'is_closed' %> +

+ +

<%= check_box 'issue_status', 'is_default' %> +

+ +

+#<%= text_field 'issue_status', 'html_color', :size => 6 %>

+ + + diff --git a/redmine/app/views/issue_statuses/edit.rhtml b/redmine/app/views/issue_statuses/edit.rhtml new file mode 100644 index 000000000..d3ed92201 --- /dev/null +++ b/redmine/app/views/issue_statuses/edit.rhtml @@ -0,0 +1,6 @@ +

<%=_('Issue status')%>

+ +<%= start_form_tag :action => 'update', :id => @issue_status %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> diff --git a/redmine/app/views/issue_statuses/list.rhtml b/redmine/app/views/issue_statuses/list.rhtml new file mode 100644 index 000000000..050e08f0f --- /dev/null +++ b/redmine/app/views/issue_statuses/list.rhtml @@ -0,0 +1,30 @@ +

<%=_('Issue statuses')%>

+ + + + + + + + + + +<% for status in @issue_statuses %> + + + + + + + +<% end %> +
<%=_('Status')%><%=_('Default status')%><%=_('Issue closed')%><%=_('Color')%>
<%= link_to status.name, :action => 'edit', :id => status %><%= image_tag 'true' if status.is_default %><%= image_tag 'true' if status.is_closed %>  + <%= start_form_tag :action => 'destroy', :id => status %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+ +<%= pagination_links_full @issue_status_pages %> +
+ +<%= link_to '» ' + _('New issue status'), :action => 'new' %> diff --git a/redmine/app/views/issue_statuses/new.rhtml b/redmine/app/views/issue_statuses/new.rhtml new file mode 100644 index 000000000..f7ac082e9 --- /dev/null +++ b/redmine/app/views/issue_statuses/new.rhtml @@ -0,0 +1,6 @@ +

<%=_('New issue status')%>

+ +<%= start_form_tag :action => 'create' %> + <%= render :partial => 'form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> diff --git a/redmine/app/views/issues/_list_simple.rhtml b/redmine/app/views/issues/_list_simple.rhtml new file mode 100644 index 000000000..66b70a1da --- /dev/null +++ b/redmine/app/views/issues/_list_simple.rhtml @@ -0,0 +1,28 @@ +<% if issues.length > 0 %> + + + +
+ + + + + + + <% for issue in issues %> + + + + + + <% end %> +
#<%=_('Tracker')%><%=_('Subject')%>
+ <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %>
+

<%= issue.project.name %> - <%= issue.tracker.name %>
+ <%= issue.status.name %> - <%= format_time(issue.updated_on) %>

+

<%= link_to issue.subject, :controller => 'issues', :action => 'show', :id => issue %>

+
+
+<% else %> + <%=_('No issue')%> +<% end %> \ No newline at end of file diff --git a/redmine/app/views/issues/change_status.rhtml b/redmine/app/views/issues/change_status.rhtml new file mode 100644 index 000000000..58032cefc --- /dev/null +++ b/redmine/app/views/issues/change_status.rhtml @@ -0,0 +1,29 @@ +

<%=_('Issue')%> #<%= @issue.id %>: <%= @issue.subject %>

+ +<%= error_messages_for 'history' %> +<%= start_form_tag :action => 'change_status', :id => @issue %> + +<%= hidden_field_tag 'confirm', 1 %> +<%= hidden_field 'history', 'status_id' %> + +

<%=_('New status')%>: <%= @history.status.name %>

+ +
+


+

+
+ +


+

+ +


+<%= text_area 'history', 'notes', :cols => 60, :rows => 10 %>

+ +<%= submit_tag _('Save') %> +<%= end_form_tag %> diff --git a/redmine/app/views/issues/edit.rhtml b/redmine/app/views/issues/edit.rhtml new file mode 100644 index 000000000..1249cfc8d --- /dev/null +++ b/redmine/app/views/issues/edit.rhtml @@ -0,0 +1,62 @@ +

<%=_('Issue')%> #<%= @issue.id %>

+ +<%= error_messages_for 'issue' %> +<%= start_form_tag :action => 'edit', :id => @issue %> + + +

<%=_('Status')%>: <%= @issue.status.name %>

+ +
+


+

+
+ +
+


+

+
+ +
+


+

+
+ +
+


+

+
+ +

*
+<%= text_field 'issue', 'subject', :size => 60 %>

+ +

*
+<%= text_area 'issue', 'descr', :cols => 60, :rows => 10 %>

+ + +<% for custom_value in @custom_values %> +

<%= content_tag "label", custom_value.custom_field.name %> + <% if custom_value.custom_field.is_required? %>*<% end %> +
+ <%= custom_field_tag custom_value %>

+<% end %> + + +


+

+ + +
<%= submit_tag _('Save') %>
+<%= end_form_tag %> diff --git a/redmine/app/views/issues/show.rhtml b/redmine/app/views/issues/show.rhtml new file mode 100644 index 000000000..1c20c92dd --- /dev/null +++ b/redmine/app/views/issues/show.rhtml @@ -0,0 +1,90 @@ + +

<%=_('Issue')%> #<%= @issue.id %> - <%= @issue.subject %>

+ +
+

<%=_('Tracker')%>: <%= @issue.tracker.name %>

+

<%=_('Priority')%>: <%= @issue.priority.name %>

+

<%=_('Category')%>: <%= @issue.category.name unless @issue.category_id.nil? %>

+

<%=_('Status')%>: <%= @issue.status.name %>

+

<%=_('Author')%>: <%= @issue.author.display_name %>

+

<%=_('Assigned to')%>: <%= @issue.assigned_to.display_name unless @issue.assigned_to.nil? %>

+ +

<%=_('Subject')%>: <%= @issue.subject %>

+

<%=_('Description')%>: <%= @issue.descr %>

+

<%=_('Created on')%>: <%= format_date(@issue.created_on) %>

+ +<% if authorize_for('issues', 'edit') %> + <%= start_form_tag ({:controller => 'issues', :action => 'edit', :id => @issue}, :method => "get" ) %> + <%= submit_tag _('Edit') %> + <%= end_form_tag %> +    +<% end %> + +<% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %> + <%= start_form_tag ({:controller => 'issues', :action => 'change_status', :id => @issue}) %> + + + <%= submit_tag _ "Update..." %> + <%= end_form_tag %> +    +<% end %> + +<% if authorize_for('issues', 'destroy') %> + <%= start_form_tag ({:controller => 'issues', :action => 'destroy', :id => @issue} ) %> + <%= submit_tag _ "Delete" %> + <%= end_form_tag %> +    +<% end %> + +
+ + +
+
+

<%=_('History')%>

+ +<% for history in @issue.histories.find(:all, :include => :author) %> + + + + + +<% if history.notes? %> + +<% end %> +<% end %> +
<%= format_date(history.created_on) %><%= history.author.display_name %><%= history.status.name %>
<%= history.notes %>
+
+
+ +
+
+

<%=_('Attachments')%>

+ +<% for attachment in @issue.attachments %> + + + + +<% if authorize_for('issues', 'destroy_attachment') %> + +<% end %> + +<% end %> +
<%= link_to attachment.filename, :action => 'download', :id => @issue, :attachment_id => attachment %> (<%= human_size(attachment.size) %>)<%= format_date(attachment.created_on) %><%= attachment.author.display_name %> + <%= start_form_tag :action => 'destroy_attachment', :id => @issue, :attachment_id => attachment %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+
+<% if authorize_for('issues', 'add_attachment') %> + <%= start_form_tag ({ :controller => 'issues', :action => 'add_attachment', :id => @issue }, :multipart => true) %> + <%=_('Add file')%>: <%= file_field 'attachment', 'file' %> + <%= submit_tag _('Add') %> + <%= end_form_tag %> +<% end %> +
+
+ diff --git a/redmine/app/views/layouts/base.rhtml b/redmine/app/views/layouts/base.rhtml new file mode 100644 index 000000000..731632fa4 --- /dev/null +++ b/redmine/app/views/layouts/base.rhtml @@ -0,0 +1,89 @@ + + + +redMine + + + +<%= stylesheet_link_tag "application" %> +<%= stylesheet_link_tag "rails" %> +<%= javascript_include_tag :defaults %> + + + +
+ + + + + +
+ + <% unless @project.nil? || @project.id.nil? %> +

<%= @project.name %>

+ + <% end %> + + <% unless session[:user].nil? %> +

<%=_('My projects') %>

+ + <% end %> + +
+ +
+ <% if flash[:notice] %>

<%= flash[:notice] %>

<% end %> + <%= @content_for_layout %> +
+ + + +
+ + \ No newline at end of file diff --git a/redmine/app/views/mailer/_issue.rhtml b/redmine/app/views/mailer/_issue.rhtml new file mode 100644 index 000000000..1f238f513 --- /dev/null +++ b/redmine/app/views/mailer/_issue.rhtml @@ -0,0 +1,6 @@ +<%=_('Issue')%> #<%= issue.id %> - <%= issue.subject %> +<%=_('Author')%>: <%= issue.author.display_name %> + +<%= issue.descr %> + +http://<%= RDM_HOST_NAME %>/issues/show/<%= issue.id %> \ No newline at end of file diff --git a/redmine/app/views/mailer/issue_add.rhtml b/redmine/app/views/mailer/issue_add.rhtml new file mode 100644 index 000000000..9efec9ad9 --- /dev/null +++ b/redmine/app/views/mailer/issue_add.rhtml @@ -0,0 +1,3 @@ +Issue #<%= @issue.id %> has been reported. +---------------------------------------- +<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> \ No newline at end of file diff --git a/redmine/app/views/mailer/issue_change_status.rhtml b/redmine/app/views/mailer/issue_change_status.rhtml new file mode 100644 index 000000000..e3612ea81 --- /dev/null +++ b/redmine/app/views/mailer/issue_change_status.rhtml @@ -0,0 +1,3 @@ +Issue #<%= @issue.id %> has been updated to "<%= @issue.status.name %>" status. +---------------------------------------- +<%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> \ No newline at end of file diff --git a/redmine/app/views/news/_form.rhtml b/redmine/app/views/news/_form.rhtml new file mode 100644 index 000000000..609b15cd9 --- /dev/null +++ b/redmine/app/views/news/_form.rhtml @@ -0,0 +1,13 @@ +<%= error_messages_for 'news' %> + + +

*
+<%= text_field 'news', 'title', :size => 60 %>

+ +


+<%= text_area 'news', 'shortdescr', :cols => 60, :rows => 2 %>

+ +


+<%= text_area 'news', 'descr', :cols => 60, :rows => 10 %>

+ + diff --git a/redmine/app/views/news/edit.rhtml b/redmine/app/views/news/edit.rhtml new file mode 100644 index 000000000..2e849ab1d --- /dev/null +++ b/redmine/app/views/news/edit.rhtml @@ -0,0 +1,6 @@ +

<%=_('News')%>

+ +<%= start_form_tag :action => 'edit', :id => @news %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> diff --git a/redmine/app/views/news/show.rhtml b/redmine/app/views/news/show.rhtml new file mode 100644 index 000000000..30aa2cda9 --- /dev/null +++ b/redmine/app/views/news/show.rhtml @@ -0,0 +1,10 @@ +

<%= @news.title %>

+ +

+<%=_('Summary')%>: <%= @news.shortdescr %>
+<%=_('By')%>: <%= @news.author.display_name %>
+<%=_('Date')%>: <%= format_time(@news.created_on) %> +

+ +<%= @news.descr %> + diff --git a/redmine/app/views/projects/_form.rhtml b/redmine/app/views/projects/_form.rhtml new file mode 100644 index 000000000..2d38117a4 --- /dev/null +++ b/redmine/app/views/projects/_form.rhtml @@ -0,0 +1,28 @@ +<%= error_messages_for 'project' %> + + +


+<%= text_field 'project', 'name' %>

+ +


+<%= text_field 'project', 'descr', :size => 60 %>

+ +


+<%= text_field 'project', 'homepage', :size => 40 %>

+ +

<%= check_box 'project', 'public' %> +

+ +
<%=_('Custom fields')%> +<% for custom_field in @custom_fields %> + checked="checked"<%end%> + > <%= custom_field.name %> + +<% end %>
+
+ + diff --git a/redmine/app/views/projects/add.rhtml b/redmine/app/views/projects/add.rhtml new file mode 100644 index 000000000..6344705b0 --- /dev/null +++ b/redmine/app/views/projects/add.rhtml @@ -0,0 +1,7 @@ +

<%=_('New project')%>

+ +<%= start_form_tag :action => 'add' %> +<%= render :partial => 'form' %> +<%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/projects/add_document.rhtml b/redmine/app/views/projects/add_document.rhtml new file mode 100644 index 000000000..13e75dd77 --- /dev/null +++ b/redmine/app/views/projects/add_document.rhtml @@ -0,0 +1,26 @@ +

<%=_('New document')%>

+ +<%= error_messages_for 'document' %> +<%= start_form_tag( { :action => 'add_document', :id => @project }, :multipart => true) %> + + +


+

+ +


+<%= text_field 'document', 'title', :size => 60 %>

+ +


+<%= text_area 'document', 'descr', :cols => 60, :rows => 5 %>

+ +


+<%= file_field 'attachment', 'file' %>

+ + + +<%= submit_tag _('Create') %> +<%= end_form_tag %> + + diff --git a/redmine/app/views/projects/add_file.rhtml b/redmine/app/views/projects/add_file.rhtml new file mode 100644 index 000000000..4e8ce9b02 --- /dev/null +++ b/redmine/app/views/projects/add_file.rhtml @@ -0,0 +1,13 @@ +

<%=_('New file')%>

+ +<%= start_form_tag ({ :action => 'add_file', :project => @project }, :multipart => true) %> + +


+

+ +

<%=_('File')%>
<%= file_field 'attachment', 'file' %>

+
+<%= submit_tag _('Add') %> +<%= end_form_tag %> \ No newline at end of file diff --git a/redmine/app/views/projects/add_issue.rhtml b/redmine/app/views/projects/add_issue.rhtml new file mode 100644 index 000000000..a6b37cc46 --- /dev/null +++ b/redmine/app/views/projects/add_issue.rhtml @@ -0,0 +1,62 @@ +

<%=_('New issue')%>

+ +<%= start_form_tag( { :action => 'add_issue', :id => @project }, :multipart => true) %> +<%= error_messages_for 'issue' %> + + + +
+


+

+
+ +
+


+

+
+ +
+


+

+
+ +
+


+

+
+ +


+<%= text_field 'issue', 'subject', :size => 80 %>

+ +


+<%= text_area 'issue', 'descr', :cols => 60, :rows => 10 %>

+ + +<% for custom_value in @custom_values %> +
+

<%= content_tag "label", custom_value.custom_field.name %> + <% if custom_value.custom_field.is_required? %>*<% end %> +
+ <%= custom_field_tag custom_value %>

+
+<% end %> + +
+


+<%= file_field 'attachment', 'file' %>

+
+ + + +<%= submit_tag _('Create') %> +<%= end_form_tag %> \ No newline at end of file diff --git a/redmine/app/views/projects/add_news.rhtml b/redmine/app/views/projects/add_news.rhtml new file mode 100644 index 000000000..c106e7c21 --- /dev/null +++ b/redmine/app/views/projects/add_news.rhtml @@ -0,0 +1,7 @@ +

<%=('Add news')%>

+ +<%= start_form_tag :action => 'add_news', :id => @project %> + <%= render :partial => 'news/form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/projects/add_version.rhtml b/redmine/app/views/projects/add_version.rhtml new file mode 100644 index 000000000..27b258fab --- /dev/null +++ b/redmine/app/views/projects/add_version.rhtml @@ -0,0 +1,7 @@ +

<%=_('New version')%>

+ +<%= start_form_tag :action => 'add_version', :id => @project %> + <%= render :partial => 'versions/form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/projects/changelog.rhtml b/redmine/app/views/projects/changelog.rhtml new file mode 100644 index 000000000..3f97abccd --- /dev/null +++ b/redmine/app/views/projects/changelog.rhtml @@ -0,0 +1,12 @@ +

<%=_('Change log')%>

+ +<% fixed_issues = @fixed_issues.group_by {|i| i.fixed_version } %> +<% fixed_issues.each do |version, issues| %> +

<%= version.name %> - <%= format_date(version.date) %>
+ <%=h version.descr %>

+
    + <% issues.each do |i| %> +
  • <%= link_to i.long_id, :controller => 'issues', :action => 'show', :id => i %> [<%= i.tracker.name %>]: <%= i.subject %>
  • + <% end %> +
+<% end %> diff --git a/redmine/app/views/projects/destroy.rhtml b/redmine/app/views/projects/destroy.rhtml new file mode 100644 index 000000000..2e26478d9 --- /dev/null +++ b/redmine/app/views/projects/destroy.rhtml @@ -0,0 +1,12 @@ +

<%=_('Confirmation')%>

+
+
+

<%=_('Are you sure you want to delete project')%> <%= @project.name %> ?

+

+ <%= start_form_tag({:controller => 'projects', :action => 'destroy', :id => @project}) %> + <%= hidden_field_tag "confirm", 1 %> + <%= submit_tag _('Delete') %> + <%= end_form_tag %> +

+
+
\ No newline at end of file diff --git a/redmine/app/views/projects/list.rhtml b/redmine/app/views/projects/list.rhtml new file mode 100644 index 000000000..2b2ac2d13 --- /dev/null +++ b/redmine/app/views/projects/list.rhtml @@ -0,0 +1,22 @@ +

<%=_('Public projects')%>

+ + + + <%= sort_header_tag('projects.name', :caption => _('Project')) %> + + <%= sort_header_tag('projects.created_on', :caption => _('Created on')) %> + + +<% odd_or_even = 1 + for project in @projects + odd_or_even = 1 - odd_or_even %> + + +<% end %> +
Description
<%= link_to project.name, :action => 'show', :id => project %> + <%= project.descr %> + <%= format_date(project.created_on) %> +
+ +<%= pagination_links_full @project_pages %> +[ <%= @project_pages.current.first_item %> - <%= @project_pages.current.last_item %> / <%= @project_count %> ] \ No newline at end of file diff --git a/redmine/app/views/projects/list_documents.rhtml b/redmine/app/views/projects/list_documents.rhtml new file mode 100644 index 000000000..1c7db5ec9 --- /dev/null +++ b/redmine/app/views/projects/list_documents.rhtml @@ -0,0 +1,21 @@ +

<%=_('Documents')%>

+ +<% documents = @documents.group_by {|d| d.category } %> +<% documents.each do |category, docs| %> +

<%= category.name %>

+
    +<% docs.each do |d| %> +
  • + <%= link_to d.title, :controller => 'documents', :action => 'show', :id => d %> +
    + <%=_('Desciption')%>: <%= d.descr %>
    + <%= format_time(d.created_on) %> +
  • + +<% end %> +
+<% end %> + +

+<%= link_to_if_authorized '» ' + _('New'), :controller => 'projects', :action => 'add_document', :id => @project %> +

diff --git a/redmine/app/views/projects/list_files.rhtml b/redmine/app/views/projects/list_files.rhtml new file mode 100644 index 000000000..217e679b5 --- /dev/null +++ b/redmine/app/views/projects/list_files.rhtml @@ -0,0 +1,47 @@ +

<%=_('Files')%>

+ +<% delete_allowed = authorize_for('versions', 'destroy_file') %> + + + + + + + + + + <% if delete_allowed %><% end %> + + +<% for version in @versions %> + + + + <% odd_or_even = 1 + for file in version.attachments + odd_or_even = 1 - odd_or_even %> + + + + + + + + <% if delete_allowed %> + + <% end %> + + <% end %> +<% end %> +
<%=_('Version')%><%=_('File')%><%=_('Date')%><%=_('Size')%>D/LMD5
<%= image_tag 'package' %> <%= version.name %>
<%= link_to file.filename, :controller => 'versions', :action => 'download', :id => version, :attachment_id => file %><%= format_date(file.created_on) %><%= human_size(file.size) %><%= file.downloads %><%= file.digest %> + <%= start_form_tag :controller => 'versions', :action => 'destroy_file', :id => version, :attachment_id => file %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+ +
+

+<%= link_to_if_authorized '» ' + _('New'), :controller => 'projects', :action => 'add_file', :id => @project %> +

+ + diff --git a/redmine/app/views/projects/list_issues.rhtml b/redmine/app/views/projects/list_issues.rhtml new file mode 100644 index 000000000..5be81b11c --- /dev/null +++ b/redmine/app/views/projects/list_issues.rhtml @@ -0,0 +1,56 @@ +

<%=_('Issues')%>

+ +
+ + + + + + + + + +
<%=_('Status')%>:
<%= search_filter_tag("issues.status_id") %>
<%=_('Tracker')%>:
<%= search_filter_tag("issues.tracker_id") %>
<%=_('Priority')%>:
<%= search_filter_tag("issues.priority_id") %>
<%=_('Category')%>:
<%= search_filter_tag("issues.category_id") %>
<%=_('Author')%>:
<%= search_filter_tag("issues.author_id") %>
+ <%= submit_tag _('Apply filter') %> + <%= end_form_tag %> + + <%= start_form_tag %> + <%= submit_tag _('Reset') %> + <%= end_form_tag %> +
+ +   + + + + + <%= sort_header_tag('issues.id', :caption => '#') %> + <%= sort_header_tag('issue_statuses.name', :caption => _('Status')) %> + <%= sort_header_tag('issues.tracker_id', :caption => _('Tracker')) %> + + <%= sort_header_tag('users.lastname', :caption => _('Author')) %> + <%= sort_header_tag('issues.created_on', :caption => _('Created on')) %> + <%= sort_header_tag('issues.updated_on', :caption => _('Last update')) %> + + + <% for issue in @issues %> + + + + + + + + + + <% end %> +
<%=_('Subject')%>
<%= link_to issue.long_id, :controller => 'issues', :action => 'show', :id => issue %><%= issue.status.name %><%= issue.tracker.name %><%= link_to issue.subject, :controller => 'issues', :action => 'show', :id => issue %><%= issue.author.display_name %><%= format_time(issue.created_on) %><%= format_time(issue.updated_on) %>
+

+ <%= pagination_links_full @issue_pages %> + [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ] +

+ + +

+<%= link_to_if_authorized '» ' + _('Report an issue'), :controller => 'projects', :action => 'add_issue', :id => @project %> +

\ No newline at end of file diff --git a/redmine/app/views/projects/list_members.rhtml b/redmine/app/views/projects/list_members.rhtml new file mode 100644 index 000000000..6fca0d2bf --- /dev/null +++ b/redmine/app/views/projects/list_members.rhtml @@ -0,0 +1,11 @@ +

<%=_('Project members')%>

+ +<% members = @members.group_by {|m| m.role } %> +<% members.each do |role, member| %> +

<%= role.name %>

+
    +<% member.each do |m| %> +
  • <%= link_to m.user.display_name, :controller => 'account', :action => 'show', :id => m.user %> (<%= format_date m.created_on %>)
  • +<% end %> +
+<% end %> diff --git a/redmine/app/views/projects/list_news.rhtml b/redmine/app/views/projects/list_news.rhtml new file mode 100644 index 000000000..7db53391e --- /dev/null +++ b/redmine/app/views/projects/list_news.rhtml @@ -0,0 +1,17 @@ +

<%=_('News')%>

+ +<% for news in @news %> +

+ <%= news.title %> (<%= link_to_user news.author %> <%= format_time(news.created_on) %>) + <%= link_to_if_authorized image_tag('edit_small'), :controller => 'news', :action => 'edit', :id => news %> + <%= link_to_if_authorized image_tag('delete'), { :controller => 'news', :action => 'destroy', :id => news }, :confirm => 'Are you sure?' %> +
+ <%= news.shortdescr %> + [<%= link_to _('Read...'), :controller => 'news', :action => 'show', :id => news %>] +

+<% end %> + +<%= pagination_links_full @news_pages %> +

+<%= link_to_if_authorized '» ' + _('Add'), :controller => 'projects', :action => 'add_news', :id => @project %> +

diff --git a/redmine/app/views/projects/settings.rhtml b/redmine/app/views/projects/settings.rhtml new file mode 100644 index 000000000..c130ed036 --- /dev/null +++ b/redmine/app/views/projects/settings.rhtml @@ -0,0 +1,105 @@ +

<%=_('Settings')%>

+ +
+<%= start_form_tag :action => 'edit', :id => @project %> +<%= render :partial => 'form' %> +
<%= submit_tag _('Save') %>
+<%= end_form_tag %> +
+ +
+

<%=_('Members')%>

+<%= error_messages_for 'member' %> + +<% for member in @project.members.find(:all, :include => :user) %> + <% unless member.new_record? %> + + + + + + + <% end %> +<% end %> +
<%= member.user.display_name %> + <%= start_form_tag :controller => 'members', :action => 'edit', :id => member %> + + + <%= submit_tag _('Save'), :class => "button-small" %> + <%= end_form_tag %> + + <%= start_form_tag :controller => 'members', :action => 'destroy', :id => member %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+
+
+ <%= start_form_tag :controller => 'projects', :action => 'add_member', :id => @project %> + + + <%= submit_tag _('Add') %> + <%= end_form_tag %> +
+ +
+

<%=_('Versions')%>

+ + +<% for version in @project.versions %> + + + + + +<% end %> +
<%= link_to version.name, :controller => 'versions', :action => 'edit', :id => version %><%=h version.descr %> + <%= start_form_tag :controller => 'versions', :action => 'destroy', :id => version %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+
+ <%= start_form_tag ({ :controller => 'projects', :action => 'add_version', :id => @project }, :method => 'get' ) %> + <%= submit_tag _('New version...') %> + <%= end_form_tag %> +
+ + +
+

<%=_('Issue categories')%>

+ +<% for @category in @project.issue_categories %> + <% unless @category.new_record? %> + + + + + + <% end %> +<% end %> +
+ <%= start_form_tag :controller => 'issue_categories', :action => 'edit', :id => @category %> + <%= text_field 'category', 'name', :size => 25 %> + + <%= submit_tag _('Save'), :class => "button-small" %> + <%= end_form_tag %> + + <%= start_form_tag :controller => 'issue_categories', :action => 'destroy', :id => @category %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+
+ +<%= start_form_tag :action => 'add_issue_category', :id => @project %> +<%= error_messages_for 'issue_category' %> +
+<%= text_field 'issue_category', 'name', :size => 25 %> +<%= submit_tag _('Create') %> +<%= end_form_tag %> + +
diff --git a/redmine/app/views/projects/show.rhtml b/redmine/app/views/projects/show.rhtml new file mode 100644 index 000000000..3f10654f5 --- /dev/null +++ b/redmine/app/views/projects/show.rhtml @@ -0,0 +1,53 @@ +

<%=_('Overview')%>

+ +
+ <%= @project.descr %> +
    +
  • <%=_('Homepage')%>: <%= link_to @project.homepage, @project.homepage %>
  • +
  • <%=_('Created on')%>: <%= format_date(@project.created_on) %>
  • +
+ +
+

<%= image_tag "tracker" %> <%=_('Trackers')%>

+
    + <% for tracker in Tracker.find_all %> +
  • <%= link_to tracker.name, :controller => 'projects', :action => 'list_issues', :id => @project, + :set_filter => 1, + "issues.tracker_id" => tracker.id %>: + <%= tracker.issues.count(["project_id=?", @project.id]) %> <%=_('open')%> +
  • + <% end %> +
+ <%= link_to_if_authorized '» ' + _('Report an issue'), :controller => 'projects', :action => 'add_issue', :id => @project %> +
+
[ <%= link_to _('View all issues'), :controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1 %> ]
+
+
+ +
+
+

<%= image_tag "users" %> <%=_('Members')%>

+ <% for member in @members %> + <%= link_to_user member.user %> (<%= member.role.name %>)
+ <% end %> +
+ +
+

<%=_('Latest news')%>

+ <% for news in @project.news %> +

+ <%= news.title %> (<%= link_to_user news.author %> <%= format_time(news.created_on) %>)
+ <%= news.shortdescr %> + [<%= link_to _('Read...'), :controller => 'news', :action => 'show', :id => news %>] +

+
+ <% end %> +
[ <%= link_to _('View all news'), :controller => 'projects', :action => 'list_news', :id => @project %> ]
+
+
+ + + + + + diff --git a/redmine/app/views/reports/_simple.rhtml b/redmine/app/views/reports/_simple.rhtml new file mode 100644 index 000000000..5816284ec --- /dev/null +++ b/redmine/app/views/reports/_simple.rhtml @@ -0,0 +1,34 @@ +<% col_width = 70 / (@statuses.length+3) %> + + + + +<% for status in @statuses %> + +<% end %> + + + + + +<% for row in rows %> + + + <% for status in @statuses %> + + <% end %> + + + +<% end %> + +
<%= status.name %><%=_('Open')%><%=_('Closed')%><%=_('Total')%>
<%= link_to row.name, :controller => 'projects', :action => 'list_issues', :id => @project, + :set_filter => 1, + "issues.#{field_name}" => row.id %><%= link_to (aggregate data, { field_name => row.id, "status_id" => status.id }), + :controller => 'projects', :action => 'list_issues', :id => @project, + :set_filter => 1, + "issues.status_id" => status.id, + "issues.#{field_name}" => row.id %><%= aggregate data, { field_name => row.id, "closed" => 0 } %><%= aggregate data, { field_name => row.id, "closed" => 1 } %><%= link_to (aggregate data, { field_name => row.id }), + :controller => 'projects', :action => 'list_issues', :id => @project, + :set_filter => 1, + "issues.#{field_name}" => row.id %>
\ No newline at end of file diff --git a/redmine/app/views/reports/issue_report.rhtml b/redmine/app/views/reports/issue_report.rhtml new file mode 100644 index 000000000..32a1f55f2 --- /dev/null +++ b/redmine/app/views/reports/issue_report.rhtml @@ -0,0 +1,13 @@ +

<%=_('Reports')%>

+ +<%=_('Issues by tracker')%> +<%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %> +
+ +<%=_('Issues by priority')%> +<%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %> +
+ +<%=_('Issues by category')%> +<%= render :partial => 'simple', :locals => { :data => @issues_by_category, :field_name => "category_id", :rows => @categories } %> + diff --git a/redmine/app/views/roles/_form.rhtml b/redmine/app/views/roles/_form.rhtml new file mode 100644 index 000000000..02eef4717 --- /dev/null +++ b/redmine/app/views/roles/_form.rhtml @@ -0,0 +1,22 @@ +<%= error_messages_for 'role' %> + + +


+<%= text_field 'role', 'name' %>

+ +<%=_('Permissions')%> +<% permissions = @permissions.group_by {|p| p.group_id } %> +<% permissions.keys.sort.each do |group_id| %> +
<%= _(Permission::GROUPS[group_id]) %> +<% permissions[group_id].each do |p| %> +
<%= check_box_tag "permission_ids[]", p.id, (@role.permissions.include? p) %> + <%= _(p.descr) %> +
+<% end %> +
+<% end %> +
+<%=_('Check all')%> | +<%=_('Uncheck all')%>
+ + diff --git a/redmine/app/views/roles/edit.rhtml b/redmine/app/views/roles/edit.rhtml new file mode 100644 index 000000000..cffbe0119 --- /dev/null +++ b/redmine/app/views/roles/edit.rhtml @@ -0,0 +1,10 @@ +

<%=_('Role')%>

+ +<%= start_form_tag ({ :action => 'edit', :id => @role }, :id => 'role_form') %> +<%= render :partial => 'form' %> + +
+<%= submit_tag _('Save') %> +<%= end_form_tag %> + + diff --git a/redmine/app/views/roles/list.rhtml b/redmine/app/views/roles/list.rhtml new file mode 100644 index 000000000..146e45886 --- /dev/null +++ b/redmine/app/views/roles/list.rhtml @@ -0,0 +1,23 @@ +

<%=_('Roles')%>

+ + + + + + + +<% for role in @roles %> + + + +<% end %> +
<%=_('Role')%>
<%= link_to role.name, :action => 'edit', :id => role %> + <%= start_form_tag :action => 'destroy', :id => role %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+ +<%= pagination_links_full @role_pages %> +
+ +<%= link_to '» ' + _('New role'), :action => 'new' %> diff --git a/redmine/app/views/roles/new.rhtml b/redmine/app/views/roles/new.rhtml new file mode 100644 index 000000000..c82fb2144 --- /dev/null +++ b/redmine/app/views/roles/new.rhtml @@ -0,0 +1,8 @@ +

<%=_('New role')%>

+ +<%= start_form_tag ({ :action => 'new' }, :id => 'role_form') %> +<%= render :partial => 'form' %> + +
<%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/roles/workflow.rhtml b/redmine/app/views/roles/workflow.rhtml new file mode 100644 index 000000000..652f6a0f1 --- /dev/null +++ b/redmine/app/views/roles/workflow.rhtml @@ -0,0 +1,70 @@ +

<%=_('Workflow setup')%>

+ +

<%=_('Select a workflow to edit')%>:

+ +<%= start_form_tag ({:action => 'workflow'}, :method => 'get') %> +
+


+

+
+ +
+


+ + +<%= submit_tag _('Edit') %> +

+
+<%= end_form_tag %> + + + +<% unless @tracker.nil? or @role.nil? %> +
+ <%= form_tag ({:action => 'workflow', :role_id => @role, :tracker_id => @tracker }, :id => 'workflow_form' ) %> + + + + + + + + <% for new_status in @statuses %> + + <% end %> + + + <% for old_status in @statuses %> + + + + <% for new_status in @statuses %> + + <% end %> + + + <% end %> +
<%=_('Issue status')%><%=_('New statuses allowed')%>
<%= new_status.name %>
<%= old_status.name %> + + checked="checked"<%end%> + <%if old_status==new_status%>disabled<%end%> + > +
+
+

+<%=_('Check all')%> | +<%=_('Uncheck all')%> +

+
+<%= submit_tag _('Save') %> +<%= end_form_tag %> + +<% end %> +
\ No newline at end of file diff --git a/redmine/app/views/trackers/_form.rhtml b/redmine/app/views/trackers/_form.rhtml new file mode 100644 index 000000000..95d06d777 --- /dev/null +++ b/redmine/app/views/trackers/_form.rhtml @@ -0,0 +1,10 @@ +<%= error_messages_for 'tracker' %> + + +


+<%= text_field 'tracker', 'name' %>

+ +

<%= check_box 'tracker', 'is_in_chlog' %> +

+ + diff --git a/redmine/app/views/trackers/edit.rhtml b/redmine/app/views/trackers/edit.rhtml new file mode 100644 index 000000000..a209b3f38 --- /dev/null +++ b/redmine/app/views/trackers/edit.rhtml @@ -0,0 +1,6 @@ +

<%=_('Tracker')%>

+ +<%= start_form_tag :action => 'edit', :id => @tracker %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> diff --git a/redmine/app/views/trackers/list.rhtml b/redmine/app/views/trackers/list.rhtml new file mode 100644 index 000000000..3622a40f9 --- /dev/null +++ b/redmine/app/views/trackers/list.rhtml @@ -0,0 +1,24 @@ +

<%=_('Trackers')%>

+ + + + + + + +<% for tracker in @trackers %> + + + + +<% end %> +
<%=_('Tracker')%>
<%= link_to tracker.name, :action => 'edit', :id => tracker %> + <%= start_form_tag :action => 'destroy', :id => tracker %> + <%= submit_tag _('Delete'), :class => "button-small" %> + <%= end_form_tag %> +
+ +<%= pagination_links_full @tracker_pages %> +
+ +<%= link_to '» ' + _('New tracker'), :action => 'new' %> diff --git a/redmine/app/views/trackers/new.rhtml b/redmine/app/views/trackers/new.rhtml new file mode 100644 index 000000000..a24fca738 --- /dev/null +++ b/redmine/app/views/trackers/new.rhtml @@ -0,0 +1,7 @@ +

<%=_('New tracker')%>

+ +<%= start_form_tag :action => 'new' %> + <%= render :partial => 'form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> + diff --git a/redmine/app/views/users/_form.rhtml b/redmine/app/views/users/_form.rhtml new file mode 100644 index 000000000..92f1e0e5a --- /dev/null +++ b/redmine/app/views/users/_form.rhtml @@ -0,0 +1,28 @@ +<%= error_messages_for 'user' %> + + +


+<%= text_field 'user', 'login' %>

+ +


+<%= password_field 'user', 'password' %>

+ +


+<%= text_field 'user', 'firstname' %>

+ +


+<%= text_field 'user', 'lastname' %>

+ +


+<%= text_field 'user', 'mail' %>

+ +


+<%= select("user", "language", Localization.langs) %>

+ +

<%= check_box 'user', 'admin' %>

+ +

<%= check_box 'user', 'mail_notification' %>

+ +

<%= check_box 'user', 'locked' %>

+ + diff --git a/redmine/app/views/users/add.rhtml b/redmine/app/views/users/add.rhtml new file mode 100644 index 000000000..9d0ba8d3a --- /dev/null +++ b/redmine/app/views/users/add.rhtml @@ -0,0 +1,6 @@ +

<%=_('New user')%>

+ +<%= start_form_tag :action => 'add' %> + <%= render :partial => 'form' %> + <%= submit_tag _('Create') %> +<%= end_form_tag %> diff --git a/redmine/app/views/users/edit.rhtml b/redmine/app/views/users/edit.rhtml new file mode 100644 index 000000000..a033a7fda --- /dev/null +++ b/redmine/app/views/users/edit.rhtml @@ -0,0 +1,7 @@ +

<%=_('User')%>

+ +<%= start_form_tag :action => 'edit', :id => @user %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> + +<%= end_form_tag %> diff --git a/redmine/app/views/users/list.rhtml b/redmine/app/views/users/list.rhtml new file mode 100644 index 000000000..b1bf937aa --- /dev/null +++ b/redmine/app/views/users/list.rhtml @@ -0,0 +1,46 @@ +

<%=_('Users')%>

+ + + + <%= sort_header_tag('users.login', :caption => _('Login')) %> + <%= sort_header_tag('users.firstname', :caption => _('Firstname')) %> + <%= sort_header_tag('users.lastname', :caption => _('Lastname')) %> + + <%= sort_header_tag('users.admin', :caption => _('Admin')) %> + <%= sort_header_tag('users.locked', :caption => _('Locked')) %> + <%= sort_header_tag('users.created_on', :caption => _('Created on')) %> + <%= sort_header_tag('users.last_login_on', :caption => _('Last login')) %> + + +<% for user in @users %> + + + + + + + + + + + +<% end %> +
<%=_('Mail')%>
<%= link_to user.login, :action => 'edit', :id => user %><%= user.firstname %><%= user.lastname %><%= user.mail %><%= image_tag 'true' if user.admin? %><%= image_tag 'locked' if user.locked? %><%= format_time(user.created_on) %><%= format_time(user.last_login_on) unless user.last_login_on.nil? %> + <%= start_form_tag :action => 'edit', :id => user %> + <% if user.locked? %> + <%= hidden_field_tag 'user[locked]', false %> + <%= submit_tag _('Unlock'), :class => "button-small" %> + <% else %> + <%= hidden_field_tag 'user[locked]', true %> + <%= submit_tag _('Lock'), :class => "button-small" %> + <% end %> + <%= end_form_tag %> +
+ +

<%= pagination_links_full @user_pages %> +[ <%= @user_pages.current.first_item %> - <%= @user_pages.current.last_item %> / <%= @user_count %> ] +

+ +

+<%= link_to '» ' + _('New user'), :action => 'add' %> +

\ No newline at end of file diff --git a/redmine/app/views/versions/_form.rhtml b/redmine/app/views/versions/_form.rhtml new file mode 100644 index 000000000..189e106cc --- /dev/null +++ b/redmine/app/views/versions/_form.rhtml @@ -0,0 +1,13 @@ +<%= error_messages_for 'version' %> + + +


+<%= text_field 'version', 'name', :size => 20 %>

+ +


+<%= text_field 'version', 'descr', :size => 60 %>

+ +


+<%= date_select 'version', 'date' %>

+ + diff --git a/redmine/app/views/versions/edit.rhtml b/redmine/app/views/versions/edit.rhtml new file mode 100644 index 000000000..6db4daa00 --- /dev/null +++ b/redmine/app/views/versions/edit.rhtml @@ -0,0 +1,8 @@ +

<%=_('Version')%>

+ +<%= start_form_tag :action => 'edit', :id => @version %> + <%= render :partial => 'form' %> + <%= submit_tag _('Save') %> +<%= end_form_tag %> + + diff --git a/redmine/app/views/welcome/index.rhtml b/redmine/app/views/welcome/index.rhtml new file mode 100644 index 000000000..cbffa82ed --- /dev/null +++ b/redmine/app/views/welcome/index.rhtml @@ -0,0 +1,30 @@ +
+

<%=_('Welcome')%> !

+ +
+

Latest news

+ <% for news in @news %> +

+ <%= news.title %> (<%= link_to_user news.author %> <%= format_time(news.created_on) %> - <%= news.project.name %>)
+ <%= news.shortdescr %>
+ [<%= link_to 'Read...', :controller => 'news', :action => 'show', :id => news %>] +

+
+ <% end %> +
+
+ +
+
+

Latest projects

+
    + <% for project in @projects %> +
  • + <%= link_to project.name, :controller => 'projects', :action => 'show', :id => project %> (added <%= format_time(project.created_on) %>)
    + <%= project.descr %> +
  • + <% end %> +
+
+ +
diff --git a/redmine/config/boot.rb b/redmine/config/boot.rb new file mode 100644 index 000000000..9fcd50fe3 --- /dev/null +++ b/redmine/config/boot.rb @@ -0,0 +1,19 @@ +# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb + +unless defined?(RAILS_ROOT) + root_path = File.join(File.dirname(__FILE__), '..') + unless RUBY_PLATFORM =~ /mswin32/ + require 'pathname' + root_path = Pathname.new(root_path).cleanpath(true).to_s + end + RAILS_ROOT = root_path +end + +if File.directory?("#{RAILS_ROOT}/vendor/rails") + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" +else + require 'rubygems' + require 'initializer' +end + +Rails::Initializer.run(:set_load_path) diff --git a/redmine/config/database.yml b/redmine/config/database.yml new file mode 100644 index 000000000..1702510d0 --- /dev/null +++ b/redmine/config/database.yml @@ -0,0 +1,32 @@ +# MySQL (default setup). Versions 4.1 and 5.0 are recommended. +# +# Get the fast C bindings: +# gem install mysql +# (on OS X: gem install mysql -- --include=/usr/local/lib) +# And be sure to use new-style password hashing: +# http://dev.mysql.com/doc/refman/5.0/en/old-client.html +development: + adapter: mysql + database: redmine_development + host: localhost + username: root + password: + +test: + adapter: mysql + database: redmine_test + host: localhost + username: root + password: + +demo: + adapter: sqlite3 + dbfile: db/redmine_demo.db + +production: + adapter: mysql + database: redmine + host: localhost + username: root + password: + \ No newline at end of file diff --git a/redmine/config/environment.rb b/redmine/config/environment.rb new file mode 100644 index 000000000..de12e7c34 --- /dev/null +++ b/redmine/config/environment.rb @@ -0,0 +1,85 @@ +# Be sure to restart your web server when you modify this file. + +# Uncomment below to force Rails into production mode when +# you don't control web/app server and can't set it the proper way +# ENV['RAILS_ENV'] ||= 'production' + +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence those specified here + + # Skip frameworks you're not going to use + # config.frameworks -= [ :action_web_service, :action_mailer ] + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + + # Use the database for sessions instead of the file system + # (create the session table with 'rake create_sessions_table') + # config.action_controller.session_store = :active_record_store + + # Enable page/fragment caching by setting a file-based store + # (remember to create the caching directory and make it readable to the application) + # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache" + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc + + # Use Active Record's schema dumper instead of SQL when creating the test database + # (enables use of different database adapters for development and test environments) + # config.active_record.schema_format = :ruby + + # See Rails::Configuration for more options + + # SMTP server configuration + config.action_mailer.server_settings = { + :address => "127.0.0.1", + :port => 25, + :domain => "somenet.foo", + :authentication => :login, + :user_name => "redmine", + :password => "redmine", + } + + config.action_mailer.perform_deliveries = true + + # Tell ActionMailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + #config.action_mailer.delivery_method = :test + config.action_mailer.delivery_method = :smtp +end + +# Add new inflection rules using the following format +# (all these examples are active by default): +# Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# Include your application configuration below + +# application name +RDM_APP_NAME = "redMine" +# application version +RDM_APP_VERSION = "0.1.0" +# application host name +RDM_HOST_NAME = "somenet.foo" +# file storage path +RDM_STORAGE_PATH = "#{RAILS_ROOT}/files" +# if RDM_LOGIN_REQUIRED is set to true, login is required to access the application +RDM_LOGIN_REQUIRED = false +# default langage +RDM_DEFAULT_LANG = 'en' + diff --git a/redmine/config/environments/demo.rb b/redmine/config/environments/demo.rb new file mode 100644 index 000000000..52aef32f1 --- /dev/null +++ b/redmine/config/environments/demo.rb @@ -0,0 +1,21 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new +config.log_level = :info + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable mail delivery +config.action_mailer.perform_deliveries = false +config.action_mailer.raise_delivery_errors = false + diff --git a/redmine/config/environments/development.rb b/redmine/config/environments/development.rb new file mode 100644 index 000000000..04b779200 --- /dev/null +++ b/redmine/config/environments/development.rb @@ -0,0 +1,19 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Enable the breakpoint server that script/breakpointer connects to +config.breakpoint_server = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false diff --git a/redmine/config/environments/production.rb b/redmine/config/environments/production.rb new file mode 100644 index 000000000..4cd4e086b --- /dev/null +++ b/redmine/config/environments/production.rb @@ -0,0 +1,20 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors if you bad email addresses should just be ignored +config.action_mailer.raise_delivery_errors = false + diff --git a/redmine/config/environments/test.rb b/redmine/config/environments/test.rb new file mode 100644 index 000000000..0b34ef19a --- /dev/null +++ b/redmine/config/environments/test.rb @@ -0,0 +1,15 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + diff --git a/redmine/config/help.yml b/redmine/config/help.yml new file mode 100644 index 000000000..a48798e03 --- /dev/null +++ b/redmine/config/help.yml @@ -0,0 +1,21 @@ +# administration +admin: + index: administration.html + mail_options: administration.html#mail_notifications + info: administration.html#app_info +users: + index: administration.html#users +roles: + index: administration.html#roles + workflow: administration.html#workflow +trackers: + index: administration.html#trackers +issue_statuses: + index: administration.html#issue_statuses + +# projects +projects: + add: projects.html#settings + + +# issues \ No newline at end of file diff --git a/redmine/config/routes.rb b/redmine/config/routes.rb new file mode 100644 index 000000000..2559159f1 --- /dev/null +++ b/redmine/config/routes.rb @@ -0,0 +1,24 @@ +ActionController::Routing::Routes.draw do |map| + # Add your own custom routes here. + # The priority is based upon order of creation: first created -> highest priority. + + # Here's a sample route: + # map.connect 'products/:id', :controller => 'catalog', :action => 'view' + # Keep in mind you can assign values other than :controller and :action + + # You can have the root of your site routed by hooking up '' + # -- just remember to delete public/index.html. + map.connect '', :controller => "welcome" + + map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow' + map.connect 'help/:ctrl/:page', :controller => 'help' + map.connect ':controller/:action/:id/:sort_key/:sort_order' + + # Allow downloading Web Service WSDL as a file with an extension + # instead of a file named 'wsdl' + map.connect ':controller/service.wsdl', :action => 'wsdl' + + + # Install the default route as the lowest priority. + map.connect ':controller/:action/:id' +end diff --git a/redmine/db/migrate/001_setup.rb b/redmine/db/migrate/001_setup.rb new file mode 100644 index 000000000..e6288f61d --- /dev/null +++ b/redmine/db/migrate/001_setup.rb @@ -0,0 +1,254 @@ +class Setup < ActiveRecord::Migration + def self.up + create_table "attachments", :force => true do |t| + t.column "container_id", :integer, :default => 0, :null => false + t.column "container_type", :string, :limit => 30, :default => "", :null => false + t.column "filename", :string, :default => "", :null => false + t.column "disk_filename", :string, :default => "", :null => false + t.column "size", :integer, :default => 0, :null => false + t.column "content_type", :string, :limit => 60, :default => "", :null => false + t.column "digest", :string, :limit => 40, :default => "", :null => false + t.column "downloads", :integer, :default => 0, :null => false + t.column "author_id", :integer, :default => 0, :null => false + t.column "created_on", :timestamp + end + + create_table "custom_fields", :force => true do |t| + t.column "name", :string, :limit => 30, :default => "", :null => false + t.column "typ", :integer, :limit => 6, :default => 0, :null => false + t.column "is_required", :boolean, :default => false, :null => false + t.column "is_for_all", :boolean, :default => false, :null => false + t.column "possible_values", :text, :default => "", :null => false + t.column "regexp", :string, :default => "", :null => false + t.column "min_length", :integer, :limit => 4, :default => 0, :null => false + t.column "max_length", :integer, :limit => 4, :default => 0, :null => false + end + + create_table "custom_fields_projects", :id => false, :force => true do |t| + t.column "custom_field_id", :integer, :default => 0, :null => false + t.column "project_id", :integer, :default => 0, :null => false + end + + create_table "custom_values", :force => true do |t| + t.column "issue_id", :integer, :default => 0, :null => false + t.column "custom_field_id", :integer, :default => 0, :null => false + t.column "value", :text, :default => "", :null => false + end + + add_index "custom_values", ["issue_id"], :name => "custom_values_issue_id" + + create_table "documents", :force => true do |t| + t.column "project_id", :integer, :default => 0, :null => false + t.column "category_id", :integer, :default => 0, :null => false + t.column "title", :string, :limit => 60, :default => "", :null => false + t.column "descr", :text, :default => "", :null => false + t.column "created_on", :timestamp + end + + create_table "enumerations", :force => true do |t| + t.column "opt", :string, :limit => 4, :default => "", :null => false + t.column "name", :string, :limit => 30, :default => "", :null => false + end + + create_table "issue_categories", :force => true do |t| + t.column "project_id", :integer, :default => 0, :null => false + t.column "name", :string, :limit => 30, :default => "", :null => false + end + + create_table "issue_histories", :force => true do |t| + t.column "issue_id", :integer, :default => 0, :null => false + t.column "status_id", :integer, :default => 0, :null => false + t.column "author_id", :integer, :default => 0, :null => false + t.column "notes", :text, :default => "", :null => false + t.column "created_on", :timestamp + end + + add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id" + + create_table "issue_statuses", :force => true do |t| + t.column "name", :string, :limit => 30, :default => "", :null => false + t.column "is_closed", :boolean, :default => false, :null => false + t.column "is_default", :boolean, :default => false, :null => false + t.column "html_color", :string, :limit => 6, :default => "FFFFFF", :null => false + end + + create_table "issues", :force => true do |t| + t.column "tracker_id", :integer, :default => 0, :null => false + t.column "project_id", :integer, :default => 0, :null => false + t.column "subject", :string, :default => "", :null => false + t.column "descr", :text, :default => "", :null => false + t.column "category_id", :integer + t.column "status_id", :integer, :default => 0, :null => false + t.column "assigned_to_id", :integer + t.column "priority_id", :integer, :default => 0, :null => false + t.column "fixed_version_id", :integer + t.column "author_id", :integer, :default => 0, :null => false + t.column "created_on", :timestamp + t.column "updated_on", :timestamp + end + + add_index "issues", ["project_id"], :name => "issues_project_id" + + create_table "members", :force => true do |t| + t.column "user_id", :integer, :default => 0, :null => false + t.column "project_id", :integer, :default => 0, :null => false + t.column "role_id", :integer, :default => 0, :null => false + t.column "created_on", :timestamp + end + + create_table "news", :force => true do |t| + t.column "project_id", :integer + t.column "title", :string, :limit => 60, :default => "", :null => false + t.column "shortdescr", :string, :default => "", :null => false + t.column "descr", :text, :default => "", :null => false + t.column "author_id", :integer, :default => 0, :null => false + t.column "created_on", :timestamp + end + + create_table "permissions", :force => true do |t| + t.column "controller", :string, :limit => 30, :default => "", :null => false + t.column "action", :string, :limit => 30, :default => "", :null => false + t.column "descr", :string, :limit => 60, :default => "", :null => false + t.column "public", :boolean, :default => false, :null => false + t.column "sort", :integer, :default => 0, :null => false + t.column "mail_option", :boolean, :default => false, :null => false + t.column "mail_enabled", :boolean, :default => false, :null => false + end + + create_table "permissions_roles", :id => false, :force => true do |t| + t.column "permission_id", :integer, :default => 0, :null => false + t.column "role_id", :integer, :default => 0, :null => false + end + + add_index "permissions_roles", ["role_id"], :name => "permissions_roles_role_id" + + create_table "projects", :force => true do |t| + t.column "name", :string, :limit => 30, :default => "", :null => false + t.column "descr", :string, :default => "", :null => false + t.column "homepage", :string, :limit => 60, :default => "", :null => false + t.column "public", :boolean, :default => true, :null => false + t.column "created_on", :timestamp + t.column "updated_on", :timestamp + end + + create_table "roles", :force => true do |t| + t.column "name", :string, :limit => 30, :default => "", :null => false + end + + create_table "trackers", :force => true do |t| + t.column "name", :string, :limit => 30, :default => "", :null => false + t.column "is_in_chlog", :boolean, :default => false, :null => false + end + + create_table "users", :force => true do |t| + t.column "login", :string, :limit => 30, :default => "", :null => false + t.column "hashed_password", :string, :limit => 40, :default => "", :null => false + t.column "firstname", :string, :limit => 30, :default => "", :null => false + t.column "lastname", :string, :limit => 30, :default => "", :null => false + t.column "mail", :string, :limit => 60, :default => "", :null => false + t.column "mail_notification", :boolean, :default => true, :null => false + t.column "admin", :boolean, :default => false, :null => false + t.column "locked", :boolean, :default => false, :null => false + t.column "last_login_on", :datetime + t.column "language", :string, :limit => 2, :default => "", :null => false + t.column "created_on", :timestamp + t.column "updated_on", :timestamp + end + + create_table "versions", :force => true do |t| + t.column "project_id", :integer, :default => 0, :null => false + t.column "name", :string, :limit => 30, :default => "", :null => false + t.column "descr", :string, :default => "", :null => false + t.column "date", :date, :null => false + t.column "created_on", :timestamp + t.column "updated_on", :timestamp + end + + create_table "workflows", :force => true do |t| + t.column "tracker_id", :integer, :default => 0, :null => false + t.column "old_status_id", :integer, :default => 0, :null => false + t.column "new_status_id", :integer, :default => 0, :null => false + t.column "role_id", :integer, :default => 0, :null => false + end + + # project + Permission.create :controller => "projects", :action => "show", :descr => "Overview", :sort => 100, :public => true + Permission.create :controller => "projects", :action => "changelog", :descr => "View change log", :sort => 105, :public => true + Permission.create :controller => "reports", :action => "issue_report", :descr => "View reports", :sort => 110, :public => true + Permission.create :controller => "projects", :action => "settings", :descr => "Settings", :sort => 150 + Permission.create :controller => "projects", :action => "edit", :descr => "Edit", :sort => 151 + # members + Permission.create :controller => "projects", :action => "list_members", :descr => "View list", :sort => 200, :public => true + Permission.create :controller => "projects", :action => "add_member", :descr => "New member", :sort => 220 + Permission.create :controller => "members", :action => "edit", :descr => "Edit", :sort => 221 + Permission.create :controller => "members", :action => "destroy", :descr => "Delete", :sort => 222 + # versions + Permission.create :controller => "projects", :action => "add_version", :descr => "New version", :sort => 320 + Permission.create :controller => "versions", :action => "edit", :descr => "Edit", :sort => 321 + Permission.create :controller => "versions", :action => "destroy", :descr => "Delete", :sort => 322 + # issue categories + Permission.create :controller => "projects", :action => "add_issue_category", :descr => "New issue category", :sort => 420 + Permission.create :controller => "issue_categories", :action => "edit", :descr => "Edit", :sort => 421 + Permission.create :controller => "issue_categories", :action => "destroy", :descr => "Delete", :sort => 422 + # issues + Permission.create :controller => "projects", :action => "list_issues", :descr => "View list", :sort => 1000, :public => true + Permission.create :controller => "issues", :action => "show", :descr => "View", :sort => 1005, :public => true + Permission.create :controller => "issues", :action => "download", :descr => "Download file", :sort => 1010, :public => true + Permission.create :controller => "projects", :action => "add_issue", :descr => "Report an issue", :sort => 1050, :mail_option => 1, :mail_enabled => 1 + Permission.create :controller => "issues", :action => "edit", :descr => "Edit", :sort => 1055 + Permission.create :controller => "issues", :action => "change_status", :descr => "Change status", :sort => 1060, :mail_option => 1, :mail_enabled => 1 + Permission.create :controller => "issues", :action => "destroy", :descr => "Delete", :sort => 1065 + Permission.create :controller => "issues", :action => "add_attachment", :descr => "Add file", :sort => 1070 + Permission.create :controller => "issues", :action => "destroy_attachment", :descr => "Delete file", :sort => 1075 + # news + Permission.create :controller => "projects", :action => "list_news", :descr => "View list", :sort => 1100, :public => true + Permission.create :controller => "news", :action => "show", :descr => "View", :sort => 1101, :public => true + Permission.create :controller => "projects", :action => "add_news", :descr => "Add", :sort => 1120 + Permission.create :controller => "news", :action => "edit", :descr => "Edit", :sort => 1121 + Permission.create :controller => "news", :action => "destroy", :descr => "Delete", :sort => 1122 + # documents + Permission.create :controller => "projects", :action => "list_documents", :descr => "View list", :sort => 1200, :public => true + Permission.create :controller => "documents", :action => "show", :descr => "View", :sort => 1201, :public => true + Permission.create :controller => "documents", :action => "download", :descr => "Download", :sort => 1202, :public => true + Permission.create :controller => "projects", :action => "add_document", :descr => "Add", :sort => 1220 + Permission.create :controller => "documents", :action => "edit", :descr => "Edit", :sort => 1221 + Permission.create :controller => "documents", :action => "destroy", :descr => "Delete", :sort => 1222 + Permission.create :controller => "documents", :action => "add_attachment", :descr => "Add file", :sort => 1223 + Permission.create :controller => "documents", :action => "destroy_attachment", :descr => "Delete file", :sort => 1224 + # files + Permission.create :controller => "projects", :action => "list_files", :descr => "View list", :sort => 1300, :public => true + Permission.create :controller => "versions", :action => "download", :descr => "Download", :sort => 1301, :public => true + Permission.create :controller => "projects", :action => "add_file", :descr => "Add", :sort => 1320 + Permission.create :controller => "versions", :action => "destroy_file", :descr => "Delete", :sort => 1322 + + # create default administrator account + user = User.create :login => "admin", :password => "admin", :firstname => "redMine", :lastname => "Admin", :mail => "admin@somenet.foo", :mail_notification => true, :language => "en" + user.admin = true + user.save + + + end + + def self.down + drop_table :attachments + drop_table :custom_fields + drop_table :custom_fields_projects + drop_table :custom_values + drop_table :documents + drop_table :enumerations + drop_table :issue_categories + drop_table :issue_histories + drop_table :issue_statuses + drop_table :issues + drop_table :members + drop_table :news + drop_table :permissions + drop_table :permissions_roles + drop_table :projects + drop_table :roles + drop_table :trackers + drop_table :users + drop_table :versions + drop_table :workflows + end +end diff --git a/redmine/db/migrate/002_default_configuration.rb b/redmine/db/migrate/002_default_configuration.rb new file mode 100644 index 000000000..8f851dfbc --- /dev/null +++ b/redmine/db/migrate/002_default_configuration.rb @@ -0,0 +1,44 @@ +class DefaultConfiguration < ActiveRecord::Migration + def self.up + # roles + r = Role.create(:name => "Manager") + r.permissions = Permission.find(:all) + r = Role.create :name => "Developer" + r.permissions = Permission.find([1, 2, 3, 6, 10, 11, 12, 16, 17, 18, 19, 20, 21, 23, 25, 26, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41]) + r = Role.create :name => "Reporter" + r.permissions = Permission.find([1, 2, 3, 6, 16, 17, 18, 19, 20, 21, 23, 25, 26, 30, 31, 32, 38, 39]) + # trackers + Tracker.create(:name => "Bug", :is_in_chlog => true) + Tracker.create(:name => "Feature request", :is_in_chlog => true) + Tracker.create(:name => "Support request", :is_in_chlog => false) + # issue statuses + IssueStatus.create(:name => "New", :is_closed => false, :is_default => true, :html_color => 'F98787') + IssueStatus.create(:name => "Assigned", :is_closed => false, :is_default => false, :html_color => 'C0C0FF') + IssueStatus.create(:name => "Resolved", :is_closed => false, :is_default => false, :html_color => '88E0B3') + IssueStatus.create(:name => "Feedback", :is_closed => false, :is_default => false, :html_color => 'F3A4F4') + IssueStatus.create(:name => "Closed", :is_closed => true, :is_default => false, :html_color => 'DBDBDB') + IssueStatus.create(:name => "Rejected", :is_closed => true, :is_default => false, :html_color => 'F5C28B') + # workflow + Tracker.find(:all).each { |t| + Role.find(:all).each { |r| + IssueStatus.find(:all).each { |os| + IssueStatus.find(:all).each { |ns| + Workflow.create(:tracker_id => t.id, :role_id => r.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns + } + } + } + } + # enumeartions + Enumeration.create(:opt => "DCAT", :name => 'Uncategorized') + Enumeration.create(:opt => "DCAT", :name => 'User documentation') + Enumeration.create(:opt => "DCAT", :name => 'Technical documentation') + Enumeration.create(:opt => "IPRI", :name => 'Low') + Enumeration.create(:opt => "IPRI", :name => 'Normal') + Enumeration.create(:opt => "IPRI", :name => 'High') + Enumeration.create(:opt => "IPRI", :name => 'Urgent') + Enumeration.create(:opt => "IPRI", :name => 'Immediate') + end + + def self.down + end +end diff --git a/redmine/db/redmine_demo.db b/redmine/db/redmine_demo.db new file mode 100644 index 0000000000000000000000000000000000000000..6a19ec527623d26c8b1c9218c7d89b3067f2ad41 GIT binary patch literal 40960 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 T9C^S`MVfYmoN*eCN8|q=Dc8m4 literal 0 HcmV?d00001 diff --git a/redmine/doc/CHANGELOG b/redmine/doc/CHANGELOG new file mode 100644 index 000000000..8db6fc29b --- /dev/null +++ b/redmine/doc/CHANGELOG @@ -0,0 +1,17 @@ +== redMine changelog + +redMine - project management software +Copyright (C) 2006 Jean-Philippe Lang +http://redmine.sourceforge.net/ + + +== 06/25/2006 - v0.1.0 + +* multiple users/multiple projects +* role based access control +* issue tracking system +* fully customizable workflow +* documents/files repository +* email notifications on issue creation and update +* multilanguage support (except for error messages):english, french, spanish +* online manual in french (unfinished) \ No newline at end of file diff --git a/redmine/doc/COPYING b/redmine/doc/COPYING new file mode 100644 index 000000000..82fa1daad --- /dev/null +++ b/redmine/doc/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/redmine/doc/INSTALL b/redmine/doc/INSTALL new file mode 100644 index 000000000..72a993687 --- /dev/null +++ b/redmine/doc/INSTALL @@ -0,0 +1,61 @@ +== redMine installation + +redMine - project management software +Copyright (C) 2006 Jean-Philippe Lang +http://redmine.sourceforge.net/ + + +== Requirements + +* Ruby on Rails 1.1 +* Any database supported by Rails (developped using MySQL 5) +* (recommended) Apache/Lighttpd with FCGI support + + +== Installation + +1. Uncompress program archive: + tar zxvf + +2. Create an empty database: "redmine" for example + +3. Configure database parameters in config/database.yml + for "production" environment + +4. Create database structure. Under application main directory: + rake migrate RAILS_ENV="production" + It will create tables and default configuration data + +5. Test the installation by running WEBrick web server: + ruby script/server -e production + + Once WEBrick has started, point your browser to http://localhost:3000/ + You should now see the application welcome page + +6. Use default administrator account to log in: + login: admin + password: admin + +7. Setup Apache or Lighttpd with fastcgi for best performance. + + +== Configuration + +You can setup a few things in config/environment.rb: +Don't forget to restart the application after any change. + + +config.action_mailer.server_settings: SMTP server configuration +config.action_mailer.perform_deliveries: set to false to disable mail delivering + +RDM_HOST_NAME: hostname used to provide urls in notification mails + +RDM_STORAGE_PATH: path for all attachments storage (default: "#{RAILS_ROOT}/files") + "#{RAILS_ROOT}/" represents application main directory + +RDM_LOGIN_REQUIRED: set to true if you want to force users to login to access + any part of the application (default: false) + +RDM_DEFAULT_LANG: default language for anonymous users: 'en' (default), 'es', 'fr' available + + diff --git a/redmine/doc/README b/redmine/doc/README new file mode 100644 index 000000000..1c794e45b --- /dev/null +++ b/redmine/doc/README @@ -0,0 +1,49 @@ +== redMine + +redMine - project management software +Copyright (C) 2006 Jean-Philippe Lang +http://redmine.sourceforge.net/ + +== License + +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. + + +== Main features + +redMine is a project management software written using Ruby on Rails. + +* multiple users/projects +* fully customizable role based access control +* issue tracking system +* fully customizable workflow +* documents/files repository +* email notifications +* multilanguage support + + +== Versioning + +redMine versioning scheme is major.minor.revision +Versions before 1.0.0 must be considered as beta versions and upgrading support +may not be provided for these versions. + + +== Credits + +* Jean-Francois Boutier (spanish translation) +* Andreas Viklund (open source XHTML layout, http://andreasviklund.com/) + + diff --git a/redmine/files/delete.me b/redmine/files/delete.me new file mode 100644 index 000000000..18beddaa8 --- /dev/null +++ b/redmine/files/delete.me @@ -0,0 +1 @@ +default directory for uploaded files \ No newline at end of file diff --git a/redmine/lang/en_US.rb b/redmine/lang/en_US.rb new file mode 100644 index 000000000..aa402421e --- /dev/null +++ b/redmine/lang/en_US.rb @@ -0,0 +1,4 @@ +Localization.define('en', 'English') do |l| + l.store '(date)', lambda { |t| t.strftime('%m/%d/%Y') } + l.store '(time)', lambda { |t| t.strftime('%m/%d/%Y %I:%M%p') } +end \ No newline at end of file diff --git a/redmine/lang/es_ES.rb b/redmine/lang/es_ES.rb new file mode 100644 index 000000000..ef15f9c53 --- /dev/null +++ b/redmine/lang/es_ES.rb @@ -0,0 +1,315 @@ +Localization.define('es', 'Español') do |l| + + # trackers + l.store 'Bug', 'Anomalía' + l.store 'Feature request', 'Evolución' + l.store 'Support request', 'Asistencia' + # issue statuses + l.store 'New', 'Nuevo' + l.store 'Assigned', 'Asignada' + l.store 'Resolved', 'Resuelta' + l.store 'Closed', 'Cerrada' + l.store 'Rejected', 'Rechazada' + l.store 'Feedback', 'Comentario' + + # issue priorities + l.store 'Issue priorities', 'Prioridad de las peticiones' + l.store 'Low', 'Bajo' + l.store 'Normal', 'Normal' + l.store 'High', 'Alto' + l.store 'Urgent', 'Urgente' + l.store 'Immediate', 'Ahora' + # document categories + l.store 'Document categories', 'Categorías del documento' + l.store 'Uncategorized', 'Sin categorías ' + l.store 'User documentation', 'Documentación del usuario' + l.store 'Technical documentation', 'Documentación tecnica' + # dates + l.store '(date)', lambda { |t| t.strftime('%d/%m/%Y') } + l.store '(time)', lambda { |t| t.strftime('%d/%m/%Y %H:%M') } + + # ./script/../config/../app/views/account/login.rhtml + + # ./script/../config/../app/views/account/my_account.rhtml + l.store 'My account', 'Mi cuenta' + l.store 'Login', 'Identificador' + l.store 'Created on', 'Creado el' + l.store 'Last update', 'Actualizado' + l.store 'Information', 'Informaciones' + l.store 'Firstname', 'Nombre' + l.store 'Lastname', 'Apellido' + l.store 'Mail', 'Mail' + l.store 'Language', 'Lengua' + l.store 'Mail notifications', 'Notificación por mail' + l.store 'Save', 'Validar' + l.store 'Password', 'Contraseña' + l.store 'New password', 'Nueva contraseña' + l.store 'Confirmation', 'Confirmación' + + # ./script/../config/../app/views/account/my_page.rhtml + l.store 'My page', 'Mi página' + l.store 'Welcome', 'Bienvenido' + l.store 'Last login', 'Última conexión' + l.store 'Reported issues', 'Peticiones registradas' + l.store 'Assigned to me', 'Peticiones que me están asignadas' + + # ./script/../config/../app/views/account/show.rhtml + l.store 'Registered on', 'Inscrito el' + l.store 'Projects', 'Proyectos' + l.store 'Activity', 'Actividad' + + # ./script/../config/../app/views/admin/index.rhtml + l.store 'Administration', 'Administración' + l.store 'Users', 'Usuarios' + l.store 'Roles and permissions', 'Papeles y permisos' + l.store 'Trackers', 'Trackers' + l.store 'Custom fields', 'Campos personalizados' + l.store 'Issue Statuses', 'Estatutos de las peticiones' + l.store 'Workflow', 'Workflow' + l.store 'Enumerations', 'Listas de valores' + + # ./script/../config/../app/views/admin/info.rhtml + l.store 'Version', 'Versión' + l.store 'Database', 'Base de datos' + + # ./script/../config/../app/views/admin/mail_options.rhtml + l.store 'Select actions for which mail notification should be enabled.', 'Seleccionar las actividades que necesitan la activación de la notificación por mail.' + l.store 'Check all', 'Seleccionar todo' + l.store 'Uncheck all', 'No seleccionar nada' + + # ./script/../config/../app/views/admin/projects.rhtml + l.store 'Project', 'Proyecto' + l.store 'Description', 'Descripción' + l.store 'Public', 'Público' + l.store 'Delete', 'Suprimir' + l.store 'Previous', 'Precedente' + l.store 'Next', 'Próximo' + + # ./script/../config/../app/views/custom_fields/edit.rhtml + l.store 'Custom field', 'Campo personalizado' + + # ./script/../config/../app/views/custom_fields/list.rhtml + l.store 'Name', 'Nombre' + l.store 'Type', 'Tipo' + l.store 'Required', 'Obligatorio' + l.store 'For all projects', 'Para todos los proyectos' + l.store 'Used by', 'Utilizado por' + + # ./script/../config/../app/views/custom_fields/new.rhtml + l.store 'New custom field', 'Nuevo campo personalizado' + l.store 'Create', 'Crear' + + # ./script/../config/../app/views/custom_fields/_form.rhtml + l.store '0 means no restriction', '0 para ninguna restricción' + l.store 'Regular expression pattern', 'Expresión regular' + l.store 'Possible values', 'Valores posibles' + + # ./script/../config/../app/views/documents/edit.rhtml + l.store 'Document', 'Documento' + + # ./script/../config/../app/views/documents/show.rhtml + l.store 'Category', 'Categoría' + l.store 'Edit', 'Modificar' + l.store 'download', 'Telecarga' + l.store 'Add file', 'Añadir el fichero' + l.store 'Add', 'Añadir' + + # ./script/../config/../app/views/documents/_form.rhtml + l.store 'Title', 'Título' + + # ./script/../config/../app/views/enumerations/edit.rhtml + + # ./script/../config/../app/views/enumerations/list.rhtml + + # ./script/../config/../app/views/enumerations/new.rhtml + l.store 'New enumeration', 'Nuevo valor' + + # ./script/../config/../app/views/enumerations/_form.rhtml + + # ./script/../config/../app/views/issues/change_status.rhtml + l.store 'Issue', 'Petición' + l.store 'New status', 'Nuevo estatuto' + l.store 'Assigned to', 'Asignado a' + l.store 'Fixed in version', 'Versión corregida' + l.store 'Notes', 'Anotación' + + # ./script/../config/../app/views/issues/edit.rhtml + l.store 'Status', 'Estatuto' + l.store 'Tracker', 'Tracker' + l.store 'Priority', 'Prioridad' + l.store 'Subject', 'Tema' + + # ./script/../config/../app/views/issues/show.rhtml + l.store 'Author', 'Autor' + l.store 'Change status', 'Cambiar el estatuto' + l.store 'History', 'Histórico' + l.store 'Attachments', 'Ficheros' + l.store 'Update...', 'Actualizar...' + + # ./script/../config/../app/views/issues/_list_simple.rhtml + l.store 'No issue', 'Ninguna petición' + + # ./script/../config/../app/views/issue_categories/edit.rhtml + + # ./script/../config/../app/views/issue_categories/_form.rhtml + + # ./script/../config/../app/views/issue_statuses/edit.rhtml + l.store 'Issue status', 'Estatuto de petición' + + # ./script/../config/../app/views/issue_statuses/list.rhtml + l.store 'Issue statuses', 'Estatutos de la petición' + l.store 'Default status', 'Estatuto por defecto' + l.store 'Issue closed', 'Petición resuelta' + l.store 'Color', 'Color' + + # ./script/../config/../app/views/issue_statuses/new.rhtml + l.store 'New issue status', 'Nuevo estatuto' + + # ./script/../config/../app/views/issue_statuses/_form.rhtml + + # ./script/../config/../app/views/layouts/base.rhtml + l.store 'Home', 'Acogida' + l.store 'Help', 'Ayuda' + l.store 'Log in', 'Conexión' + l.store 'Logout', 'Desconexión' + l.store 'Overview', 'Vistazo' + l.store 'Issues', 'Peticiones' + l.store 'Reports', 'Rapports' + l.store 'News', 'Noticias' + l.store 'Change log', 'Cambios' + l.store 'Documents', 'Documentos' + l.store 'Members', 'Miembros' + l.store 'Files', 'Ficheros' + l.store 'Settings', 'Configuración' + l.store 'My projects', 'Mis proyectos' + l.store 'Logged as', 'Conectado como' + + # ./script/../config/../app/views/mailer/issue_add.rhtml + + # ./script/../config/../app/views/mailer/issue_change_status.rhtml + + # ./script/../config/../app/views/mailer/_issue.rhtml + + # ./script/../config/../app/views/news/edit.rhtml + + # ./script/../config/../app/views/news/show.rhtml + l.store 'Summary', 'Resumen' + l.store 'By', 'Por' + l.store 'Date', 'Fecha' + + # ./script/../config/../app/views/news/_form.rhtml + + # ./script/../config/../app/views/projects/add.rhtml + l.store 'New project', 'Nuevo proyecto' + + # ./script/../config/../app/views/projects/add_document.rhtml + l.store 'New document', 'Nuevo documento' + l.store 'File', 'Fichero' + + # ./script/../config/../app/views/projects/add_issue.rhtml + l.store 'New issue', 'Nueva petición' + l.store 'Attachment', 'Fichero' + + # ./script/../config/../app/views/projects/add_news.rhtml + + # ./script/../config/../app/views/projects/add_version.rhtml + l.store 'New version', 'Nueva versión' + + # ./script/../config/../app/views/projects/changelog.rhtml + + # ./script/../config/../app/views/projects/destroy.rhtml + l.store 'Are you sure you want to delete project', '¿ Estás seguro de querer eliminar el proyecto ?' + + # ./script/../config/../app/views/projects/list.rhtml + l.store 'Public projects', 'Proyectos publicos' + + # ./script/../config/../app/views/projects/list_documents.rhtml + l.store 'Desciption', 'Descripción' + + # ./script/../config/../app/views/projects/list_files.rhtml + l.store 'New file', 'Nuevo fichero' + + # ./script/../config/../app/views/projects/list_issues.rhtml + l.store 'Apply filter', 'Aplicar' + l.store 'Reset', 'Anular' + l.store 'Report an issue', 'Nueva petición' + + # ./script/../config/../app/views/projects/list_members.rhtml + l.store 'Project members', 'Miembros del proyecto' + + # ./script/../config/../app/views/projects/list_news.rhtml + l.store 'Read...', 'Leer...' + + # ./script/../config/../app/views/projects/settings.rhtml + l.store 'New member', 'Nuevo miembro' + l.store 'Versions', 'Versiónes' + l.store 'New version...', 'Nueva versión...' + l.store 'Issue categories', 'Categorías de las peticiones' + l.store 'New category', 'Nueva categoría' + + # ./script/../config/../app/views/projects/show.rhtml + l.store 'Homepage', 'Sitio web' + l.store 'open', 'abierta(s)' + l.store 'View all issues', 'Ver todas las peticiones' + l.store 'View all news', 'Ver todas las noticias' + l.store 'Latest news', 'Últimas noticias' + + # ./script/../config/../app/views/projects/_form.rhtml + + # ./script/../config/../app/views/reports/issue_report.rhtml + l.store 'Issues by tracker', 'Peticiones por tracker' + l.store 'Issues by priority', 'Peticiones por prioridad' + l.store 'Issues by category', 'Peticiones por categoría' + + # ./script/../config/../app/views/reports/_simple.rhtml + l.store 'Open', 'Abierta' + l.store 'Total', 'Total' + + # ./script/../config/../app/views/roles/edit.rhtml + l.store 'Role', 'Papel' + + # ./script/../config/../app/views/roles/list.rhtml + l.store 'Roles', 'Papeles' + + # ./script/../config/../app/views/roles/new.rhtml + l.store 'New role', 'Nuevo papel' + + # ./script/../config/../app/views/roles/workflow.rhtml + l.store 'Workflow setup', 'Configuración del workflow' + l.store 'Select a workflow to edit', 'Seleccionar un workflow para actualizar' + l.store 'New statuses allowed', 'Nuevos estatutos autorizados' + + # ./script/../config/../app/views/roles/_form.rhtml + l.store 'Permissions', 'Permisos' + + # ./script/../config/../app/views/trackers/edit.rhtml + + # ./script/../config/../app/views/trackers/list.rhtml + l.store 'View issues in change log', 'Consultar las peticiones en el histórico' + l.store 'New tracker', 'Nuevo tracker' + + # ./script/../config/../app/views/trackers/new.rhtml + + # ./script/../config/../app/views/trackers/_form.rhtml + + # ./script/../config/../app/views/users/add.rhtml + l.store 'New user', 'Nuevo usuario' + + # ./script/../config/../app/views/users/edit.rhtml + l.store 'User', 'Usuario' + + # ./script/../config/../app/views/users/list.rhtml + l.store 'Admin', 'Admin' + l.store 'Locked', 'Cerrado' + + # ./script/../config/../app/views/users/_form.rhtml + l.store 'Administrator', 'Administrador' + + # ./script/../config/../app/views/versions/edit.rhtml + + # ./script/../config/../app/views/versions/_form.rhtml + + # ./script/../config/../app/views/welcome/index.rhtml + + +end diff --git a/redmine/lang/fr_FR.rb b/redmine/lang/fr_FR.rb new file mode 100644 index 000000000..5dc7a59e0 --- /dev/null +++ b/redmine/lang/fr_FR.rb @@ -0,0 +1,316 @@ +Localization.define('fr', 'Français') do |l| + + # trackers + l.store 'Bug', 'Anomalie' + l.store 'Feature request', 'Evolution' + l.store 'Support request', 'Assistance' + # issue statuses + l.store 'New', 'Nouveau' + l.store 'Assigned', 'Assignée' + l.store 'Resolved', 'Résolue' + l.store 'Closed', 'Fermée' + l.store 'Rejected', 'Rejetée' + l.store 'Feedback', 'Commentaire' + + # issue priorities + l.store 'Issue priorities', 'Priorités des demandes' + l.store 'Low', 'Bas' + l.store 'Normal', 'Normal' + l.store 'High', 'Haut' + l.store 'Urgent', 'Urgent' + l.store 'Immediate', 'Immédiat' + # document categories + l.store 'Document categories', 'Catégories de documents' + l.store 'Uncategorized', 'Sans catégorie' + l.store 'User documentation', 'Documentation utilisateur' + l.store 'Technical documentation', 'Documentation technique' + # dates + l.store '(date)', lambda { |t| t.strftime('%d/%m/%Y') } + l.store '(time)', lambda { |t| t.strftime('%d/%m/%Y %H:%M') } + + # ./script/../config/../app/views/account/login.rhtml + + # ./script/../config/../app/views/account/my_account.rhtml + l.store 'My account', 'Mon compte' + l.store 'Login', 'Identifiant' + l.store 'Created on', 'Crée le' + l.store 'Last update', 'Mis à jour' + l.store 'Information', 'Informations' + l.store 'Firstname', 'Prénom' + l.store 'Lastname', 'Nom' + l.store 'Mail', 'Mail' + l.store 'Language', 'Langue' + l.store 'Mail notifications', 'Notifications par mail' + l.store 'Save', 'Valider' + l.store 'Password', 'Mot de passe' + l.store 'New password', 'Nouveau mot de passe' + l.store 'Confirmation', 'Confirmation' + + # ./script/../config/../app/views/account/my_page.rhtml + l.store 'My page', 'Ma page' + l.store 'Welcome', 'Bienvenue' + l.store 'Last login', 'Dernière connexion' + l.store 'Reported issues', 'Demandes soumises' + l.store 'Assigned to me', 'Demandes qui me sont assignées' + + # ./script/../config/../app/views/account/show.rhtml + l.store 'Registered on', 'Inscrit le' + l.store 'Projects', 'Projets' + l.store 'Activity', 'Activité' + + # ./script/../config/../app/views/admin/index.rhtml + l.store 'Administration', 'Administration' + l.store 'Users', 'Utilisateurs' + l.store 'Roles and permissions', 'Rôles et permissions' + l.store 'Trackers', 'Trackers' + l.store 'Custom fields', 'Champs personnalisés' + l.store 'Issue Statuses', 'Statuts des demandes' + l.store 'Workflow', 'Workflow' + l.store 'Enumerations', 'Listes de valeurs' + + # ./script/../config/../app/views/admin/info.rhtml + l.store 'Version', 'Version' + l.store 'Database', 'Base de données' + + # ./script/../config/../app/views/admin/mail_options.rhtml + l.store 'Select actions for which mail notification should be enabled.', 'Sélectionner les actions pour lesquelles la notification par mail doit être activée.' + l.store 'Check all', 'Cocher tout' + l.store 'Uncheck all', 'Décocher tout' + + # ./script/../config/../app/views/admin/projects.rhtml + l.store 'Project', 'Projet' + l.store 'Description', 'Description' + l.store 'Public', 'Public' + l.store 'Delete', 'Supprimer' + l.store 'Previous', 'Précédent' + l.store 'Next', 'Suivant' + + # ./script/../config/../app/views/custom_fields/edit.rhtml + l.store 'Custom field', 'Champ personnalisé' + + # ./script/../config/../app/views/custom_fields/list.rhtml + l.store 'Name', 'Nom' + l.store 'Type', 'Type' + l.store 'Required', 'Obligatoire' + l.store 'For all projects', 'Pour tous les projets' + l.store 'Used by', 'Utilisé par' + + # ./script/../config/../app/views/custom_fields/new.rhtml + l.store 'New custom field', 'Nouveau champ personnalisé' + l.store 'Create', 'Créer' + + # ./script/../config/../app/views/custom_fields/_form.rhtml + l.store '0 means no restriction', '0 pour aucune restriction' + l.store 'Regular expression pattern', 'Expression régulière' + l.store 'Possible values', 'Valeurs possibles' + + # ./script/../config/../app/views/documents/edit.rhtml + l.store 'Document', 'Document' + + # ./script/../config/../app/views/documents/show.rhtml + l.store 'Category', 'Catégorie' + l.store 'Edit', 'Modifier' + l.store 'download', 'téléchargement' + l.store 'Add file', 'Ajouter le fichier' + l.store 'Add', 'Ajouter' + + # ./script/../config/../app/views/documents/_form.rhtml + l.store 'Title', 'Titre' + + # ./script/../config/../app/views/enumerations/edit.rhtml + + # ./script/../config/../app/views/enumerations/list.rhtml + + # ./script/../config/../app/views/enumerations/new.rhtml + l.store 'New enumeration', 'Nouvelle valeur' + + # ./script/../config/../app/views/enumerations/_form.rhtml + + # ./script/../config/../app/views/issues/change_status.rhtml + l.store 'Issue', 'Demande' + l.store 'New status', 'Nouveau statut' + l.store 'Assigned to', 'Assigné à' + l.store 'Fixed in version', 'Version corrigée' + l.store 'Notes', 'Remarques' + + # ./script/../config/../app/views/issues/edit.rhtml + l.store 'Status', 'Statut' + l.store 'Tracker', 'Tracker' + l.store 'Priority', 'Priorité' + l.store 'Subject', 'Sujet' + + # ./script/../config/../app/views/issues/show.rhtml + l.store 'Author', 'Auteur' + l.store 'Change status', 'Changer le statut' + l.store 'History', 'Historique' + l.store 'Attachments', 'Fichiers' + l.store 'Update...', 'Changer...' + + # ./script/../config/../app/views/issues/_list_simple.rhtml + l.store 'No issue', 'Aucune demande' + + # ./script/../config/../app/views/issue_categories/edit.rhtml + + # ./script/../config/../app/views/issue_categories/_form.rhtml + + # ./script/../config/../app/views/issue_statuses/edit.rhtml + l.store 'Issue status', 'Statut de demande' + + # ./script/../config/../app/views/issue_statuses/list.rhtml + l.store 'Issue statuses', 'Statuts de demande' + l.store 'Default status', 'Statut par défaut' + l.store 'Issue closed', 'Demande fermée' + l.store 'Color', 'Couleur' + + # ./script/../config/../app/views/issue_statuses/new.rhtml + l.store 'New issue status', 'Nouveau statut' + + # ./script/../config/../app/views/issue_statuses/_form.rhtml + + # ./script/../config/../app/views/layouts/base.rhtml + l.store 'Home', 'Accueil' + l.store 'Help', 'Aide' + l.store 'Log in', 'Connexion' + l.store 'Logout', 'Déconnexion' + l.store 'Overview', 'Aperçu' + l.store 'Issues', 'Demandes' + l.store 'Reports', 'Rapports' + l.store 'News', 'Annonces' + l.store 'Change log', 'Historique' + l.store 'Documents', 'Documents' + l.store 'Members', 'Membres' + l.store 'Files', 'Fichiers' + l.store 'Settings', 'Configuration' + l.store 'My projects', 'Mes projets' + l.store 'Logged as', 'Connecté en tant que' + + # ./script/../config/../app/views/mailer/issue_add.rhtml + + # ./script/../config/../app/views/mailer/issue_change_status.rhtml + + # ./script/../config/../app/views/mailer/_issue.rhtml + + # ./script/../config/../app/views/news/edit.rhtml + + # ./script/../config/../app/views/news/show.rhtml + l.store 'Summary', 'Résumé' + l.store 'By', 'Par' + l.store 'Date', 'Date' + + # ./script/../config/../app/views/news/_form.rhtml + + # ./script/../config/../app/views/projects/add.rhtml + l.store 'New project', 'Nouveau projet' + + # ./script/../config/../app/views/projects/add_document.rhtml + l.store 'New document', 'Nouveau document' + l.store 'File', 'Fichier' + + # ./script/../config/../app/views/projects/add_issue.rhtml + l.store 'New issue', 'Nouvelle demande' + l.store 'Attachment', 'Fichier' + + # ./script/../config/../app/views/projects/add_news.rhtml + + # ./script/../config/../app/views/projects/add_version.rhtml + l.store 'New version', 'Nouvelle version' + + # ./script/../config/../app/views/projects/changelog.rhtml + + # ./script/../config/../app/views/projects/destroy.rhtml + l.store 'Are you sure you want to delete project', 'Êtes-vous sûr de vouloir supprimer le projet' + + # ./script/../config/../app/views/projects/list.rhtml + l.store 'Public projects', 'Projets publics' + + # ./script/../config/../app/views/projects/list_documents.rhtml + l.store 'Desciption', 'Description' + + # ./script/../config/../app/views/projects/list_files.rhtml + l.store 'Files', 'Fichiers' + l.store 'New file', 'Nouveau fichier' + + # ./script/../config/../app/views/projects/list_issues.rhtml + l.store 'Apply filter', 'Appliquer' + l.store 'Reset', 'Annuler' + l.store 'Report an issue', 'Nouvelle demande' + + # ./script/../config/../app/views/projects/list_members.rhtml + l.store 'Project members', 'Membres du projet' + + # ./script/../config/../app/views/projects/list_news.rhtml + l.store 'Read...', 'Lire...' + + # ./script/../config/../app/views/projects/settings.rhtml + l.store 'New member', 'Nouveau membre' + l.store 'Versions', 'Versions' + l.store 'New version...', 'Nouvelle version...' + l.store 'Issue categories', 'Catégories des demandes' + l.store 'New category', 'Nouvelle catégorie' + + # ./script/../config/../app/views/projects/show.rhtml + l.store 'Homepage', 'Site web' + l.store 'open', 'ouverte(s)' + l.store 'View all issues', 'Voir toutes les demandes' + l.store 'View all news', 'Voir toutes les annonces' + l.store 'Latest news', 'Dernières annonces' + + # ./script/../config/../app/views/projects/_form.rhtml + + # ./script/../config/../app/views/reports/issue_report.rhtml + l.store 'Issues by tracker', 'Demandes par tracker' + l.store 'Issues by priority', 'Demandes par priorité' + l.store 'Issues by category', 'Demandes par catégorie' + + # ./script/../config/../app/views/reports/_simple.rhtml + l.store 'Open', 'Ouverte' + l.store 'Total', 'Total' + + # ./script/../config/../app/views/roles/edit.rhtml + l.store 'Role', 'Rôle' + + # ./script/../config/../app/views/roles/list.rhtml + l.store 'Roles', 'Rôles' + + # ./script/../config/../app/views/roles/new.rhtml + l.store 'New role', 'Nouveau rôle' + + # ./script/../config/../app/views/roles/workflow.rhtml + l.store 'Workflow setup', 'Configuration du workflow' + l.store 'Select a workflow to edit', 'Sélectionner un workflow à mettre à jour' + l.store 'New statuses allowed', 'Nouveaux statuts autorisés' + + # ./script/../config/../app/views/roles/_form.rhtml + l.store 'Permissions', 'Permissions' + + # ./script/../config/../app/views/trackers/edit.rhtml + + # ./script/../config/../app/views/trackers/list.rhtml + l.store 'View issues in change log', 'Demandes affichées dans l\'historique' + + # ./script/../config/../app/views/trackers/new.rhtml + l.store 'New tracker', 'Nouveau tracker' + + # ./script/../config/../app/views/trackers/_form.rhtml + + # ./script/../config/../app/views/users/add.rhtml + l.store 'New user', 'Nouvel utilisateur' + + # ./script/../config/../app/views/users/edit.rhtml + l.store 'User', 'Utilisateur' + + # ./script/../config/../app/views/users/list.rhtml + l.store 'Admin', 'Admin' + l.store 'Locked', 'Verrouillé' + + # ./script/../config/../app/views/users/_form.rhtml + l.store 'Administrator', 'Administrateur' + + # ./script/../config/../app/views/versions/edit.rhtml + + # ./script/../config/../app/views/versions/_form.rhtml + + # ./script/../config/../app/views/welcome/index.rhtml + + +end diff --git a/redmine/public/.htaccess b/redmine/public/.htaccess new file mode 100644 index 000000000..d3c998345 --- /dev/null +++ b/redmine/public/.htaccess @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

Application error

Rails application failed to start properly" \ No newline at end of file diff --git a/redmine/public/404.html b/redmine/public/404.html new file mode 100644 index 000000000..e1c8e916f --- /dev/null +++ b/redmine/public/404.html @@ -0,0 +1,7 @@ + + + +

File not found

+ + \ No newline at end of file diff --git a/redmine/public/500.html b/redmine/public/500.html new file mode 100644 index 000000000..713ee8aa0 --- /dev/null +++ b/redmine/public/500.html @@ -0,0 +1,7 @@ + + + +

Sorry, an application error occured

+ + \ No newline at end of file diff --git a/redmine/public/dispatch.cgi b/redmine/public/dispatch.cgi new file mode 100644 index 000000000..7095803c1 --- /dev/null +++ b/redmine/public/dispatch.cgi @@ -0,0 +1,10 @@ +#!e:/ruby/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/redmine/public/dispatch.fcgi b/redmine/public/dispatch.fcgi new file mode 100644 index 000000000..418aa3a97 --- /dev/null +++ b/redmine/public/dispatch.fcgi @@ -0,0 +1,24 @@ +#!e:/ruby/bin/ruby +# +# You may specify the path to the FastCGI crash log (a log of unhandled +# exceptions which forced the FastCGI instance to exit, great for debugging) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/redmine/public/dispatch.rb b/redmine/public/dispatch.rb new file mode 100644 index 000000000..7095803c1 --- /dev/null +++ b/redmine/public/dispatch.rb @@ -0,0 +1,10 @@ +#!e:/ruby/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/redmine/public/favicon.ico b/redmine/public/favicon.ico new file mode 100644 index 000000000..e69de29bb diff --git a/redmine/public/images/Copie de help.png b/redmine/public/images/Copie de help.png new file mode 100644 index 0000000000000000000000000000000000000000..6dc4f684a771e74bd03e644dac9e71bf7257f5ce GIT binary patch literal 379 zcmV->0fhdEP) z4-En9y*hjV000SaNLh0L01FZT01FZU(%pXi00004XF*Lt006O%3;baP0001HP)t-s z|NsBEsTjt#8rH-dysa7M*hl5mG~&=8%w_=W;A`pIQtI4U&%PVIwHfc_fa={~>)vGN z*FxjbCiU;N^Xs7S=Zx*)a^=-G_VK**?XL3bobKa(^zEnj^2Pf1*ZcY7`SsBF^T!Pn z!dw6V00DGTPE!Ct=GbNc004_gL_t(|+HH;L62c%127$EY&>k35dq|J>e@WBOQO9xr z?eYOi{bx%^U%IU8whyCy`+|r>G(q;1&^uxrxSc-PUqlx)LEmCFpd+=EiXDK zBn)@zd-lkKBOrUrp?p9sK{ZVqx)|fl6ovd^eFzul;9Bb(6qJ?akY5lC9Gn+2T7Qmz Zcmc7E2Xsj42f+XU002ovPDHLkV1jWdstf=C literal 0 HcmV?d00001 diff --git a/redmine/public/images/admin.png b/redmine/public/images/admin.png new file mode 100644 index 0000000000000000000000000000000000000000..0c190984fc7202276f9d75e799d83cfff6fce08a GIT binary patch literal 716 zcmV;-0yF)IP)9yhK}ScV}{X|I1ka zxg?KsKxli7yO2UwVr*?>QIv&X|GYB)v?JM5u>Qocfj_`L8nn#zX(iQUBL(|Kf)K@}m3Wk#t`~ zjB`@|!YKS=7VtzB@{ck9(`WzcmH+RW|NXPRn_K;-CjB=L`~U#&U?Ka)QUBU>|Ky7Q z|F-7QjKygx|4l6aMNaxUJ^7 zzP3%U(QumAlJ4JxskPgLr~T{fzI1JGueV>%(#*ro@wK+fdUboIg;s=aTIEahpa1{> z0d!JMQvg8b*k%9#0NqJMK~#9!V_<-Q62;QevUCO*$X6=Jwk|9xR)q3RGBUF=vvYED z^Eko$IKB9U#H8eu)YP;PkghP^@W609NA72JVJ9`I5Csj*l4HqjzS2s}x6H_yD3q1i#eRTsXYa3ez1`a`O z9bG*ERiJ>Tp^-5IgOa4AgtCf&sv6J`Es(>+B_yRprDfP<<>VE>4&@gR6k-tO6%plQ y5`(y#lZ}g;hnJ550a#es*f|)G0V5MLf&&1s-zLY=jF7zm0000lm$G>8qI;8 eo(!SZK$AcJU=9tPyNd^?jlt8^&t;ucLK6T|c{Tt5 literal 0 HcmV?d00001 diff --git a/redmine/public/images/bulletred.png b/redmine/public/images/bulletred.png new file mode 100644 index 0000000000000000000000000000000000000000..26a1210579eb8929fb270f426b8f579eff5fa7f1 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPF1O+}0E~gB)8BSTiqpZ;!=;_H2 cY7I2`^AG0G(7C&KfZ7;5UHx3vIVCg!0IX~>DF6Tf literal 0 HcmV?d00001 diff --git a/redmine/public/images/delete.png b/redmine/public/images/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..2ed33bdf3547b2e0c4241b73817a60b0b4ba1518 GIT binary patch literal 320 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s3?%0jwTl2L_7YEDSN3bn+#-ftOSk$u1BEyX zJR*yM%7j3e@zUM8KR`j2bVpxD28NCO+u=IGeCVt(B&pQL?x&H?MQ z&PKmlzS92L=T}`;dy_U7onQ53PG(DNL!gk?l>NMGOm+&$tO#EqC;uXjS!|QI-%g+< N44$rjF6*2UngHz^i}C;f literal 0 HcmV?d00001 diff --git a/redmine/public/images/dir.png b/redmine/public/images/dir.png new file mode 100644 index 0000000000000000000000000000000000000000..d078094ac8f655c3836237cc9f1be1de99907b05 GIT binary patch literal 314 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gw!VDxI9G)Z!q}WS5eO=kFF-!0=$ZkwnzXK@5 zS>O>_%)r3)0fZTy)|kuy3bLd-`Z_W&Z0zU$lgJ9>8wB`-xc>kD-__N1=FFKl+8EN( z((X)WxI3TW@oI)Q+ZcWxW%zg1=;I!S|966b%FdiQ^Z$8=Y@Pl~pkBt3AirP+hi5m^ zfSd+T7srr_TgeGPP?XTXyxf(e@Bdv_J|De+Z}WF|$6Rx?_?I4bH}-p$Y{bIzD?>Lo z$Z%9&T{YGEozs!MVQZuA&U?n1d_Kd!IEZ1RZTh(v8vceXvmf8P7#KHg5l7nUCDWHK zwY27HuvA*OaG_sQlfZ1(9$%k?95zK^SH~T{9TqXTozRr!+?bRObPI#0tDnm{r-UW| D1kHGz literal 0 HcmV?d00001 diff --git a/redmine/public/images/dir_new.png b/redmine/public/images/dir_new.png new file mode 100644 index 0000000000000000000000000000000000000000..2d29814f22db3acb14e72705458e2eaeedc2a874 GIT binary patch literal 321 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gw!3-o-jq62#6nlxMuPggCW(giM3C}AFN`XS0 z1s;*b3=G^tAk28_ZrvZCAWOQVuOkD)#(wTUiL5}raez;V>;M1%U0q#g&YXFpjUg>9 z?ap+DyYm?yuV#3&jp6?pATs{AhvEO7AfU1{XU_a*VEF&MqjI-74^TT}NswPKgTu2M zX+Tbkr;B4q#jV&rPocvKJS?pbnWCl=>CEd~2k%3`jKlh(RRt5%Uh5(-s*Z=?jySlojrKL@%Vwjo6 zaIT(VWjn*nnKSoHWH{f;aAqdMouv%#_b~iF!|?zA%>Q>o|39q+8Z`<=LtuD>fKj5r zexOenOM?7@862M7NCR?eJY5_^DsJVTixxU6AmAF!!pNMVf;D358OLR>s>ys*n(C?EIi>3vLPcM?+-~6iCU1HYt)t56f6zd9l zRNRZV{l4wYocVROWS|L)m*&Adb1_8=_j}dZ|6`kik9V(k$d^fc#Ch)w8^4R}tgWEn N^>p=fS?83{1OPHKb29({ literal 0 HcmV?d00001 diff --git a/redmine/public/images/document.png b/redmine/public/images/document.png new file mode 100644 index 0000000000000000000000000000000000000000..34fde6eb34278a9c511d34d47ccdf0b00d9935fd GIT binary patch literal 1014 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRp!3-o*pRS7lQtTz3zOL-om}Ld!Rm*N|o(B}- zEbxddW?Nn~YUU}gyL332`Z|NqRHGt<)2jE#-g z7&FW?X4qlOuyyO!GikAYB(84v6p!Iy0Twm<`&U6*#2X+9#Dw0 zz$3C4sPqE}Gdis?nE@1JNq6*hWMJ6X&;2Kn705RT@CkAK|NsBYnKOYrAmjfT#WAGfR&1}Q5Q8Gek(96h?dP$vEzRL~ z;@MI2c+JeW?`BsD^nZ|ABO}uObY9l8oaxg;7kX^|ALLLcD-}HJlxkGhoU0#?1-$W-JFTn9*sC$qb+%OS+@4BLl<6e(pbstPBjy3;{kNuK)i3RkWQbXWc1p)}&xk ztK_`ECVsb1(dDStC#h53#rHf9sJv_*wo=xlLdAV;VA18s`bS|+@BPZ(hBUm6>v@+v z@tH%)4n^bIp#0NC6W?^K_*CydBRRi}m&V61n`+H{3 zhn0JO&D!|gzv7*H3DAT$ex+9wjO#T54z1q*sbt2t{Ar&z9QzSc^VYxWm3Q%*z?wJF zUC*@q4;{byt99v*(rModCVji}{BLB<2fxZ!34QP6%sVxMP8`1UbMdxc6|=wQP5PAC z|0${Cb5!&DklOb?mG8pqAIjKtY6hQNw)?~3%ir5q{Vt#Ly>$AIfB*g`c72X*eD9Te zQNdJYQHW!}q64}KlH^kc!U zuk9u|mlT~AY6YE1>$tya_s7#$e{MYby?gQ7z@qai?yI#z&Z-9; zRSP(v;j;%A1*2fFg@9t~--4Ep+7I^J z+4HBV>CmD_LLAr4Emci*ZIz9+t=Z4?cl31ib@q1qd-yPU|NhC&!p6$p-qP0A%KWp> z@0_0Ay1aLC^WyH=Iqcinx^(K+o}S*dbLZ}5p0IxP^85r1<>Zu{q^x5+PqZ~xuUK_* zMRrD3R_5ylo+%TiOqw=v>g37c0W7zVAJ*(C=hwe;w8zKi dj7~%?8^e@(`6V~x7A^pVtf#A=%Q~loCID_OMxp=! literal 0 HcmV?d00001 diff --git a/redmine/public/images/home.png b/redmine/public/images/home.png new file mode 100644 index 0000000000000000000000000000000000000000..7a12add6afb4f25a8dd95c7ed8b301ae02a9d4cf GIT binary patch literal 301 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJdx@v7EBiHOE)fPspSCYDKq1Zo zkH}&M25un`X1sK_?hjCqCEd~2k%3`jKlh(RRv_Opz$e7@|Ns9ROBrXTF&G;&q%knu zozHM)Cd2l6h8JrY_H;0Q+Qsny48!X!4DRld!ND5&`QB-1#+8-fXBZgf>es#k8p2o- zZCW4ycX4!9Pg~F+@9W?pCUe6_CrcV1K<5KJ-k7SZY*o`j>}Cv0Vr5z)_D33MD}$%2pUXO@geCywmS}_k literal 0 HcmV?d00001 diff --git a/redmine/public/images/issues.png b/redmine/public/images/issues.png new file mode 100644 index 0000000000000000000000000000000000000000..e6948bff7a54bdcb80d08155aaf01d6cfa3744f7 GIT binary patch literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7T3?v)swEqJs_7YEDSN3bnf?~QNt#AEa0EIXU zJR*x37`TN%nDNrxx<5ccmUKs7M+SzC{oH>NS%G|m0G|-o|NsBboH-N7o0-O7Y-|kV zrlqC*KV!UQ%a)ZZR|0uwfWVmH;lqbOtKPnSyZGp&XrOAwk|4ie28U-i(tw;*o-U3d z6}L>!DDoY05NUZBDW!9gdrQ;0h08dbF4*t|Y}(6^WO{JTa}E^_z5~1*T(=YEt#T-0 z;%Q4ZNt_<^{ptA>2A3X1hE@l$pOa@ixo4=hXx=o9mn-D&-6{5t`NJlCn@cpdbnO*Z zxAz|A*E$0uYW_4=FKztXop&pCZ-~U3^}k*|dC^%@CG6?@^5y0e2Sd18Z2bJ5hi+|K q)F`{#b3wUTa&W7zgXE5uUrb;BXx;j~Paf!K1_n=8KbLh*2~7aPZI06b literal 0 HcmV?d00001 diff --git a/redmine/public/images/locked.png b/redmine/public/images/locked.png new file mode 100644 index 0000000000000000000000000000000000000000..5199dfe2279374d99e1f64ca030457e7754f7fe7 GIT binary patch literal 437 zcmV;m0ZRUfP)ZWzG@P~Z5F$09{>OU|I~W_z*zsgQUAkQ|K@zZaU!}|0{{QN|L~~) z#dH6rPXC)jvttwg;-CN1i~q4!|C~Gjx=yxM0RR8*|NpiB>YM+$WdD>rwQ3;$;D-O+ zeE;2ZylENdWdQhT0P0Er|9AlVdI0y394Ks)!vFu8=l>{qp-X&~08w?!|NlmWi2_%E z+5i7(rmvjT)VcKY8)AC~ReQC)y}QuRtG&G~Yla@10q6h#00DGTPE!Ct=GbNc0041G zL_t(|+GAi~V6e2Zwy|XZ17mg*Q!{f5Fk7G9fPul#2+Y>j0TQ};V7987x`w8fCWy(# zuE@cmq|BoNWb;YO$jZsfb0`4W%Eip-#gC`UKlj&v z?r;7)-u`}j{{R2)|NsC0{r∓pX3;umAph|NHy%pI@K;Jl-8B{5(DUetY`!{P6qb z>CemK*K$rh12mAaB*-tA!Qt7BG$5zl)5S5Q;#ThYXucx~9IO|@CPu$nks<#({*^}B zB(>m2{O)_&Qh(~pqz8vSlQ{7!h9Tzq-6?NMQuI$&?1i9seTz(tw0}62# zctjR6FmMZjFyp1Wb$@_@Ea{HEjtmSN`?>!lvI6;v0X`wF|Ns9tHa1R6OPe`!=9Vp6 zR<2xm=FAzO;M=!vA3l6|#+cz`@a6wN#f&9Ee!&b5&u*jvIn|ynjv*Ddbo&hX4h3+u zEzI$I-|swmomBcg#VUcV51C|NybIF55Sg!KBhPtK{9ivqpnjj&+mcD4(zW)FI(;U0 zsT<7|ZP>phcSrv5%|c$vt4-2&-BjK?WAEfcx4$3eY8PLr>$tdU=G}{XX6uTcczn3@ j*!+wg3kvGj3H)a;ST4c;F_~>S&>0M#u6{1-oD!M<0?l#W literal 0 HcmV?d00001 diff --git a/redmine/public/images/notes.png b/redmine/public/images/notes.png new file mode 100644 index 0000000000000000000000000000000000000000..d26b1d57738334f668dcc37c7b8e1a533173e03d GIT binary patch literal 996 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJdx@v7EBiHOSwT6&Qt7ZoKq1Zo zkH}&M2Cfev%;>bnWCl=>CEd~2k%3`jKlh(RRt5%Uh5(-s*Z=?jr=_KxIdf*_%$a-k z?Af(z*T#(-*Q{Cd?%lg*&z?Pb^5p5$r+4n$*}Z%BmMvR0Y}f!)zjNo#moHyFeE9J0 z-MjDKzXzH;3PwX9$@(X5gcy=QV$cgfFaSW-rmD+bxtU-aph4og( z%pU;{ryDSu6p;VYO^F#Q|4~hYQ9c)~DZ1nfE weccv4K~Cajd*E!&xn{l%Hh;Ra@>)MJT)oPoKlhN}5>RM)y85}Sb4q9e0L*}V)c^nh literal 0 HcmV?d00001 diff --git a/redmine/public/images/options.png b/redmine/public/images/options.png new file mode 100644 index 0000000000000000000000000000000000000000..a907c20f16fce510355a9143968e1cf88b7cab84 GIT binary patch literal 1005 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJdx@v7EBiHOSwT6~{DV#RfkK=G z9+AZi3|t>Tn9*sC$qb+%OS+@4BLl<6e(pbstPBjy3;{kNuK)l4H#Rm-OG}$MbEdKJ z|1Dd#tX#R$c;^2zXU_aT!*C{z;mk~iw{PG6pUD7JIn$Woj4{K*hYx|Kje^k-7zQEm z-eg)O&>M^;L4Lsu4$p3+0XfN@E{-7;w_?vm@*Pm%a1CV4oxAn-{TxS=Yxnk-J2e@~ zOtf75OL^7TCHHzlY5PG`$xvXEYS%M8ALpzw=QN?QJ*&L=0(K=2M#=X z6egvpapJ&(M~@z9X=rFLh!hqIvUD1BG)TB{Is8>>YCGoUrO?dM+v}**qtGUF!^n|? cH>ELy;qD=kjajqRVnB}aboFyt=akR{0LXS>CIA2c literal 0 HcmV?d00001 diff --git a/redmine/public/images/projects.png b/redmine/public/images/projects.png new file mode 100644 index 0000000000000000000000000000000000000000..b42347a92ce282d9188971e85b9f4254ee944e9d GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPF2*ig0XH)*OKsIAZkY6x^!?PP{Ku)2ji(^Q|t>lD+ga?TY zO~&jBCg*1w3mlteoNm@2x%%9kHlu5A+Kj>+C2}HHmrZY$$jn^5ZMwLDfx)t+sne!U zXOc|H^n3MmAsbImlct~7(uHCUZ5@**X$7=61{`tpG?iG<9oW+7d6i*Fd_$w-%tl5A X#z0wZgUpv9AXj<1`njxgN@xNAxJY5@ literal 0 HcmV?d00001 diff --git a/redmine/public/images/rails.png b/redmine/public/images/rails.png new file mode 100644 index 0000000000000000000000000000000000000000..b8441f182e06974083cf08f0acaf0e2fd612bd40 GIT binary patch literal 1787 zcmVCLdthj)A!BBmWB&y|X`RY;f`BJ<_ju%@N||NoLFD~mQl$aHGjq>;5dG_D{h(5s}0 z6&=HANU$m__3PuddU(lvR_xWj`}Oho@9EyQt-n!E*P(KhM@X_VFV2l&>deNZJT%y8iwA zoG>u1B`p2=_u9k4v1Mud`1+qvOZoHg#bITJ9U`qBAek?40RR96!AV3xRCwBy*IQ$v zN(=yC9IhRft9V64L`77pqF_Cx@c;kSNoGK)`?Ps*cP(EtGlYZ{D5cxspMQvjKH)Oh6X(pa|J{ zGy1J$Ej7=Z{uvmMfRRsE;v`p;45B~6*ep#hM^ji zl$+7qoWq~}ewG=61uFw0He{tJurMU&4Iv?=B^eR(wAHk!miA)O7p_+YR>lbmU3rmn ze?+ze(+sEd6foB&*l9+?zkr_a-5*v&p*?c}HOGtyHg6r{WFYpQ=#z0Hc7VWLx$>M3|b0|Gn z+5t#z6*ffSVc6DjpmB2?AAR@@vB!wCK?9Yl;33;Q7^%(401QW|k=R8b!OwtLJPjjm zO9Ia;qCq)rOq!1Ia*6#A%#xb}yDx1P*pWla>9j$bnMn3CBqe4`TRll_Iy29kmG?4fbKuF=XqU|?3b@B zA`&a?KIgZ|KJx5eND_c3Em=WZn@xW8hRJ^G&sY^b(FW?WC9W_sb;+lAPdLTdBaKIK;-f}*h4|1aTjw7qX_k~e{TWO7jqcekERN;Jyh%67)q4rKpL*CEYL;|#GY{B@5 zi52XoC?xsoorJKxsliugF#z38MJqrYCWV(t<=G&f;^Me13&AiI9{3jUZ$ zFM`*L(9qc^VMxkz1oaDH!1pcD^IXp>Z0Jb=_qs?Vsrs{mp<^{$N!EC9o+`CO-(o}E zJ`y{*;9s|wr22-QoJ87y^~;)Q@b%P4UgSSsx>2$o@Vd{%Pk0@4qZ^fhB(vt$c1TG> z*{Ad;foraENbld`=MCNm4?9kvlgK~&J>ialpJ7nua zx0oRzwG5;}Qne)Fg(N3kf?JVmB;}y&5(0+~r*aL$0Zof8fe!AtHWH>A^1Y)@G@GsA zup`R{Qg?{+MaxTq#2n{6w|)c&yaJ7{U4ngAH5v6I)*;@rEBE*ehIPBwKBQU)YKE8F0lR!Sm?sE4Xk-sj&E$|A-9n dP56HS1^^A-61FoN)nxzx002ovPDHLkV1kw_Sd9Px literal 0 HcmV?d00001 diff --git a/redmine/public/images/rails_powered.png b/redmine/public/images/rails_powered.png new file mode 100644 index 0000000000000000000000000000000000000000..5255e62def5400ef2863aadcbc4c1e05b28cbc01 GIT binary patch literal 262 zcmV+h0r~!kP)twtJ{H2&HXIs+%wegM>jwsKkVb5D6IonECB89s&{NYJF4M^b9Oz zQ|5d4yj{T42q!qJ`!<1c$g8t?u#{>yV@z+58I)5Jt*wYQ&osalEGzH~57PiykY`xg z-O?><8sG|6FUjWeU>nknSx-11+B}Py`yE!a^(v>caQmZe8|a;;F*<_b)V#8*WJ|{f z*rO|M^|Y%j39hPC%ya}Q5+X8lkUdO9;nq?(!N(c?tHIlf6zy;62Y=SGM!E{as{jB1 M07*qoM6N<$f``*@wEzGB literal 0 HcmV?d00001 diff --git a/redmine/public/images/rails_small.png b/redmine/public/images/rails_small.png new file mode 100644 index 0000000000000000000000000000000000000000..aff4b7a843b28629e0f9e26e6f51d9006695f5b1 GIT binary patch literal 1140 zcmV-)1dIELP)1|BPNp>B9Is$bPgSc6&`*WAF>}Pl^!RVA1aw5ESDxMnJO-s zFEExdFR~yitsf|`A1MF-|G`>IlPM~cEiLxz^8zYJqA)Ol|yG}W|NjI`7HnJl$m>MB=7agjos>PU% zxI{I!K{d2BFtRc_vMxBNC^WjFoxf#D=Eb<(s+hJsGPOA|w@^TB5*ejKJHw@;@ZHq8 zSwyx;Jd>~%hibG-Hp6U4?bg8Q&9UdmspiF^pC~BWp^w;`hR1nSzFk4+%B!e>UjQ#-(4Kfz-}#cNN#T0G&or?WUTy;widjBm_?V#|J7$$V4Pm~@&OBBebt z#d1o;Z%D&xN4i%-j{SjC00001bW%=J06^y0W&i*J3Q0skRCwAo&E5DF9!;R`US%xf%l>M!0W;^^bMAL%&Y8J{ z7#N>1m6}n*YPC8XDTh|a5QFIX`K!HIsr>%a*ls~md5|cXOL#_EYH$Hs)6oXETe%29VP>2ruHb8ER~iYKUrC=uf=8g>C#lgPK=cD>1?il;o9AM z_siw7EI)WyuoH$-noJkxZ!FxrB}o!KT)%w>tBo`U3d~-)iVce$UvdzJp%It^xKx~< zE0rX*p?cYh%w;0~r=4|YbEUlM6_s_Ni{0&tli6H8FHKF)AO}O|yW1(rG!B)&P)HGG zAQ`u+YxHbD>qfx|%`vWDYqE|(~JWK_y~z1{(p zvDs$B;{Zs24B!?Is%!>+9|*7?lhWM)qD$YYRxhvzlc&_V7myhfm{hid1FUKVK)=8e zqsC-1QS*+SyZZVp7K@dqHeN8`SFtRs=?16Oayq@iK>h)#vU$kG7F3D=0000=?BG literal 0 HcmV?d00001 diff --git a/redmine/public/images/role.png b/redmine/public/images/role.png new file mode 100644 index 0000000000000000000000000000000000000000..ad36d1ca83494163cd165a4f0708a9a8c187eb05 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRp!VDxAh&o3CDfSXiUsv{P%-n)35}&@=gaL&( z3p^r=85p>QK$!8;-MT+OL6&q!Uq=Rpjs4tz5?O(KtpJ}8*Z=?jpP9)pGmRlFEzQ{2 z`2QJ(GiT21+_C)it0!s33^S*C0TnR(XE2`05I*^AB~TY*NswPKgTu2MX+Tb)r;B4q z#jWH7ATUc{U{32k%O+42?Y@a|idk7gfC55)g%AJNY5;w|taC0$8rg!)9ma{WT zW&-K$T#XVA6CFG`oI^4fF*UnNPY{&SIFc}tFR6vgamfLm862A~`UtF);-GX)B9 z7I;J!Gca%qfiUBxyLEqnf-LEdzK#qG8~eHcB(ehe>H$6>t`DOawt6shnKS(FwEB?1 zaMYjSf4R*6G={H94F7>3hwJ~E(#O$^(G23vKrJzzE{-7;w~}w<^swbf^jyBspxk`( z)2CB2jZdFW+%ae3!8LpK6j^ahYc^aJV9BA(&1f2`l-V-FShgo|mBS{3wH&=q1$e}@ v=662h2x-s^Id~%BPy-v2qy+251_1`~`SJSG+?NMQuI$%X7!lvI6-A0X`wF|NsA=Hf`FoXU~8F#DLo_ zr66UDB|(0{3=Yq3qyagyo-U3d6}OTT92nGC6&5aGNzu^MWRP@NrXbMpltEI1apob1 Q$mbw!p00i_>zopr08rE~3IG5A literal 0 HcmV?d00001 diff --git a/redmine/public/images/sort_desc.png b/redmine/public/images/sort_desc.png new file mode 100644 index 0000000000000000000000000000000000000000..f82d539175624b088e448595d770127adf49bee0 GIT binary patch literal 217 zcmeAS@N?(olHy`uVBq!ia0vp^93afX3?$7I7w-U4>?NMQuI$%X7$35^2{3=A3% W8OvXa_lN_PGI+ZBxvX%y{W;-5;PJOS+@4BLl<6e(pbstU$g&fKQ0)|NsAI&YTJ4%}ir3HZ}%w z)6&xZpE2IDWy{KyD}lT-Kw!-9@Zm$CRd3(EU3_#>G*C5TNswPKgTu2MX+X{@PZ!6K zid&{<6!{K0h_pP6l+roLy`^d0!eyLI7i{&&e~M+%r^LG;f;5%N6qX?i72+{9%*6%_SOJy7mgI z+j|f5Yn_1+HGi6`mo|Rx&bt-6H$>vi`d=@fyy&c{687|c`Ev7#gCSfkHhzB3L$|gq qYLwmWxuD!EIk;8VL2^gSFQ%`5v~K<0Cl7Qq1B0ilpUXO@geCx>*Nx!- literal 0 HcmV?d00001 diff --git a/redmine/public/images/true.png b/redmine/public/images/true.png new file mode 100644 index 0000000000000000000000000000000000000000..9afc0b52a4fed4001e8a760161f889b69908896d GIT binary patch literal 183 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=%3?!FCJ6-`&>?NMQuI$%Xn0OgjBKmVC1BEyX zJR*x37`TN%nDNrxx<5ccmUKs7M+SzC{oH>NS%G}E0G|-o|Ns9R&t$lqv-1g1gs~*Z zFPOpM*^M+HN8QuKF{I*Fa>46C48?7+Y9b9c2{67&sYrHVITRs$4z6z+h6! V==jdE|UNN literal 0 HcmV?d00001 diff --git a/redmine/public/images/user.png b/redmine/public/images/user.png new file mode 100644 index 0000000000000000000000000000000000000000..89d591c0b28d4ecca87fbe2397e8b0f2eba0dbb6 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFyP z?A0 literal 0 HcmV?d00001 diff --git a/redmine/public/images/user_page.png b/redmine/public/images/user_page.png new file mode 100644 index 0000000000000000000000000000000000000000..940a7b8e19f1aa1fdda5bf3c6fe99146d899370b GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRt!VDzU8V$b#DfSXiUsv{P%-r0n`qxue$pD2o z3p^r=85p>QK$!8;-MT+OL6&q!Uq=Rpjs4tz5?O(KtpJ}8*Z=?jpP9)pGmYW@83tox zlpjF7T}G;_MuY&m3hvqmkceT=?d4^@G7GkCiCxvXdr%UBZR7tG-B>_!@p6XxmS7*cV|^~^=y1_c4;3sr@ULTmwi|4j1=3>ts)KNeK@ zwp{9odT*0-NhA1W99O^8X5xZzQMPh5f9 b;W)e6Eq>;0ERDTD8yGxY{an^LB{Ts5rD#se literal 0 HcmV?d00001 diff --git a/redmine/public/images/workflow.png b/redmine/public/images/workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..868332fedf0626a9b6c928689cd0bc54ddd0a32b GIT binary patch literal 285 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFbnWCl=>CEd~2k%3`jKlh(RRv_OXz$e7@|NsBS#>Q!BX)|Ze+_GiM z%9Sh6oH_IG;ls3T59`8jO&JWfd!?O4o~c=Y O9OCKf=d#Wzp$P!I>}5s( literal 0 HcmV?d00001 diff --git a/redmine/public/javascripts/application.js b/redmine/public/javascripts/application.js new file mode 100644 index 000000000..d8083a957 --- /dev/null +++ b/redmine/public/javascripts/application.js @@ -0,0 +1,8 @@ +function checkAll (id, checked) { + var el = document.getElementById(id); + for (var i = 0; i < el.elements.length; i++) { + if (el.elements[i].disabled==false) { + el.elements[i].checked = checked; + } + } +} \ No newline at end of file diff --git a/redmine/public/javascripts/controls.js b/redmine/public/javascripts/controls.js new file mode 100644 index 000000000..9742b6918 --- /dev/null +++ b/redmine/public/javascripts/controls.js @@ -0,0 +1,750 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// See scriptaculous.js for full license. + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if (this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if (typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (navigator.appVersion.indexOf('MSIE')>0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) + return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + + var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.firstChild); + + if(this.update.firstChild && this.update.firstChild.childNodes) { + this.entryCount = + this.update.firstChild.childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + okText: "ok", + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + ajaxOptions: {} + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + this.form.appendChild(okButton); + + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + this.form.appendChild(cancelLink); + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.type = "text"; + textField.name = "value"; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.name = "value"; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + new Ajax.Updater( + { + success: this.element, + // don't update on failure (this could be an option) + failure: null + }, + this.url, + Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions) + ); + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; \ No newline at end of file diff --git a/redmine/public/javascripts/dragdrop.js b/redmine/public/javascripts/dragdrop.js new file mode 100644 index 000000000..92d1f7316 --- /dev/null +++ b/redmine/public/javascripts/dragdrop.js @@ -0,0 +1,584 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// See scriptaculous.js for full license. + +/*--------------------------------------------------------------------------*/ + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + isContained: function(element, drop) { + var parentNode = element.parentNode; + return drop._containers.detect(function(c) { return parentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) { + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + if(drop.greedy) { + Droppables.activate(drop); + throw $break; + } + } + }); + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) + this.last_active.onDrop(element, this.last_active.element, event); + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + }, + + deactivate: function(draggbale) { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable.prototype = { + initialize: function(element) { + var options = Object.extend({ + handle: false, + starteffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); + }, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur}); + }, + endeffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); + }, + zindex: 1000, + revert: false, + snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] } + }, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) + this.handle = Element.childrenWithClassName(this.element, options.handle)[0]; + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + Element.makePositioned(this.element); // fix IE + + this.delta = this.currentDelta(); + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(this.element.style.left || '0'), + parseInt(this.element.style.top || '0')]); + }, + + initDrag: function(event) { + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + if(this.element._revert) { + this.element._revert.cancel(); + this.element._revert = null; + } + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + Draggables.notify('onStart', this, event); + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + Position.prepare(); + Droppables.show(pointer, this.element); + Draggables.notify('onDrag', this, event); + this.draw(pointer); + if(this.options.change) this.options.change(this); + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(!event.keyCode==Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1]); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + sortables: new Array(), + + options: function(element){ + element = $(element); + return this.sortables.detect(function(s) { return s.element == element }); + }, + + destroy: function(element){ + element = $(element); + this.sortables.findAll(function(s) { return s.element == element }).each(function(s){ + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + }); + this.sortables = this.sortables.reject(function(s) { return s.element == element }); + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, // fixme: unimplemented + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + hoverclass: null, + ghosting: false, + format: null, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass, + onHover: Sortable.onHover, + greedy: !options.dropOnEmpty + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // make it so + + // drop on empty handling + if(options.dropOnEmpty) { + Droppables.add(element, + {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false}); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.childrenWithClassName(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + options.droppables.push(e); + }); + + // keep reference + this.sortables.push(options); + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + if(!element.hasChildNodes()) return null; + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() && + (!options.only || (Element.hasClassName(e, options.only)))) + elements.push(e); + if(options.tree) { + var grandchildren = this.findElements(e, options); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : null); + }, + + onHover: function(element, dropon, overlap) { + if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon) { + if(element.parentNode!=dropon) { + var oldParentNode = element.parentNode; + dropon.appendChild(element); + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon).onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.addClassName(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.left = offsets[0] + 'px'; + Sortable._marker.style.top = offsets[1] + 'px'; + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px'; + else + Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + + Element.show(Sortable._marker); + }, + + serialize: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format || /^[^_]*_(.*)$/ + }, arguments[1] || {}); + return $(this.findElements(element, options) || []).map( function(item) { + return (encodeURIComponent(options.name) + "[]=" + + encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : '')); + }).join("&"); + } +} \ No newline at end of file diff --git a/redmine/public/javascripts/effects.js b/redmine/public/javascripts/effects.js new file mode 100644 index 000000000..414398ce4 --- /dev/null +++ b/redmine/public/javascripts/effects.js @@ -0,0 +1,854 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// See scriptaculous.js for full license. + +/* ------------- element ext -------------- */ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if(this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { + var children = $(element).childNodes; + var text = ''; + var classtest = new RegExp('^([^ ]+ )*' + ignoreclass+ '( [^ ]+)*$','i'); + + for (var i = 0; i < children.length; i++) { + if(children[i].nodeType==3) { + text+=children[i].nodeValue; + } else { + if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) + text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); + } + } + + return text; +} + +Element.setStyle = function(element, style) { + element = $(element); + for(k in style) element.style[k.camelize()] = style[k]; +} + +Element.setContentZoom = function(element, percent) { + Element.setStyle(element, {fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} + +Element.getOpacity = function(element){ + var opacity; + if (opacity = Element.getStyle(element, 'opacity')) + return parseFloat(opacity); + if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + Element.setStyle(element, { opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : null }); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + Element.setStyle(element, {opacity: value}); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, + { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.childrenWithClassName = function(element, className) { + return $A($(element).getElementsByTagName('*')).select( + function(c) { return Element.hasClassName(c, className) }); +} + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1'; + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = {} + +Effect.Transitions.linear = function(pos) { + return pos; +} +Effect.Transitions.sinoidal = function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; +} +Effect.Transitions.reverse = function(pos) { + return 1-pos; +} +Effect.Transitions.flicker = function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; +} +Effect.Transitions.wobble = function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; +} +Effect.Transitions.pulse = function(pos) { + return (Math.floor(pos*10) % 2 == 0 ? + (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); +} +Effect.Transitions.none = function(pos) { + return 0; +} +Effect.Transitions.full = function(pos) { + return 1; +} + +/* ------------- core effects ------------- */ + +Effect.Queue = { + effects: [], + _each: function(iterator) { + this.effects._each(iterator); + }, + interval: null, + add: function(effect) { + var timestamp = new Date().getTime(); + + switch(effect.options.queue) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + this.effects.push(effect); + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +} +Object.extend(Effect.Queue, Enumerable); + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + setOptions: function(options) { + this.options = Object.extend({ + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, options || {}); + }, + start: function(options) { + this.setOptions(options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) Effect.Queue.add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) Effect.Queue.remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) + Element.setStyle(this.element, {zoom: 1}); + var options = Object.extend({ + from: Element.getOpacity(this.element) || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + Element.setOpacity(this.element, position); + } +}); + +Effect.MoveBy = Class.create(); +Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { + initialize: function(element, toTop, toLeft) { + this.element = $(element); + this.toTop = toTop; + this.toLeft = toLeft; + this.start(arguments[3]); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + Element.makePositioned(this.element); + this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0'); + this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0'); + }, + update: function(position) { + Element.setStyle(this.element, { + top: this.toTop * position + this.originalTop + 'px', + left: this.toLeft * position + this.originalLeft + 'px' + }); + } +}); + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element) + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = Element.getStyle(this.element,'position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = Element.getStyle(this.element,'font-size') || '100%'; + ['em','px','%'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = width + 'px'; + if(this.options.scaleY) d.height = height + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + Element.setStyle(this.element, d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: Element.getStyle(this.element, 'background-image') }; + Element.setStyle(this.element, {backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = Element.getStyle(this.element, 'background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + Element.setStyle(this.element, Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + var oldOpacity = Element.getInlineOpacity(element); + var options = Object.extend({ + from: Element.getOpacity(element) || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { with(Element) { + if(effect.options.to!=0) return; + hide(effect.element); + setStyle(effect.element, {opacity: oldOpacity}); }} + }, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + var options = Object.extend({ + from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0), + to: 1.0, + beforeSetup: function(effect) { with(Element) { + setOpacity(effect.element, effect.options.from); + show(effect.element); }} + }, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { with(Element) { + setStyle(effect.effects[0].element, {position: 'absolute'}); }}, + afterFinishInternal: function(effect) { with(Element) { + hide(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + Element.makeClipping(element); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping].call(effect.element); }} + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var oldHeight = Element.getStyle(element, 'height'); + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { with(Element) { + makeClipping(effect.element); + setStyle(effect.element, {height: '0px'}); + show(effect.element); + }}, + afterFinishInternal: function(effect) { with(Element) { + undoClipping(effect.element); + setStyle(effect.element, {height: oldHeight}); + }} + }, arguments[1] || {}) + ); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = Element.getInlineOpacity(element); + return new Effect.Appear(element, { + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { with(Element) { + [makePositioned,makeClipping].call(effect.element); + }}, + afterFinishInternal: function(effect) { with(Element) { + [hide,undoClipping,undoPositioned].call(effect.element); + setStyle(effect.element, {opacity: oldOpacity}); + }} + }) + } + }); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: Element.getStyle(element, 'top'), + left: Element.getStyle(element, 'left'), + opacity: Element.getInlineOpacity(element) }; + return new Effect.Parallel( + [ new Effect.MoveBy(element, 100, 0, { sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { with(Element) { + makePositioned(effect.effects[0].element); }}, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoPositioned].call(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: Element.getStyle(element, 'top'), + left: Element.getStyle(element, 'left') }; + return new Effect.MoveBy(element, 0, 20, + { duration: 0.05, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -20, + { duration: 0.05, afterFinishInternal: function(effect) { with(Element) { + undoPositioned(effect.element); + setStyle(effect.element, oldStyle); + }}}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element); + Element.cleanWhitespace(element); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom'); + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { with(Element) { + makePositioned(effect.element); + makePositioned(effect.element.firstChild); + if(window.opera) setStyle(effect.element, {top: ''}); + makeClipping(effect.element); + setStyle(effect.element, {height: '0px'}); + show(element); }}, + afterUpdateInternal: function(effect) { with(Element) { + setStyle(effect.element.firstChild, {bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }}, + afterFinishInternal: function(effect) { with(Element) { + undoClipping(effect.element); + undoPositioned(effect.element.firstChild); + undoPositioned(effect.element); + setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }} + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element); + Element.cleanWhitespace(element); + var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom'); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { with(Element) { + makePositioned(effect.element); + makePositioned(effect.element.firstChild); + if(window.opera) setStyle(effect.element, {top: ''}); + makeClipping(effect.element); + show(element); }}, + afterUpdateInternal: function(effect) { with(Element) { + setStyle(effect.element.firstChild, {bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }}, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping].call(effect.element); + undoPositioned(effect.element.firstChild); + undoPositioned(effect.element); + setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }} + }, arguments[1] || {}) + ); +} + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { with(Element) { + makeClipping(effect.element); }}, + afterFinishInternal: function(effect) { with(Element) { + hide(effect.element); + undoClipping(effect.element); }} + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransistion: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: Element.getInlineOpacity(element) }; + + var dims = Element.getDimensions(element); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.MoveBy(element, initialMoveY, initialMoveX, { + duration: 0.01, + beforeSetup: function(effect) { with(Element) { + hide(effect.element); + makeClipping(effect.element); + makePositioned(effect.element); + }}, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { with(Element) { + setStyle(effect.effects[0].element, {height: '0px'}); + show(effect.effects[0].element); }}, + afterFinishInternal: function(effect) { with(Element) { + [undoClipping, undoPositioned].call(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransistion: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: Element.getInlineOpacity(element) }; + + var dims = Element.getDimensions(element); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { with(Element) { + [makePositioned, makeClipping].call(effect.effects[0].element) }}, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping, undoPositioned].call(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = Element.getInlineOpacity(element); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, from: 0, + afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + Element.makeClipping(element); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping].call(effect.element); + setStyle(effect.element, oldStyle); + }} }); + }}, arguments[1] || {})); +} diff --git a/redmine/public/javascripts/prototype.js b/redmine/public/javascripts/prototype.js new file mode 100644 index 000000000..e9ccd3c88 --- /dev/null +++ b/redmine/public/javascripts/prototype.js @@ -0,0 +1,1785 @@ +/* Prototype JavaScript framework, version 1.4.0 + * (c) 2005 Sam Stephenson + * + * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff + * against the source tree, available from the Prototype darcs repository. + * + * Prototype is freely distributable under the terms of an MIT-style license. + * + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.4.0', + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + + emptyFunction: function() {}, + K: function(x) {return x} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.inspect = function(object) { + try { + if (object == undefined) return 'undefined'; + if (object == null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } +} + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + return __method.call(object, event || window.event); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + return elements; +} +Object.extend(String.prototype, { + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(eval); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; + }, + + toQueryParams: function() { + var pairs = this.match(/^\??(.*)$/)[1].split('&'); + return pairs.inject({}, function(params, pairString) { + var pair = pairString.split('='); + params[pair[0]] = pair[1]; + return params; + }); + }, + + toArray: function() { + return this.split(''); + }, + + camelize: function() { + var oStringList = this.split('-'); + if (oStringList.length == 1) return oStringList[0]; + + var camelizedString = this.indexOf('-') == 0 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) + : oStringList[0]; + + for (var i = 1, len = oStringList.length; i < len; i++) { + var s = oStringList[i]; + camelizedString += s.charAt(0).toUpperCase() + s.substring(1); + } + + return camelizedString; + }, + + inspect: function() { + return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; + } +}); + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value >= (result || value)) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value <= (result || value)) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + iterator(value = collections.pluck(index)); + return value; + }); + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != undefined || value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0; i < this.length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + shift: function() { + var result = this[0]; + for (var i = 0; i < this.length - 1; i++) + this[i] = this[i + 1]; + this.length--; + return result; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); +var Hash = { + _each: function(iterator) { + for (key in this) { + var value = this[key]; + if (typeof value == 'function') continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject($H(this), function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + return pair.map(encodeURIComponent).join('='); + }).join('&'); + }, + + inspect: function() { + return '#'; + } +} + +function $H(object) { + var hash = Object.extend({}, object || {}); + Object.extend(hash, Enumerable); + Object.extend(hash, Hash); + return hash; +} +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + do { + iterator(value); + value = value.succ(); + } while (this.include(value)); + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responderToAdd) { + if (!this.include(responderToAdd)) + this.responders.push(responderToAdd); + }, + + unregister: function(responderToRemove) { + this.responders = this.responders.without(responderToRemove); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (responder[callback] && typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + parameters: '' + } + Object.extend(this.options, options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + try { + this.url = url; + if (this.options.method == 'get' && parameters.length > 0) + this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; + + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.options.method, this.url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + this.dispatchException(e); + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version]; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', + 'application/x-www-form-urlencoded'); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + header: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) {} + }, + + evalJSON: function() { + try { + return eval(this.header('X-JSON')); + } catch (e) {} + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (event == 'Complete') { + try { + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.header('Content-type') || '').match(/^text\/javascript/i)) + this.evalResponse(); + } + + try { + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, object) { + this.updateContent(); + onComplete(transport, object); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + var response = this.transport.responseText; + + if (!this.options.evalScripts) + response = response.stripScripts(); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + Element.update(receiver, response); + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + elements.push(child); + return elements; + }); +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) { + var Element = new Object(); +} + +Object.extend(Element, { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + Element[Element.visible(element) ? 'hide' : 'show'](element); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + update: function(element, html) { + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).include(className); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).add(className); + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).remove(className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + scrollTo: function(element) { + element = $(element); + var x = element.x ? element.x : element.offsetLeft, + y = element.y ? element.y : element.offsetTop; + window.scrollTo(x, y); + }, + + getStyle: function(element, style) { + element = $(element); + var value = element.style[style.camelize()]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + } + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (name in style) + element.style[name.camelize()] = style[name]; + }, + + getDimensions: function(element) { + element = $(element); + if (Element.getStyle(element, 'display') != 'none') + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = ''; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = 'none'; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return; + element._overflow = element.style.overflow; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + }, + + undoClipping: function(element) { + element = $(element); + if (element._overflow) return; + element.style.overflow = element._overflow; + element._overflow = undefined; + } +}); + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + if (this.element.tagName.toLowerCase() == 'tbody') { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
    '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set(this.toArray().concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set(this.select(function(className) { + return className != classNameToRemove; + }).join(' ')); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select) + element.select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + form = $(form); + var elements = new Array(); + + for (tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + findFirstElement: function(form) { + return Form.getElements(form).find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + Field.activate(Form.findFirstElement(form)); + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) { + var key = encodeURIComponent(parameter[0]); + if (key.length == 0) return; + + if (parameter[1].constructor != Array) + parameter[1] = [parameter[1]]; + + return parameter[1].map(function(value) { + return key + '=' + encodeURIComponent(value); + }).join('&'); + } + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value; + if (!value && !('value' in opt)) + value = opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = new Array(); + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) { + var optValue = opt.value; + if (!optValue && !('value' in opt)) + optValue = opt.text; + value.push(optValue); + } + } + return [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* prevent memory leaks in IE */ +Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px';; + element.style.left = left + 'px';; + element.style.width = width + 'px';; + element.style.height = height + 'px';; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} \ No newline at end of file diff --git a/redmine/public/manual/administration.html b/redmine/public/manual/administration.html new file mode 100644 index 000000000..22016c4d6 --- /dev/null +++ b/redmine/public/manual/administration.html @@ -0,0 +1,121 @@ + + + +redMine - Aide en ligne + + + + + + + +

    [ Index ]

    +

    Administration

    +Sommaire: +
      +
    1. Utilisateurs
    2. +
    3. Rôles et permissions
    4. +
    5. Trackers
    6. +
    7. Champs personnalisés
    8. +
    9. Statuts de demande
    10. +
    11. Workflow
    12. +
    13. Listes de valeurs
    14. +
    15. Notifications par mail
    16. +
    17. Informations
    18. +
    + + +

    1. Utilisateurs

    +

    Ces écrans vous permettent de gérer les utilisateurs de l'application.

    +

    1.1 Liste des utilisateurs

    +

    +Liste des utilisateurs
    +

    1.2 Création ou modification d'un utilisateur

    +
      +
    • Administrateur: déclare l'utilisateur comme administrateur de l'application.
    • +
    • Notifications par mail: permet d'activer ou non l'envoi automatique de notifications par mail pour cet utilisateur
    • +
    • Verrouillé: désactive le compte de l'utilisateur
    • +
    +

    En mode modification, laissez le champ Password vide pour laisser le mot de passe de l'utilisateur inchangé.

    +

    Un utilisateur déclaré comme administrateur dispose de toutes les permissions sur l'application et sur tous les projets.

    + + +

    2. Rôles et permissions

    +

    Les rôles permettent de définir les permissions des différents membres d'un projet.
    +Chaque membre d'un projet dispose d'un rôle unique au sein d'un projet. +Un utilisateur peut avoir différents rôles au sein de différents projets.

    +

    Sur l'écran d'édition du rôle, cochez les actions que vous souhaitez autoriser pour le rôle.

    + + +

    3. Trackers

    +

    Les trackers permettent de typer les demandes et de définir des workflows spécifiques pour chacun de ces types.

    + +

    4. Champs personnalisés

    +

    Les champs personnalisés vous permettent d'ajouter des informations supplémentaires sur les demandes.

    +Un champ personnalisé peut être de l'un des types suivants: +
      +
    • Integer: entier positif ou négatif
    • +
    • String: chaîne de caractère
    • +
    • Date: date
    • +
    • Boolean: booléen (case à cocher)
    • +
    • List: valeur à sélectionnée parmi une liste prédéfinie (liste déroulante)
    • +
    +Des éléments de validation peuvent être définis: +
      +
    • Required: champ dont la saisie est obligatoire sur les demandes
    • +
    • For all projects: champ automatiquement associé à l'ensemble des projets
    • +
    • Min - max length: longueurs minimales et maximales pour les champs en saisie libre (0 signifie qu'il n'y a pas de restriction)
    • +
    • Regular expression: expression régulière permettant de valider la valeur saisie
    • +
    • Possible values (only for lists): valeurs possibles pour les champs de type "List". Les valeurs sont séparées par le caractère |
    • +
    +

    Si l'option For all projects n'est pas activée, chaque projet pourra ou non utilisé le champ personnalisé pour ses demandes +(voir Project settings).

    + + +

    5. Statuts des demandes

    +

    Cet écran vous permet de définir les différents statuts possibles des demandes.

    +
      +
    • Closed: indique que le statut correspond à une demande considérée comme fermée
    • +
    • Default: statut appliqué par défaut aux nouvelles demandes (seul un statut peut être déclaré comme statut par défaut)
    • +
    • HTML color: code de couleur HTML représentant le statut à l'affichage
    • +
    + + +

    6. Workflow

    +

    Le workflow permet de définir quels changements les différents membres d'un projet sont autorisés à effectuer sur le statut des demandes, en fonction de leur type.

    +

    Sélectionnez le rôle et le type de demande pour lesquels vous souhaitez modifier le workflow, puis cliquez sur Edit. +L'écran vous permet alors de modifier, pour le rôle et le type de demande choisi, les changements autorisés.

    +

    Les lignes représentent les statuts initiaux des demandes. Les colonnes représentent les statuts autorisés à être appliqués.

    +

    Dans l'exemple ci-dessous, les demandes de type Bug au statut New pourront être passées au statut Assigned ou Resolved par le rôle Développeur.
    +Celles au statut Assigned pourront être passées au statut Resolved.
    +Le statut de toutes les autres demandes de type Bug ne pourra pas être modifié par le Développeur.

    +

    +Exemple de configuration d'un workflow
    +

    Remarque: pour qu'un rôle puisse changer le statut des demandes, la permission "Changer le statut des demandes" doit lui être explicitement donnée indépendemment de la configuration du workflow (voir Roles et permissions). + + +

    7. Listes de valeurs

    +

    Les listes de valeurs utilisées par l'application (exemple: les priorités des demandes) peuvent être personnalisées en fonction de vos besoins.
    +Cet écran vous permet de définir les valeurs possibles pour chacune des listes suivantes:

    +
      +
    • Priorités des demandes
    • +
    • Catégories de documents
    • +
    + + +

    8. Notifications par mail

    +

    Cet écran vous permet de sélectionner les actions qui donneront lieu à une notification par mail aux membres du projet.

    +

    Remarque: l'envoi de mails doit être activé dans la configuration de l'application si souhaitez effectuer des notifications.

    + + +

    9. Informations

    +

    Affiche des informations relatives à l'application

    +
      +
    • Version: version de l'application
    • +
    • Database: type de base de données utilisée
    • +
    + + + + + diff --git a/redmine/public/manual/images/issues_list.png b/redmine/public/manual/images/issues_list.png new file mode 100644 index 0000000000000000000000000000000000000000..2fa6dc1b630e4b6f7696cf71451bc7b3bc9d6e66 GIT binary patch literal 8055 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 literal 0 HcmV?d00001 diff --git a/redmine/public/manual/images/users_edit.png b/redmine/public/manual/images/users_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..3a4797e9d30d697a2710f82eaf31cdbb543478a9 GIT binary patch literal 2700 zcmb7GdpHy7AD^RXwbY!7m0>ugl$mmw<=&8T>0(S+?ha0oL}ZrxDwjHiBos%SC5)&k ziLlewwit6Q*SU_|HkQlI)bIKI-FcqR`+nc|`+UFe`#zud^M0TA=4pFt*{yrG0ssJ6 zn-iF`63Ybuqz;09k&HZ!oRX3lM3^Nm?0BfJXIMb+CAee2MPGnVz(oXH2L%tj8gL1I z1c}s#>*%777@&_Dzz-pHkjQG4`vH>7&0#inmYcaUzixwV4RsCOSZqtk370DX0QlF9 zCe`*iW>wodxl)V15VVuOJT^77YCy4lwB~^kH8j()8Gc;iYio`=lVZt;qSh0MoI2h8dS9HQc_TX zc*B#RatS|!uAAy7ri-U$2OOLZs-&##se|+4DH_32NC!`0WMpj!;%t^l$&AMNY%$ci zMX>=XUw8mF_)vDATwwxpWkTflBhQ@K!tC=P()K@ZIY&269 z=hB_gSm8gmm&7Qz`%*+M(z0EQ_U{~fn&Of}@DEGY=y;-_TfU`nenJuJveX$$yE|v> zMVgxcHId*OD50n2p3YD@>F7(l{6myjwQE#2ee~?UGbZD2kc1oOre>wwtH$HAb5=)8d}mXTSHpNPm=-K%-YTaQseOQ-|(@ ze-q?9<8kL;1&kUN0lgzOwbWSQa7tc4@owYY7olbKx;v?Y58OGu?M;J?eE|UdLCe8kAZk~Ld-91&Fj&_K#Hw4saj#fNpo9_ zo1i4sjoHsZ2#;e{ze3MQrOVdAo8%pR^LO1UIG3U%n?mL%UI?s)pqslW?L+ua(YL(# zzdNOSl!Zt*ZB;o5EE@i}gK+r*#0#v3%KzmEtWbS}8AabcIWJ6hWe7PB|w5y&-G8yQ8@ zuD!3HzG@SK08!e^U`}o-rC%tgRl{dMoSv>kyT;E+g^^DtS1SI(M8A0dB62XuVaYUv z{yLVSQNz=(=aF=+_|t|9D>G!dMvIOZ72}r%dmlK(4qw+J7rrhJyIzQXJ>TVcw|U?J_a9b&N$nKX9#?)RQq9%9 z*l=kzx%3qt3ZkoVeT-Ttx*Ie)Z(yQ#HG#LrBD6T_>)0l_Sn8aI zauW;~^~F=gQ`>Czh(xrC$kzA0QBGc9UUrdNJL<4# z=Aaml*D&I~=!yPRA{Fqs(70I9O8d7S>E@Gbm30H{;?x~q#Ju=^Q_)3MziIv&fjXty zp|v!>Xw_OZC5;&y%d%^0U$9H(tfq>0h3}PhjW2O7Nn1~BITsyckQlqnTs|fwF8qlk z=Eb65EXNU9Gi;FpOxgf1-fpx0kPTcqJiR{jWp>u5tnWtO*4EGkYu$=^>(y1B0dZ}% z8s0bLT`>*M!)apmq9!Qdq+*KjV?ka(gz@?F}h9Y@xmiyj5z1CyQ4 zpfKfyDxwHV$H((e`@(R?{W8hpfA_86y7VL5uH^HwE2HYlCi(c|F;Qg};N0Yp8Jf7) zCHQ@IHGWe;r=NXVW=;{^l)xRxO9~25G-|uZtl{}BX`1Tcb|&&3PWPveeP$`W%#v() zzP&GF;d)rM^2lFDE2la9Y1aNKIk%%#+=p46!lW`{5cj3ISF)eAKj`}QrXYXrpQtlY z_CA~~bj`W}-70Rc>01u6DpL##&)f0pO@3cf>0k}>t0Ps`UAQN&K+pDr?~GO_zfm>w zt#=Oo_O_3uKtn%n9Q0_Ac}jLQyhTM~K^A;dH)kXDDS-w_^7#YUSlVOCEIi`=1#RQc A=l}o! literal 0 HcmV?d00001 diff --git a/redmine/public/manual/images/users_list.png b/redmine/public/manual/images/users_list.png new file mode 100644 index 0000000000000000000000000000000000000000..0c9ef86ec6d739f9e86e7d586c3a7581710065fa GIT binary patch literal 6502 zcmbVQcQ~AHmmX0<5G{h}qD1uGjW%lZGWZc;^cG@7i$QeJyXZZl_ny&53BjmQ644ng zYSbNm`|Y>iUVH6cyMN3)&wHM8&b;qj*L&aRzQeUNln5U@c>n+a2vw97bO3;RJLtVT z?!DXZx5^^ug&PVogv!6MGKbo|c4pACbFuNm;_c*^umPB(yr1~2kCAj{%sWp4 zgW{3 z_MY`xTDX7%#K*&?MM6k-wy2@?b4yEWORM+V4bpQ#*OW(7%5>qdGG({IGfN45cL0E@ za=k6)-Huep<|Zm8hQbN&_ELu6I}k z4$P8%Nv|k4olg6WLvm$MLTfRQ`@+mMuwHpBL3trIodY_^b<%`rxtG!&-q+s3+CTp- z!Fe?Hx|JZgov=Wm3^LQnI(q?%g9nH9z_FCFa84+H_}MtBdWK?g=g9qR=UqJx|FVib z#W$bNF*M0acn+S%n*A()?*FAMQJQZp$&$j?d|LKd^_9$x0-+<4p$dGX6bG)aj7ukFF3MipBO+rmYQ$M%;)F6&-^nCYcf5KP zEsf)CX`Z!WY+^m5$>SArCg&`9)0Eq1PaTn3{RRnDUSSdZeV&c-V5~7mToFE(XxV<} zD>6_NYegZHo6`N~+1rC9b~U_i1p;^+U@^<^VK}+jW2Gogm8sS5;Vm(xwvek(ROFhe zY#p_~H4Xpt5^J7XE${vMdA(wxUsQ9(?=_B>4LZL+M>tV$J>OU_S|*l`wuT5Fx5oC_ zJ*HbdVm4Z*X1yO{sjVQ>I$F7Q;qP@yz?zygK{C$8wHwrYr{+$13jWAf-I#o{H~K zr1a6CQbUc2(Jo>XD!t1~K@Uvkjuy(u(H&G` zQ>=BdeG=O9Tg77TH&5dug72|OQpf7ZM^<*oiHdz)`5%Dct-svK;~6;@%FH)usi_c& zokkgtd|&h)N}m5XJM*lar3rel4cWnM%lvO&hBRmi{wcXrmdZFiodhlE_Trf25O%WG ztJZp#fr5i4RzXtHPxg-|Fy;JAzF~_O0iRpP#)}seSYzrMP6EpK`>b(>wF3>zVzE6n znQvdJ!3R)+e_03HE|MaIac*NW_?=g(WC)(ePDvogHajDB8jJAEkm&2BII_lOfQLMkS4qP&&E{JF) z+;++r=F&aNhMV_!qBFfVcQAcUd`~v6AX}-1^mn*};X_0>+w63hOz6-y-+Z-3F45?8 zzW+1j*jV0)xeo#Wkf3JZ8P`{vMBI;@Z~+WJ$zK{-j!v^x-w)>(AEd36TE-{tdU=$a zfe4!_US>K=M5GQNAaCJb{;=O=`r&6|MDW+l8rlt3PPv4_Gl25UD{@OF97@K6r3QHB z430@=mZ~PBX!Q1|ANLjRr)g^EcY-=%wF1c6+6AGmNmY=O4Q!e3#ZlY&L_3HsdEUIZ zXAXGrZ66He-IgOu$PBi2!Zq=5+;J;OUp_QM!FB$>ZiA!rAYWeN6XE}~#GYS5AV=9U?a+>EWrh+qKH z3fMltS&=_$t5>B@eE8ht-M~GP{E*jn0S0U+%Un&Bq$gkI}Qe#<67^ctg>UzbnuxS{9(Td9)bIVU$8F7$)w#|<#CtJmm8@AXiG7NR;-d5jh}GO77XpNK_Uo{5pGYt-1OrZ=@Et2cB)Q=5pv}Rlrk%{zQc8R-;GLA z(v2+IN{~J>k690OLS%Y2%(a|ZQ?CZ{!kT=^mlf^TsKBi~$MS>5l&GLjwSz4Q%PppS zxublX?>U2-C8S*d(a7WgAQlZLdA zJIvBj?26-?z;PStXpi!bUS*P{_o`CDIYe+MJBp*$t?wL>_o$H_FW~){!pzJgpW0{D z9hZ}F!!1lVuDGQmx<4p$!L`orj?9XDbYzRYp;+-erY(7s;t08ozW)AWl*6<8DN=-* z2)XKLbA0z;j#v9LluSm1_9v%04L=2aoy9KLbgADhF}3;0`bJu)$Pepnh2(zONUwRk z5Iql<2)PVk)v?cmygWr`g6G4S`#Gh8rf=$fK0U*Z&cIFr}}55YHi{@SHIjb z*yg-CV}zy`5r$Sp&Q535h;cldbe9@W2S>CVv~(Sf+yQZhD6HBx#;B(+Jvph1F?q%) zhYHWRcniX5NA&oKu3i+u2=s`MN{FCIoT{dq&&O(Om(2_aKlQ3C7W00?^xK>pC#Sbr zqgYBp{-zxED>I+I?QA&mCyt}NZ~}KK_+Ft+ERReFY@Fv2_Q#INkP4vqD`uygk zIoj)kwgF@cnnX6_A=?y!_FHHF$adnKIX;ux+r3y>J$x611WX* zc9C@3RO|89F;0noHf-gKP2=lw(3f&;xfNSg|Fp%@at^ly!;1AxxFD^$%~{Y9EQw_CWlWM_6vKTa_Fnn z8<m1(B@^M6cUMBuKp{LvZ1OX|<$A;LU&3VvZ`M!J!KnF$&F(<6 zDF?#(ILesY8G%hohwRY5M(* z;!oqXKb3%&urXFjVQKZK$YQQ~A9A6cA#V(-a3YcKDkeX-j`X}Abh?^^Ub}57$+R!J zEa%4!re&sOW$sjvM8A9b9x53U9tqBF5s9KWdl6}A_y{)CaC5wSsq*P{Ieh}Fhz>$9 ztw)H}Wubb9a=_kva56{c5g6zAAc@6t$(3yDzT)FtC96kHPZ>VwR=x?!1g&a=KZYE%`=3=>ag06)epfzP|n zYJ%e8!IVb8Sli1L{94tu2Ul;Fwwi!^0jW5qwALCfp4uewRvbm85J`l)gt$?-dn9mq z8J`69FpS=83Dc_N%rrnd%_Y7jr(`f8DmqopI%HG>n6W3%j64f?smwqY*cbH8&)iSh zJTh8Jyu^~PyZ&cBsHH^#GXOIzFMC*oSse<4=?Dk*GX?or0ju9RM+7PQmv+-m(D<21 z?p88&H4+eVnNx!+U&aSl=?P}wfUQLXxwA)~7qaER;;a0N{;6Es( zPbGmZKcpx?LS7AEGU7$9f8#TktL^Le7TC)D*bjL$`(r$usv@}Rt&xX~Y*EkFM(LqB zbzJ`E@VUSuevM@K{x32yU$Vw943GM2-Wc|oj__aFp3wrYEv*iIjSZ#1t>^TLMt59r z)q1-y2dL^qi$9b?%p)(7RNFhOTz>kEas654*cX}68w*GH$o_!bqI`UR9`5j^)fJif zX?K4bE_j{CLK%#J_6Erzs&H$F8+JlpsGGi;)9X2#GjBOmm4b3kjPnJAGb&SE@Okd= zTHy_tf~h#gJ0t9Pad8osT|UU8O}CfVR^1@^ALL8zzI*oOWQiOnep~9E47((5QAdWz8}^|%7AEltlD1mt zHitPt3!bLt-Zt;vwaq$e(7-qLp5_1K9YVwS!NcmuYAAx#xb+QawMg)62+K~V93kyUwZ*{qSKpoHZ~jZNzFF5qd*FH z8p*uB`mpslh?h=x(I+>6a0ULz`u~dP78sE-2$?VbfC`wB3i#Vw7LjY45K*G0tBJ+v2|>u5vU z7PsdzfrG&vUw2<$XF4O38W{;VrTGFJ*cu8CUQQTvA=SXZI=8@7p`TZC+*r+u~) zI?4DOOvpspf1?LvO>Dkh%Ogfid>l?s)@U6*G+5J_(MTMQ&)q*kU7*G;5cgWEq;UXR z7Q2hb2{)ON@0PZITzmiCCj-K2ZQTIRME)rgB!`;*vrKsUN&qd17~a!fq#!lN3~UZcJwb34E4n>kVzQf?3J&-NU4$t93hhuTfLvM|*oxM_tjt+22YZ z{A~Vsg!uXG#OFon#GO@rN7BjA>u##tLu`Hy$?_)FsuX!-Cgjl@v-x%8#kY~SDFmTr zvHr(=#`_d;sKxacYJBlxwU75QADWyyxrL!z((Q4o_j?C5{aj?(=1E}-Z-_|WY-GQ! ztqj#jSOz=yBLVQJV4}2h(SOktCFxccV8C2XUao{78HwzSa7QJdILrkLY^EyPF|DSS{1ASZR%bccqnV~N#{TxKY{<`Ln=OSvFX4}>UTQa>|j{r$ierM z@o7aI=mYolFg@qo)esHAsNK!^#s(@eRH6oV>1Eo-B{zZX2U>!3r(hEE#p&E!7Bu1H z`E3d>;G!(dF85tT!)$blc3}c# z*kMgY2MXLVP}K=n<;Sc8;)!V9v0UwwI!FDY6P6&RE1gfoDu|@FWj^HG=gwj%068zp zjASyAWye>3L4{mzCZ`R)&)^CdD#{xPb;1%X?M!>3j-A1IEAdYiHM4|h9m}vr^_!fe{M03-VSPGEUSeC)qnLsOSIc-5-(3U)9mAxNZu^(#k;W$l$aTe z&<`OW1lI6(qBI|aY}a`7mV&4Q1H#zZE9R2$AJBF)1;I7cP4^pTac)FtM%Z$hLNw_* z(pv*mo4_9EVSc_>`5hgshMhN7m*HHucZ-HUI6H}fZ@r7`Otx0>9D5a_^Zxm#T7K$U z!*}eE*05Yrfd#lo1*_WQII~Pva8Y<;?7MXX8&{Z!As*rcO??E}D{WtgUR}$9{rO`p zU$Q^iRjDDh`417pqb*)(xp@fr^tM{z3l=Z`bh_Pi#;{S9W>KMKN;7-HOlSP@!(ju) ztBy!1OkuswM42999ix+lT|f9a(VMfUZ;s&uQQa#I`j*3aODs?SP%=2`UC!EE3RbK3 z1W}xm-C7^RW;!$b~gZaR*7x%t<7fMc{~W3uU#60IxqNjf? zoPO*6?8tnW@kG|>i1v_5aPbAQWfx!1gy`SzdVa^8DurKiZ=NWY91tN;NhPh>@2)Nv z*W0r`Z{UTfwzLt-M7d^a_`D6d{_CnoLJ8=3ke>EXAYFw*5{Preb#+b+7zCXQH3L=@ z3Txg#Hx38L8s~XY-p;|WO2ZsT3xrRvzYZ=wnYSin-+9dYx1M2`wBHF(-+t+HgWQ2Q z?3zdz*NNR|dbsreD^FrG{cRWf>rvysm4bb1y^!|$)8369;z8*GF-iIIL?W+!tPlni z|87~Odbi8kPZrZR@6GP~<)AO1tGcw5VEDDJHKw=ptGa7*mdEutbQG0$iMHJV0H2)a zOU84}Q&Ya|@hpbtmv@rHU->`NB-UPQ(T@+pclw3_hrG8n=e7{wh_j7$q0oBEAm_iV zUrg6qU6&Ob`1ts=!`LVMn?MxK0=KCTEEnDOC+rylM)t|JRnjAfYGbzRlB$MP4M$glUv@QV!SM1}F``Zlv~; z^Ipp(W4&QkH@=M>@u5A`C&3s2cYJMWd3~9h=n&-Qem4N9g3z`gDW`sJ9C@wARwt$V zT?v7xu{djCNC%hNZG-;zUgE4b5`j9&!QpJ^>sPTQo>|~J!rL=|N{0{At&aNVQ(qG$ VCkaPPG{z3yor5$KD&$_h|2LwJGA954 literal 0 HcmV?d00001 diff --git a/redmine/public/manual/images/workflow.png b/redmine/public/manual/images/workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..26071cadea608cf0bc95ca302d293c911e14a267 GIT binary patch literal 4967 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 literal 0 HcmV?d00001 diff --git a/redmine/public/manual/index.html b/redmine/public/manual/index.html new file mode 100644 index 000000000..3f0cb1e6f --- /dev/null +++ b/redmine/public/manual/index.html @@ -0,0 +1,64 @@ + + + +redMine - Aide en ligne + + + + + + + +

    Aide

    + +

    Documentation en ligne redMine

    + +Sommaire: + + + + \ No newline at end of file diff --git a/redmine/public/manual/projects.html b/redmine/public/manual/projects.html new file mode 100644 index 000000000..13922f69d --- /dev/null +++ b/redmine/public/manual/projects.html @@ -0,0 +1,109 @@ + + + +redMine - Aide en ligne + + + + + + + +

    [ Index ]

    +

    Projets

    +Sommaire: +
      +
    1. Aperçu
    2. +
    3. Demandes +
        +
      • Liste des demandes
      • +
      • Nouvelle demande
      • +
      • Changer le statut d'une demande
      • +
      +
    4. +
    5. Rapports
    6. +
    7. Annonces
    8. +
    9. Historique
    10. +
    11. Documents
    12. +
    13. Membres
    14. +
    15. Fichiers
    16. +
    17. Configuration
    18. +
        +
      • Projet
      • +
      • Membres
      • +
      • Versions
      • +
      • Catégories de demande
      • +
      +
    + + +

    1. Aperçu

    +

    L'aperçu vous présente les informations générales relatives au projet, les principaux membres, les dernières annonces, ainsi qu'une synthèse du nombre de demandes ouvertes par tracker. + + +

    2. Demandes

    +

    2.1 Liste des demandes

    +Par défaut, l'ensemble des demandes sont affichées. Vous pouvez utiliser les différents filtres pour limiter l'affichage à certaines demandes seulement.
    +Lorsque vous appliquez un filtre, il reste en place durant toute votre session. Vous pouvez le redéfinir, ou le supprimer en cliquant sur Annuler. +

    +Liste des demandes
    + +

    2.2 Nouvelle demande

    +

    TODO

    + +

    2.3 Changer le statut d'une demande

    +

    TODO

    + + +

    3. Rapports

    +

    Synthèse du nombre de demandes par statut et selon différents critères (tracker, priorité, catégorie). +Des liens directs permettent d'accéder à la liste détaillée des demandes pour chaque critère.

    + + +

    4. Annonces

    +

    Les nouvelles vous permettent d'informer les utilisateurs sur l'activité du projet.

    + + +

    5. Historique

    +

    Cette page présente l'ensemble des demandes résolues dans chacune des versions du projet. +Certains types de demande peuvent être exclus de cet affichage (voir Trackers).

    + + +

    6. Documents

    +

    Les documents sont groupés par catégories (voir Listes de valeurs).
    +Un document peut contenir plusieurs fichiers (exemple: révisions ou versions successives)

    + + +

    7. Membres

    +

    Affichage de l'ensemble des membres du projet, par rôle

    + + +

    8. Fichiers

    +

    Ce module vous permet de publier les fichiers de l'application (sources, binaires, ...) pour chaque version de l'application .

    + +

    9. Configuration

    +

    9.1 Projet

    +
      +
    • Public: si le projet est public, il sera visible (consultation des demandes, des documents, ...) pour l'ensemble des utilisateurs, y compris ceux qui ne sont pas membres du projet.
      + Si le projet n'est pas public, seuls les membres du projet y ont accès, en fonction de leur rôle.
    • +
    • Champs personnalisés: sélectionner les champs personnalisés que vous souhaitez utiliser au sein du projet.
      + Seul l'administrateur peut ajouter de nouveaux champs personnalisés.
    • +
    +

    9.2 Membres

    +

    Cette section vous permet de définir les membres du projet ainsi que leurs rôles respectifs.
    +Un utilisateur ne peut avoir qu'un rôle au sein d'un projet donné. Le rôle d'un membre détermine les permissions dont il bénéficie sur le projet.

    + +

    9.3 Versions

    +

    Les versions vous permettent de suivre les changements survenus tout au long du projet. +A la fermeture d'une demande, vous pouvez indiquer quelle version la prend en compte.
    +Vous pouvez par ailleurs publier les différentes versions de l'application (voir Fichiers). +

    + + +

    9.4 Catégories de demande

    +

    Les catégories de demande vous permettent de typer les demandes. Les catégories peuvent par exemple correspondre aux modules de l'application.

    + + + + + diff --git a/redmine/public/manual/stylesheets/help.css b/redmine/public/manual/stylesheets/help.css new file mode 100644 index 000000000..f67bc72bf --- /dev/null +++ b/redmine/public/manual/stylesheets/help.css @@ -0,0 +1,70 @@ +/* andreas08 - an open source xhtml/css website layout by Andreas Viklund - http://andreasviklund.com . Free to use in any way and for any purpose as long as the proper credits are given to the original designer. Version: 1.0, November 28, 2005 */ + +/**************** Body and tag styles ****************/ + + + +body{ +font:76% Verdana,Tahoma,Arial,sans-serif; +line-height:1.4em; +color:#303030; +margin: 20px; +} + +a{ +color:#467aa7; +font-weight:bold; +text-decoration:none; +background-color:inherit; +} + +a:hover{color:#2a5a8a; text-decoration:none; background-color:inherit;} +a img{border:none;} + +p{padding:0 0 0.2em 0;} +p form{margin-top:0; margin-bottom:20px;} + +h1 { +display:block; + +font-size:1.7em; +font-weight:normal; +letter-spacing:-1px; +color:#505050; +background-color:inherit; +} + +h2 { +display:block; +margin: 30px 0 0 0; +font-size:1.5em; +font-weight:normal; +letter-spacing:-1px; +color:#505050; +background-color:inherit; +} + +hr { border:0px; border-bottom:1px dashed #000000; } + + +/**************** Misc classes and styles ****************/ + +.splitcontentleft{float:left; width:49%;} +.splitcontentright{float:right; width:49%;} +.clear{clear:both;} +.small{font-size:0.8em;line-height:1.4em;padding:0 0 0 0;} +.hide{display:none;} +.textcenter{text-align:center;} +.textright{text-align:right;} +.important{color:#f02025; background-color:inherit; } + +.box{ +margin:0 0 20px 0; +padding:10px; +border:1px solid #c0c0c0; +background-color:#fafbfc; +color:#505050; +line-height:1.5em; +} + + diff --git a/redmine/public/robots.txt b/redmine/public/robots.txt new file mode 100644 index 000000000..4ab9e89fe --- /dev/null +++ b/redmine/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file \ No newline at end of file diff --git a/redmine/public/stylesheets/application.css b/redmine/public/stylesheets/application.css new file mode 100644 index 000000000..ac6610520 --- /dev/null +++ b/redmine/public/stylesheets/application.css @@ -0,0 +1,322 @@ +/* andreas08 - an open source xhtml/css website layout by Andreas Viklund - http://andreasviklund.com . Free to use in any way and for any purpose as long as the proper credits are given to the original designer. Version: 1.0, November 28, 2005 */ +/* Edited by Jean-Philippe Lang *> +/**************** Body and tag styles ****************/ + + +#header * {margin:0; padding:0;} +p, ul, ol, li {margin:0; padding:0;} + + +body{ +font:76% Verdana,Tahoma,Arial,sans-serif; +line-height:1.4em; +text-align:center; +color:#303030; +background:#e8eaec; +} + +a{ +color:#467aa7; +font-weight:bold; +text-decoration:none; +background-color:inherit; +} + +a:hover{color:#2a5a8a; text-decoration:none; background-color:inherit;} +a img{border:none;} + +p{padding:0 0 1em 0;} +p form{margin-top:0; margin-bottom:20px;} + +img.left,img.center,img.right{padding:4px; border:1px solid #a0a0a0;} +img.left{float:left; margin:0 12px 5px 0;} +img.center{display:block; margin:0 auto 5px auto;} +img.right{float:right; margin:0 0 5px 12px;} + +/**************** Header and navigation styles ****************/ + +#container{ +width:100%; +min-width: 800px; +margin:5px auto; +padding:1px 0; +text-align:left; +background:#ffffff; +color:#303030; +border:2px solid #a0a0a0; +} + +#header{ +height:5.5em; +/*width:758px;*/ +margin:0 1px 1px 1px; +background:#467aa7; +color:#ffffff; +} + +#header h1{ +padding:14px 0 0 20px; +font-size:2.4em; +background-color:inherit; +color:#fff; /*rgb(152, 26, 33);*/ +letter-spacing:-2px; +font-weight:normal; +} + +#header h2{ +margin:10px 0 0 40px; +font-size:1.4em; +background-color:inherit; +color:#f0f2f4; +letter-spacing:-1px; +font-weight:normal; +} + +#navigation{ +height:2.2em; +line-height:2.2em; +/*width:758px;*/ +margin:0 1px; +background:#578bb8; +color:#ffffff; +} + +#navigation li{ +float:left; +list-style-type:none; +border-right:1px solid #ffffff; +white-space:nowrap; +} + +#navigation li.right { + float:right; +list-style-type:none; +border-right:0; +border-left:1px solid #ffffff; +white-space:nowrap; +} + +#navigation li a{ +display:block; +padding:0px 10px 0px 22px; +font-size:0.8em; +font-weight:normal; +/*text-transform:uppercase;*/ +text-decoration:none; +background-color:inherit; +color: #ffffff; +} + +* html #navigation a {width:1%;} + +#navigation .selected,#navigation a:hover{ +color:#ffffff; +text-decoration:none; +background-color: #80b0da; +} + +/**************** Icons links *******************/ +.picHome { background: url(../images/home.png) no-repeat 4px 50%; } +.picUser { background: url(../images/user.png) no-repeat 4px 50%; } +.picUserPage { background: url(../images/user_page.png) no-repeat 4px 50%; } +.picAdmin { background: url(../images/admin.png) no-repeat 4px 50%; } +.picProject { background: url(../images/projects.png) no-repeat 4px 50%; } +.picLogout { background: url(../images/logout.png) no-repeat 4px 50%; } +.picHelp { background: url(../images/help.png) no-repeat 4px 50%; } + +/**************** Content styles ****************/ + +#content{ +/*float:right;*/ +/*width:530px;*/ +width: auto; +min-height: 500px; +font-size:0.9em; +padding:20px 10px 10px 20px; +/*position: absolute;*/ +margin: 0 0 0 140px; +border-left: 1px dashed #c0c0c0; + +} + +#content h2{ +display:block; +margin:0 0 16px 0; +font-size:1.7em; +font-weight:normal; +letter-spacing:-1px; +color:#505050; +background-color:inherit; +} + +#content h2 a{font-weight:normal;} +#content h3{margin:0 0 5px 0; font-size:1.4em; letter-spacing:-1px;} +#content a:hover,#subcontent a:hover{text-decoration:underline;} +#content ul,#content ol{margin:0 5px 16px 35px;} +#content dl{margin:0 5px 10px 25px;} +#content dt{font-weight:bold; margin-bottom:5px;} +#content dd{margin:0 0 10px 15px;} + + +/***********************************************/ + +/* +form{ + padding:15px; + margin:0 0 20px 0; + border:1px solid #c0c0c0; + background-color:#CEE1ED; + width:600px; +} +*/ + +form { + display: inline; +} + +.noborder { + border:0px; + background-color:#fff; + width:100%; +} + +input { + vertical-align: top; +} + +input.button-small +{ + font-size: 0.8em; +} + +label { + font-weight: bold; + font-size: 1em; +} + +.required { + color: #bb0000; +} + +table.listTableContent { + /*margin: 2em 2em 2em 0; */ + border:1px solid #c0c0c0; + width:99%; +} + +table.listTableContent td { + margin: 2px; + +} + +tr.ListHead { + background-color:#467aa7; + color:#FFFFFF; + text-align:center; +} + +tr.ListHead a { + color:#FFFFFF; + text-decoration:underline; +} + +tr.ListLine0 { + background-color: #C1E2F7; +} +tr.ListLine1 { + background-color:#CEE1ED; +} + +hr { border:0px; border-bottom:1px dashed #000000; } + + +/**************** Sidebar styles ****************/ + +#subcontent{ +float:left; +clear:both; +width:130px; +padding:20px 20px 10px 5px; +line-height:1.4em; +} + +#subcontent h2{ +display:block; +margin:0 0 15px 0; +font-size:1.6em; +font-weight:normal; +text-align:left; +letter-spacing:-1px; +color:#505050; +background-color:inherit; +} + +#subcontent p{margin:0 0 16px 0; font-size:0.9em;} + +/**************** Menublock styles ****************/ + +.menublock{margin:0 0 20px 8px; font-size:0.9em;} +.menublock li{list-style:none; display:block; padding:1px; margin-bottom:0px;} +.menublock li a{font-weight:bold; text-decoration:none;} +.menublock li a:hover{text-decoration:none;} +.menublock li ul{margin:3px 0 3px 15px; font-size:1em; font-weight:normal;} +.menublock li ul li{margin-bottom:0;} +.menublock li ul a{font-weight:normal;} + +/**************** Searchbar styles ****************/ + +#searchbar{margin:0 0 20px 0;} +#searchbar form fieldset{margin-left:10px; border:0 solid;} + +#searchbar #s{ +height:1.2em; +width:110px; +margin:0 5px 0 0; +border:1px solid #a0a0a0; +} + +#searchbar #searchbutton{ +width:auto; +padding:0 1px; +border:1px solid #808080; +font-size:0.9em; +text-align:center; +} + +/**************** Footer styles ****************/ + +#footer{ +clear:both; +/*width:758px;*/ +padding:5px 0; +margin:0 1px; +font-size:0.9em; +color:#f0f0f0; +background:#467aa7; +} + +#footer p{padding:0; margin:0; text-align:center;} +#footer a{color:#f0f0f0; background-color:inherit; font-weight:bold;} +#footer a:hover{color:#ffffff; background-color:inherit; text-decoration: underline;} + +/**************** Misc classes and styles ****************/ + +.splitcontentleft{float:left; width:49%;} +.splitcontentright{float:right; width:49%;} +.clear{clear:both;} +.small{font-size:0.8em;line-height:1.4em;padding:0 0 0 0;} +.hide{display:none;} +.textcenter{text-align:center;} +.textright{text-align:right;} +.important{color:#f02025; background-color:inherit; font-weight:bold;} + +.box{ +margin:0 0 20px 0; +padding:10px; +border:1px solid #c0c0c0; +background-color:#fafbfc; +color:#505050; +line-height:1.5em; +} + + diff --git a/redmine/public/stylesheets/rails.css b/redmine/public/stylesheets/rails.css new file mode 100644 index 000000000..e2954c9a7 --- /dev/null +++ b/redmine/public/stylesheets/rails.css @@ -0,0 +1,56 @@ + +.fieldWithErrors { + padding: 2px; + background-color: red; + display: table; +} + +#errorExplanation { + width: 400px; + border: 2px solid red; + padding: 7px; + padding-bottom: 12px; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#errorExplanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + background-color: #c00; + color: #fff; +} + +#errorExplanation p { + color: #333; + margin-bottom: 0; + padding: 5px; +} + +#errorExplanation ul li { + font-size: 12px; + list-style: square; +} + +div.uploadStatus { + margin: 5px; +} + +div.progressBar { + margin: 5px; +} + +div.progressBar div.border { + background-color: #fff; + border: 1px solid grey; + width: 100%; +} + +div.progressBar div.background { + background-color: #333; + height: 18px; + width: 0%; +} diff --git a/redmine/script/about b/redmine/script/about new file mode 100644 index 000000000..7b07d46a3 --- /dev/null +++ b/redmine/script/about @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/about' \ No newline at end of file diff --git a/redmine/script/breakpointer b/redmine/script/breakpointer new file mode 100644 index 000000000..64af76edd --- /dev/null +++ b/redmine/script/breakpointer @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/breakpointer' \ No newline at end of file diff --git a/redmine/script/console b/redmine/script/console new file mode 100644 index 000000000..42f28f7d6 --- /dev/null +++ b/redmine/script/console @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/console' \ No newline at end of file diff --git a/redmine/script/destroy b/redmine/script/destroy new file mode 100644 index 000000000..fa0e6fcd0 --- /dev/null +++ b/redmine/script/destroy @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/destroy' \ No newline at end of file diff --git a/redmine/script/generate b/redmine/script/generate new file mode 100644 index 000000000..ef976e09f --- /dev/null +++ b/redmine/script/generate @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/generate' \ No newline at end of file diff --git a/redmine/script/performance/benchmarker b/redmine/script/performance/benchmarker new file mode 100644 index 000000000..c842d35d3 --- /dev/null +++ b/redmine/script/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/benchmarker' diff --git a/redmine/script/performance/profiler b/redmine/script/performance/profiler new file mode 100644 index 000000000..d855ac8b1 --- /dev/null +++ b/redmine/script/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/profiler' diff --git a/redmine/script/plugin b/redmine/script/plugin new file mode 100644 index 000000000..26ca64c06 --- /dev/null +++ b/redmine/script/plugin @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/plugin' \ No newline at end of file diff --git a/redmine/script/process/reaper b/redmine/script/process/reaper new file mode 100644 index 000000000..c77f04535 --- /dev/null +++ b/redmine/script/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/reaper' diff --git a/redmine/script/process/spawner b/redmine/script/process/spawner new file mode 100644 index 000000000..7118f3983 --- /dev/null +++ b/redmine/script/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/redmine/script/process/spinner b/redmine/script/process/spinner new file mode 100644 index 000000000..6816b32ef --- /dev/null +++ b/redmine/script/process/spinner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spinner' diff --git a/redmine/script/runner b/redmine/script/runner new file mode 100644 index 000000000..ccc30f9d2 --- /dev/null +++ b/redmine/script/runner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' \ No newline at end of file diff --git a/redmine/script/server b/redmine/script/server new file mode 100644 index 000000000..dfabcb881 --- /dev/null +++ b/redmine/script/server @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' \ No newline at end of file diff --git a/redmine/test/fixtures/attachments.yml b/redmine/test/fixtures/attachments.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/attachments.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/custom_fields.yml b/redmine/test/fixtures/custom_fields.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/custom_fields.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/documents.yml b/redmine/test/fixtures/documents.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/documents.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/enumerations.yml b/redmine/test/fixtures/enumerations.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/enumerations.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issue_categories.yml b/redmine/test/fixtures/issue_categories.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issue_categories.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issue_custom_fields.yml b/redmine/test/fixtures/issue_custom_fields.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issue_custom_fields.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issue_custom_values.yml b/redmine/test/fixtures/issue_custom_values.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issue_custom_values.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issue_histories.yml b/redmine/test/fixtures/issue_histories.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issue_histories.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issue_statuses.yml b/redmine/test/fixtures/issue_statuses.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issue_statuses.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/issues.yml b/redmine/test/fixtures/issues.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/issues.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/mailer/issue_closed b/redmine/test/fixtures/mailer/issue_closed new file mode 100644 index 000000000..bb5e51d95 --- /dev/null +++ b/redmine/test/fixtures/mailer/issue_closed @@ -0,0 +1,3 @@ +Mailer#issue_closed + +Find me in app/views/mailer/issue_closed.rhtml diff --git a/redmine/test/fixtures/members.yml b/redmine/test/fixtures/members.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/members.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/news.yml b/redmine/test/fixtures/news.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/news.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/permissions.yml b/redmine/test/fixtures/permissions.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/permissions.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/projects.yml b/redmine/test/fixtures/projects.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/projects.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/roles.yml b/redmine/test/fixtures/roles.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/roles.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/trackers.yml b/redmine/test/fixtures/trackers.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/trackers.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/users.yml b/redmine/test/fixtures/users.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/users.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/versions.yml b/redmine/test/fixtures/versions.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/versions.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/fixtures/workflow.yml b/redmine/test/fixtures/workflow.yml new file mode 100644 index 000000000..8794d28ae --- /dev/null +++ b/redmine/test/fixtures/workflow.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/redmine/test/functional/account_controller_test.rb b/redmine/test/functional/account_controller_test.rb new file mode 100644 index 000000000..537eb8ffc --- /dev/null +++ b/redmine/test/functional/account_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'account_controller' + +# Re-raise errors caught by the controller. +class AccountController; def rescue_action(e) raise e end; end + +class AccountControllerTest < Test::Unit::TestCase + def setup + @controller = AccountController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/functional/admin_controller_test.rb b/redmine/test/functional/admin_controller_test.rb new file mode 100644 index 000000000..e44ac9423 --- /dev/null +++ b/redmine/test/functional/admin_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'admin_controller' + +# Re-raise errors caught by the controller. +class AdminController; def rescue_action(e) raise e end; end + +class AdminControllerTest < Test::Unit::TestCase + def setup + @controller = AdminController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/functional/custom_fields_controller_test.rb b/redmine/test/functional/custom_fields_controller_test.rb new file mode 100644 index 000000000..f86e32569 --- /dev/null +++ b/redmine/test/functional/custom_fields_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'custom_fields_controller' + +# Re-raise errors caught by the controller. +class CustomFieldsController; def rescue_action(e) raise e end; end + +class CustomFieldsControllerTest < Test::Unit::TestCase + fixtures :custom_fields + + def setup + @controller = CustomFieldsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:custom_fields) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:custom_field) + assert assigns(:custom_field).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:custom_field) + end + + def test_create + num_custom_fields = CustomField.count + + post :create, :custom_field => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_custom_fields + 1, CustomField.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:custom_field) + assert assigns(:custom_field).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil CustomField.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + CustomField.find(1) + } + end +end diff --git a/redmine/test/functional/documents_controller_test.rb b/redmine/test/functional/documents_controller_test.rb new file mode 100644 index 000000000..c9bd463d3 --- /dev/null +++ b/redmine/test/functional/documents_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'documents_controller' + +# Re-raise errors caught by the controller. +class DocumentsController; def rescue_action(e) raise e end; end + +class DocumentsControllerTest < Test::Unit::TestCase + fixtures :documents + + def setup + @controller = DocumentsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:documents) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:document) + assert assigns(:document).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:document) + end + + def test_create + num_documents = Document.count + + post :create, :document => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_documents + 1, Document.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:document) + assert assigns(:document).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Document.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Document.find(1) + } + end +end diff --git a/redmine/test/functional/enumerations_controller_test.rb b/redmine/test/functional/enumerations_controller_test.rb new file mode 100644 index 000000000..e9f4b7660 --- /dev/null +++ b/redmine/test/functional/enumerations_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'enumerations_controller' + +# Re-raise errors caught by the controller. +class EnumerationsController; def rescue_action(e) raise e end; end + +class EnumerationsControllerTest < Test::Unit::TestCase + fixtures :enumerations + + def setup + @controller = EnumerationsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:enumerations) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:enumeration) + assert assigns(:enumeration).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:enumeration) + end + + def test_create + num_enumerations = Enumeration.count + + post :create, :enumeration => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_enumerations + 1, Enumeration.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:enumeration) + assert assigns(:enumeration).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Enumeration.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Enumeration.find(1) + } + end +end diff --git a/redmine/test/functional/help_controller_test.rb b/redmine/test/functional/help_controller_test.rb new file mode 100644 index 000000000..291838b8f --- /dev/null +++ b/redmine/test/functional/help_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'help_controller' + +# Re-raise errors caught by the controller. +class HelpController; def rescue_action(e) raise e end; end + +class HelpControllerTest < Test::Unit::TestCase + def setup + @controller = HelpController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/functional/issue_categories_controller_test.rb b/redmine/test/functional/issue_categories_controller_test.rb new file mode 100644 index 000000000..4ea4e1c55 --- /dev/null +++ b/redmine/test/functional/issue_categories_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'issue_categories_controller' + +# Re-raise errors caught by the controller. +class IssueCategoriesController; def rescue_action(e) raise e end; end + +class IssueCategoriesControllerTest < Test::Unit::TestCase + fixtures :issue_categories + + def setup + @controller = IssueCategoriesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:issue_categories) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:issue_category) + assert assigns(:issue_category).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:issue_category) + end + + def test_create + num_issue_categories = IssueCategory.count + + post :create, :issue_category => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_issue_categories + 1, IssueCategory.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:issue_category) + assert assigns(:issue_category).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil IssueCategory.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + IssueCategory.find(1) + } + end +end diff --git a/redmine/test/functional/issue_statuses_controller_test.rb b/redmine/test/functional/issue_statuses_controller_test.rb new file mode 100644 index 000000000..17f11ef55 --- /dev/null +++ b/redmine/test/functional/issue_statuses_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'issue_statuses_controller' + +# Re-raise errors caught by the controller. +class IssueStatusesController; def rescue_action(e) raise e end; end + +class IssueStatusesControllerTest < Test::Unit::TestCase + fixtures :issue_statuses + + def setup + @controller = IssueStatusesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:issue_statuses) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:issue_status) + assert assigns(:issue_status).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:issue_status) + end + + def test_create + num_issue_statuses = IssueStatus.count + + post :create, :issue_status => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_issue_statuses + 1, IssueStatus.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:issue_status) + assert assigns(:issue_status).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil IssueStatus.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + IssueStatus.find(1) + } + end +end diff --git a/redmine/test/functional/issues_controller_test.rb b/redmine/test/functional/issues_controller_test.rb new file mode 100644 index 000000000..1be41f86a --- /dev/null +++ b/redmine/test/functional/issues_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'issues_controller' + +# Re-raise errors caught by the controller. +class IssuesController; def rescue_action(e) raise e end; end + +class IssuesControllerTest < Test::Unit::TestCase + fixtures :issues + + def setup + @controller = IssuesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:issues) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:issue) + assert assigns(:issue).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:issue) + end + + def test_create + num_issues = Issue.count + + post :create, :issue => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_issues + 1, Issue.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:issue) + assert assigns(:issue).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Issue.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Issue.find(1) + } + end +end diff --git a/redmine/test/functional/members_controller_test.rb b/redmine/test/functional/members_controller_test.rb new file mode 100644 index 000000000..5f47c358d --- /dev/null +++ b/redmine/test/functional/members_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'members_controller' + +# Re-raise errors caught by the controller. +class MembersController; def rescue_action(e) raise e end; end + +class MembersControllerTest < Test::Unit::TestCase + fixtures :members + + def setup + @controller = MembersController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:members) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:member) + assert assigns(:member).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:member) + end + + def test_create + num_members = Member.count + + post :create, :member => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_members + 1, Member.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:member) + assert assigns(:member).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Member.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Member.find(1) + } + end +end diff --git a/redmine/test/functional/news_controller_test.rb b/redmine/test/functional/news_controller_test.rb new file mode 100644 index 000000000..b360c6cb3 --- /dev/null +++ b/redmine/test/functional/news_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'news_controller' + +# Re-raise errors caught by the controller. +class NewsController; def rescue_action(e) raise e end; end + +class NewsControllerTest < Test::Unit::TestCase + fixtures :news + + def setup + @controller = NewsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:news) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:news) + assert assigns(:news).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:news) + end + + def test_create + num_news = News.count + + post :create, :news => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_news + 1, News.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:news) + assert assigns(:news).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil News.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + News.find(1) + } + end +end diff --git a/redmine/test/functional/projects_controller_test.rb b/redmine/test/functional/projects_controller_test.rb new file mode 100644 index 000000000..8da34ec7e --- /dev/null +++ b/redmine/test/functional/projects_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'projects_controller' + +# Re-raise errors caught by the controller. +class ProjectsController; def rescue_action(e) raise e end; end + +class ProjectsControllerTest < Test::Unit::TestCase + fixtures :projects + + def setup + @controller = ProjectsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:projects) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:project) + assert assigns(:project).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:project) + end + + def test_create + num_projects = Project.count + + post :create, :project => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_projects + 1, Project.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:project) + assert assigns(:project).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Project.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Project.find(1) + } + end +end diff --git a/redmine/test/functional/reports_controller_test.rb b/redmine/test/functional/reports_controller_test.rb new file mode 100644 index 000000000..4b52ffab3 --- /dev/null +++ b/redmine/test/functional/reports_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'reports_controller' + +# Re-raise errors caught by the controller. +class ReportsController; def rescue_action(e) raise e end; end + +class ReportsControllerTest < Test::Unit::TestCase + def setup + @controller = ReportsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/functional/roles_controller_test.rb b/redmine/test/functional/roles_controller_test.rb new file mode 100644 index 000000000..299aef211 --- /dev/null +++ b/redmine/test/functional/roles_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'roles_controller' + +# Re-raise errors caught by the controller. +class RolesController; def rescue_action(e) raise e end; end + +class RolesControllerTest < Test::Unit::TestCase + fixtures :roles + + def setup + @controller = RolesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:roles) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:role) + assert assigns(:role).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:role) + end + + def test_create + num_roles = Role.count + + post :create, :role => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_roles + 1, Role.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:role) + assert assigns(:role).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Role.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Role.find(1) + } + end +end diff --git a/redmine/test/functional/trackers_controller_test.rb b/redmine/test/functional/trackers_controller_test.rb new file mode 100644 index 000000000..75063c6c5 --- /dev/null +++ b/redmine/test/functional/trackers_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'trackers_controller' + +# Re-raise errors caught by the controller. +class TrackersController; def rescue_action(e) raise e end; end + +class TrackersControllerTest < Test::Unit::TestCase + fixtures :trackers + + def setup + @controller = TrackersController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:trackers) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:tracker) + assert assigns(:tracker).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:tracker) + end + + def test_create + num_trackers = Tracker.count + + post :create, :tracker => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_trackers + 1, Tracker.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:tracker) + assert assigns(:tracker).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Tracker.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Tracker.find(1) + } + end +end diff --git a/redmine/test/functional/users_controller_test.rb b/redmine/test/functional/users_controller_test.rb new file mode 100644 index 000000000..f1e22817b --- /dev/null +++ b/redmine/test/functional/users_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'users_controller' + +# Re-raise errors caught by the controller. +class UsersController; def rescue_action(e) raise e end; end + +class UsersControllerTest < Test::Unit::TestCase + fixtures :users + + def setup + @controller = UsersController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:users) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:user) + assert assigns(:user).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:user) + end + + def test_create + num_users = User.count + + post :create, :user => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_users + 1, User.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:user) + assert assigns(:user).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil User.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + User.find(1) + } + end +end diff --git a/redmine/test/functional/versions_controller_test.rb b/redmine/test/functional/versions_controller_test.rb new file mode 100644 index 000000000..85b2ef76c --- /dev/null +++ b/redmine/test/functional/versions_controller_test.rb @@ -0,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'versions_controller' + +# Re-raise errors caught by the controller. +class VersionsController; def rescue_action(e) raise e end; end + +class VersionsControllerTest < Test::Unit::TestCase + fixtures :versions + + def setup + @controller = VersionsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:versions) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:version) + assert assigns(:version).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:version) + end + + def test_create + num_versions = Version.count + + post :create, :version => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_versions + 1, Version.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:version) + assert assigns(:version).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil Version.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + Version.find(1) + } + end +end diff --git a/redmine/test/functional/welcome_controller_test.rb b/redmine/test/functional/welcome_controller_test.rb new file mode 100644 index 000000000..d773945e1 --- /dev/null +++ b/redmine/test/functional/welcome_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'welcome_controller' + +# Re-raise errors caught by the controller. +class WelcomeController; def rescue_action(e) raise e end; end + +class WelcomeControllerTest < Test::Unit::TestCase + def setup + @controller = WelcomeController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/test_helper.rb b/redmine/test/test_helper.rb new file mode 100644 index 000000000..a299c7f6d --- /dev/null +++ b/redmine/test/test_helper.rb @@ -0,0 +1,28 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'test_help' + +class Test::Unit::TestCase + # Transactional fixtures accelerate your tests by wrapping each test method + # in a transaction that's rolled back on completion. This ensures that the + # test database remains unchanged so your fixtures don't have to be reloaded + # between every test method. Fewer database queries means faster tests. + # + # Read Mike Clark's excellent walkthrough at + # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting + # + # Every Active Record database supports transactions except MyISAM tables + # in MySQL. Turn off transactional fixtures in this case; however, if you + # don't care one way or the other, switching from MyISAM to InnoDB tables + # is recommended. + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where otherwise you + # would need people(:david). If you don't want to migrate your existing + # test cases which use the @david style and don't mind the speed hit (each + # instantiated fixtures translates to a database query per test method), + # then set this back to true. + self.use_instantiated_fixtures = false + + # Add more helper methods to be used by all tests here... +end diff --git a/redmine/test/unit/attachment_test.rb b/redmine/test/unit/attachment_test.rb new file mode 100644 index 000000000..6f66833d3 --- /dev/null +++ b/redmine/test/unit/attachment_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class AttachmentTest < Test::Unit::TestCase + fixtures :attachments + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/custom_field_test.rb b/redmine/test/unit/custom_field_test.rb new file mode 100644 index 000000000..886bd517f --- /dev/null +++ b/redmine/test/unit/custom_field_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class CustomFieldTest < Test::Unit::TestCase + fixtures :custom_fields + + # Replace this with your real tests. + def test_truth + assert_kind_of CustomField, custom_fields(:first) + end +end diff --git a/redmine/test/unit/document_test.rb b/redmine/test/unit/document_test.rb new file mode 100644 index 000000000..acd96ddba --- /dev/null +++ b/redmine/test/unit/document_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class DocumentTest < Test::Unit::TestCase + fixtures :documents + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/enumeration_test.rb b/redmine/test/unit/enumeration_test.rb new file mode 100644 index 000000000..ea8c01405 --- /dev/null +++ b/redmine/test/unit/enumeration_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class EnumerationTest < Test::Unit::TestCase + fixtures :enumerations + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/issue_category_test.rb b/redmine/test/unit/issue_category_test.rb new file mode 100644 index 000000000..6f5051be9 --- /dev/null +++ b/redmine/test/unit/issue_category_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueCategoryTest < Test::Unit::TestCase + fixtures :issue_categories + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/issue_custom_field_test.rb b/redmine/test/unit/issue_custom_field_test.rb new file mode 100644 index 000000000..2adee1061 --- /dev/null +++ b/redmine/test/unit/issue_custom_field_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueCustomFieldTest < Test::Unit::TestCase + fixtures :issue_custom_fields + + # Replace this with your real tests. + def test_truth + assert_kind_of IssueCustomField, issue_custom_fields(:first) + end +end diff --git a/redmine/test/unit/issue_custom_value_test.rb b/redmine/test/unit/issue_custom_value_test.rb new file mode 100644 index 000000000..09c0551b6 --- /dev/null +++ b/redmine/test/unit/issue_custom_value_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueCustomValueTest < Test::Unit::TestCase + fixtures :issue_custom_values + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/issue_history_test.rb b/redmine/test/unit/issue_history_test.rb new file mode 100644 index 000000000..3da38e73c --- /dev/null +++ b/redmine/test/unit/issue_history_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueHistoryTest < Test::Unit::TestCase + fixtures :issue_histories + + # Replace this with your real tests. + def test_truth + assert_kind_of IssueHistory, issue_histories(:first) + end +end diff --git a/redmine/test/unit/issue_status_test.rb b/redmine/test/unit/issue_status_test.rb new file mode 100644 index 000000000..8e7c0b97b --- /dev/null +++ b/redmine/test/unit/issue_status_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueStatusTest < Test::Unit::TestCase + fixtures :issue_statuses + + # Replace this with your real tests. + def test_truth + assert_kind_of IssueStatus, issue_statuses(:first) + end +end diff --git a/redmine/test/unit/issue_test.rb b/redmine/test/unit/issue_test.rb new file mode 100644 index 000000000..3b318778e --- /dev/null +++ b/redmine/test/unit/issue_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class IssueTest < Test::Unit::TestCase + fixtures :issues + + # Replace this with your real tests. + def test_truth + assert_kind_of Issue, issues(:first) + end +end diff --git a/redmine/test/unit/mailer_test.rb b/redmine/test/unit/mailer_test.rb new file mode 100644 index 000000000..70615d712 --- /dev/null +++ b/redmine/test/unit/mailer_test.rb @@ -0,0 +1,35 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'mailer' + +class MailerTest < Test::Unit::TestCase + FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures' + CHARSET = "utf-8" + + include ActionMailer::Quoting + + def setup + ActionMailer::Base.delivery_method = :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries = [] + + @expected = TMail::Mail.new + @expected.set_content_type "text", "plain", { "charset" => CHARSET } + end + + def test_issue_closed + @expected.subject = 'Mailer#issue_closed' + @expected.body = read_fixture('issue_closed') + @expected.date = Time.now + + assert_equal @expected.encoded, Mailer.create_issue_closed(@expected.date).encoded + end + + private + def read_fixture(action) + IO.readlines("#{FIXTURES_PATH}/mailer/#{action}") + end + + def encode(subject) + quoted_printable(subject, CHARSET) + end +end diff --git a/redmine/test/unit/member_test.rb b/redmine/test/unit/member_test.rb new file mode 100644 index 000000000..443c096ec --- /dev/null +++ b/redmine/test/unit/member_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class MemberTest < Test::Unit::TestCase + fixtures :members + + # Replace this with your real tests. + def test_truth + assert_kind_of Member, members(:first) + end +end diff --git a/redmine/test/unit/news_test.rb b/redmine/test/unit/news_test.rb new file mode 100644 index 000000000..db9b3ab0d --- /dev/null +++ b/redmine/test/unit/news_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class NewsTest < Test::Unit::TestCase + fixtures :news + + # Replace this with your real tests. + def test_truth + assert_kind_of News, news(:first) + end +end diff --git a/redmine/test/unit/packages_test.rb b/redmine/test/unit/packages_test.rb new file mode 100644 index 000000000..a5ebcdf1b --- /dev/null +++ b/redmine/test/unit/packages_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class PackagesTest < Test::Unit::TestCase + fixtures :packages + + # Replace this with your real tests. + def test_truth + assert_kind_of Packages, packages(:first) + end +end diff --git a/redmine/test/unit/permission_test.rb b/redmine/test/unit/permission_test.rb new file mode 100644 index 000000000..306c2e61c --- /dev/null +++ b/redmine/test/unit/permission_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class PermissionTest < Test::Unit::TestCase + fixtures :permissions + + # Replace this with your real tests. + def test_truth + assert_kind_of Permission, permissions(:first) + end +end diff --git a/redmine/test/unit/project_test.rb b/redmine/test/unit/project_test.rb new file mode 100644 index 000000000..8a9912576 --- /dev/null +++ b/redmine/test/unit/project_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ProjectTest < Test::Unit::TestCase + fixtures :projects + + # Replace this with your real tests. + def test_truth + assert_kind_of Project, projects(:first) + end +end diff --git a/redmine/test/unit/role_test.rb b/redmine/test/unit/role_test.rb new file mode 100644 index 000000000..90565ae80 --- /dev/null +++ b/redmine/test/unit/role_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class RoleTest < Test::Unit::TestCase + fixtures :roles + + # Replace this with your real tests. + def test_truth + assert_kind_of Role, roles(:first) + end +end diff --git a/redmine/test/unit/tracker_test.rb b/redmine/test/unit/tracker_test.rb new file mode 100644 index 000000000..f738f288b --- /dev/null +++ b/redmine/test/unit/tracker_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class TrackerTest < Test::Unit::TestCase + fixtures :trackers + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/redmine/test/unit/user_test.rb b/redmine/test/unit/user_test.rb new file mode 100644 index 000000000..d6a2a2245 --- /dev/null +++ b/redmine/test/unit/user_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class UserTest < Test::Unit::TestCase + fixtures :users + + # Replace this with your real tests. + def test_truth + assert_kind_of User, users(:first) + end +end diff --git a/redmine/test/unit/version_test.rb b/redmine/test/unit/version_test.rb new file mode 100644 index 000000000..91c52d4a0 --- /dev/null +++ b/redmine/test/unit/version_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class VersionTest < Test::Unit::TestCase + fixtures :versions + + # Replace this with your real tests. + def test_truth + assert_kind_of Version, versions(:first) + end +end diff --git a/redmine/test/unit/workflow_test.rb b/redmine/test/unit/workflow_test.rb new file mode 100644 index 000000000..ff88a9763 --- /dev/null +++ b/redmine/test/unit/workflow_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class WorkflowTest < Test::Unit::TestCase + fixtures :workflows + + # Replace this with your real tests. + def test_truth + assert_kind_of Workflow, workflows(:first) + end +end diff --git a/redmine/vendor/plugins/localization/README b/redmine/vendor/plugins/localization/README new file mode 100644 index 000000000..0996906eb --- /dev/null +++ b/redmine/vendor/plugins/localization/README @@ -0,0 +1,85 @@ += Localization Plugin for Rails + +This plugin provides a simple, gettext-like method to +provide localizations. + +== Features + + * Any number of languages or locales + * Simple method to defines singluar/plural translations + * Can use lambdas to provide Ruby-code based dynamic translations + * Customizable for different instances of the application + +== Usage + +If the localization plugin is installed, it is used automatically. + +You need to create a /lang dir in your RAILS_ROOT. + +The recommended way to use it is to create files that are named +like the languages you define in them (but you can put everything in +one big file too.) + +For instance-customizable strings, add overrides in files you +put in /lang/custom. + +=== Simple example: + +Create a file /lang/translations.rb: + + Localization.define('de') do |l| + l.store 'yes', 'Ja' + l.store 'no', 'Nein' + end + + Localization.define('fr') do |l| + l.store 'yes', 'oui' + l.store 'no', 'non' + end + +In your controller or application.rb: + + Localization.lang = 'de' # or 'fr' + +In your view: + + <%=_ 'yes' %> + <%=_ 'no' %> + +Because the _ method is simply an extension to Object, you +can use it anywhere (models/controllers/views/libs). + +=== Extended example: + +Create a file /lang/default.rb with following contents: + + Localization.define do |l| + l.store '(time)', lambda { |t| t.strftime('%I:%M%p') } + end + +Create a file /lang/de_DE.rb with following contents: + + Localization.define('de_DE') do |l| + l.store '%d entries', ['Ein Eintrag', '%d Einträge'] + l.store '(time)', lambda { |t| t.strftime('%H:%M') } + end + +In your controller or application.rb: + + Localization.lang = 'de_DE' + +In your view: + + <%=_ '%d entries', 1 %> # singular variant is chosen + <%=_ '%d entries', 4 %> # plural variant is chosen + <%=_ '(time)', Time.now %> # call the block with a parameter + +== Translation file guesstimation + +You can generate a guesstimation of all strings needed to be +translated in your views by first adding the _('blah') syntax +everywhere and then calling: + + puts Localization.generate_l10n_file + +in the Rails console. \ No newline at end of file diff --git a/redmine/vendor/plugins/localization/init.rb b/redmine/vendor/plugins/localization/init.rb new file mode 100644 index 000000000..72ed7b9ce --- /dev/null +++ b/redmine/vendor/plugins/localization/init.rb @@ -0,0 +1,3 @@ +require "#{directory}/lib/localization.rb" + +Localization.load \ No newline at end of file diff --git a/redmine/vendor/plugins/localization/lib/localization.rb b/redmine/vendor/plugins/localization/lib/localization.rb new file mode 100644 index 000000000..5fe0b1ed6 --- /dev/null +++ b/redmine/vendor/plugins/localization/lib/localization.rb @@ -0,0 +1,57 @@ +# Original Localization plugin for Rails can be found at: +# http://mir.aculo.us/articles/2005/10/03/ruby-on-rails-i18n-revisited +# +# Slightly edited by Jean-Philippe Lang +# - added @@langs and modified self.define method to maintain an array of available +# langages with labels, eg. { 'en' => 'English, 'fr' => 'French } +# - modified self.generate_l10n_file method to retrieve already translated strings +# + +module Localization + mattr_accessor :lang, :langs + + @@l10s = { :default => {} } + @@lang = :default + @@langs = [] + + def self._(string_to_localize, *args) + translated = @@l10s[@@lang][string_to_localize] || string_to_localize + return translated.call(*args).to_s if translated.is_a? Proc + if translated.is_a? Array + translated = if translated.size == 3 + translated[args[0]==0 ? 0 : (args[0]>1 ? 2 : 1)] + else + translated[args[0]>1 ? 1 : 0] + end + end + sprintf translated, *args + end + + def self.define(lang = :default, name = :default) + @@l10s[lang] ||= {} + @@langs << [ name, lang ] + yield @@l10s[lang] + end + + def self.load + Dir.glob("#{RAILS_ROOT}/lang/*.rb"){ |t| require t } + Dir.glob("#{RAILS_ROOT}/lang/custom/*.rb"){ |t| require t } + end + + # Generates a best-estimate l10n file from all views by + # collecting calls to _() -- note: use the generated file only + # as a start (this method is only guesstimating) + def self.generate_l10n_file(lang) + "Localization.define('en_US') do |l|\n" << + Dir.glob("#{RAILS_ROOT}/app/views/**/*.rhtml").collect do |f| + ["# #{f}"] << File.read(f).scan(/<%.*[^\w]_\s*[\(]+[\"\'](.*?)[\"\'][\)]+/) + end.uniq.flatten.collect do |g| + g.starts_with?('#') ? "\n #{g}" : " l.store '#{g}', '#{@@l10s[lang][g]}'" + end.uniq.join("\n") << "\nend" + end + +end + +class Object + def _(*args); Localization._(*args); end +end \ No newline at end of file