From a377069fb2f208213c07e949aa20a36d4cd8cdbf Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sat, 30 Aug 2008 14:41:07 +0000 Subject: Merged trunk r1773. git-svn-id: http://redmine.rubyforge.org/svn/branches/work@1774 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- groups/app/controllers/account_controller.rb | 79 +- groups/app/controllers/admin_controller.rb | 1 - groups/app/controllers/application.rb | 33 +- groups/app/controllers/attachments_controller.rb | 28 +- groups/app/controllers/auth_sources_controller.rb | 1 - groups/app/controllers/boards_controller.rb | 1 - groups/app/controllers/custom_fields_controller.rb | 27 +- groups/app/controllers/documents_controller.rb | 10 - groups/app/controllers/enumerations_controller.rb | 22 +- .../app/controllers/issue_categories_controller.rb | 1 - .../app/controllers/issue_relations_controller.rb | 1 - .../app/controllers/issue_statuses_controller.rb | 1 - groups/app/controllers/issues_controller.rb | 79 +- groups/app/controllers/journals_controller.rb | 1 - groups/app/controllers/mail_handler_controller.rb | 44 + groups/app/controllers/members_controller.rb | 1 - groups/app/controllers/messages_controller.rb | 17 +- groups/app/controllers/my_controller.rb | 5 +- groups/app/controllers/news_controller.rb | 3 +- groups/app/controllers/projects_controller.rb | 137 +- groups/app/controllers/queries_controller.rb | 1 - groups/app/controllers/reports_controller.rb | 1 - groups/app/controllers/repositories_controller.rb | 78 +- groups/app/controllers/roles_controller.rb | 1 - groups/app/controllers/search_controller.rb | 83 +- groups/app/controllers/settings_controller.rb | 4 +- groups/app/controllers/timelog_controller.rb | 23 +- groups/app/controllers/trackers_controller.rb | 1 - groups/app/controllers/users_controller.rb | 21 +- groups/app/controllers/versions_controller.rb | 10 - groups/app/controllers/watchers_controller.rb | 51 +- groups/app/controllers/welcome_controller.rb | 1 - groups/app/controllers/wiki_controller.rb | 30 +- groups/app/controllers/wikis_controller.rb | 1 - groups/app/helpers/application_helper.rb | 98 +- groups/app/helpers/attachments_helper.rb | 4 + groups/app/helpers/custom_fields_helper.rb | 28 +- groups/app/helpers/issues_helper.rb | 18 +- groups/app/helpers/journals_helper.rb | 11 +- groups/app/helpers/mail_handler_helper.rb | 19 + groups/app/helpers/projects_helper.rb | 8 +- groups/app/helpers/repositories_helper.rb | 30 +- groups/app/helpers/search_helper.rb | 27 +- groups/app/helpers/settings_helper.rb | 1 + groups/app/helpers/sort_helper.rb | 2 +- groups/app/helpers/timelog_helper.rb | 17 +- groups/app/helpers/users_helper.rb | 31 +- groups/app/helpers/watchers_helper.rb | 7 +- groups/app/helpers/wiki_helper.rb | 16 + groups/app/models/attachment.rb | 41 +- groups/app/models/auth_source.rb | 5 +- groups/app/models/auth_source_ldap.rb | 5 +- groups/app/models/change.rb | 4 + groups/app/models/changeset.rb | 31 +- groups/app/models/custom_field.rb | 6 +- groups/app/models/custom_value.rb | 5 + groups/app/models/document.rb | 5 +- groups/app/models/enumeration.rb | 38 +- groups/app/models/issue.rb | 39 +- groups/app/models/issue_relation.rb | 2 +- groups/app/models/journal.rb | 15 +- groups/app/models/mail_handler.rb | 141 +- groups/app/models/mailer.rb | 33 + groups/app/models/message.rb | 11 +- groups/app/models/news.rb | 5 +- groups/app/models/project.rb | 48 +- groups/app/models/query.rb | 91 +- groups/app/models/repository.rb | 37 +- groups/app/models/repository/bazaar.rb | 5 + groups/app/models/repository/cvs.rb | 18 +- groups/app/models/repository/darcs.rb | 14 +- groups/app/models/repository/filesystem.rb | 43 + groups/app/models/repository/git.rb | 6 +- groups/app/models/repository/subversion.rb | 15 + groups/app/models/setting.rb | 39 + groups/app/models/time_entry.rb | 16 +- groups/app/models/time_entry_custom_field.rb | 23 + groups/app/models/user.rb | 40 +- groups/app/models/user_preference.rb | 6 +- groups/app/models/watcher.rb | 7 + groups/app/models/wiki.rb | 2 +- groups/app/models/wiki_content.rb | 11 + groups/app/models/wiki_page.rb | 28 +- groups/app/views/account/login.rhtml | 1 + groups/app/views/account/register.rhtml | 8 +- groups/app/views/account/show.rhtml | 10 +- groups/app/views/attachments/_links.rhtml | 2 +- groups/app/views/attachments/diff.rhtml | 15 + groups/app/views/attachments/file.rhtml | 15 + groups/app/views/auth_sources/_form.rhtml | 8 +- groups/app/views/boards/index.rhtml | 2 + groups/app/views/boards/show.rhtml | 4 +- groups/app/views/common/_diff.rhtml | 64 + groups/app/views/common/_file.rhtml | 11 + groups/app/views/common/_preview.rhtml | 2 +- groups/app/views/common/feed.atom.rxml | 10 +- groups/app/views/custom_fields/_form.rhtml | 3 + groups/app/views/enumerations/destroy.rhtml | 12 + groups/app/views/enumerations/list.rhtml | 9 +- groups/app/views/issues/_edit.rhtml | 8 +- groups/app/views/issues/_form.rhtml | 2 +- groups/app/views/issues/_form_custom_fields.rhtml | 15 +- groups/app/views/issues/_history.rhtml | 3 +- groups/app/views/issues/_list.rhtml | 2 +- groups/app/views/issues/_pdf.rfpdf | 118 - groups/app/views/issues/context_menu.rhtml | 64 +- groups/app/views/issues/index.rhtml | 2 +- groups/app/views/issues/show.rfpdf | 118 +- groups/app/views/issues/show.rhtml | 41 +- groups/app/views/layouts/base.rhtml | 2 +- groups/app/views/mailer/layout.text.html.rhtml | 32 +- .../app/views/mailer/lost_password.text.html.rhtml | 2 + .../views/mailer/lost_password.text.plain.rhtml | 2 + groups/app/views/mailer/reminder.text.html.rhtml | 9 + groups/app/views/mailer/reminder.text.plain.rhtml | 7 + groups/app/views/messages/show.rhtml | 10 + groups/app/views/my/blocks/_documents.rhtml | 5 +- groups/app/views/news/edit.rhtml | 2 +- groups/app/views/news/index.rhtml | 2 +- groups/app/views/news/new.rhtml | 2 +- groups/app/views/news/show.rhtml | 6 +- groups/app/views/projects/_form.rhtml | 14 +- groups/app/views/projects/activity.rhtml | 14 +- groups/app/views/projects/calendar.rhtml | 2 +- groups/app/views/projects/gantt.rfpdf | 4 +- groups/app/views/projects/gantt.rhtml | 9 +- groups/app/views/projects/index.rhtml | 31 + groups/app/views/projects/list.rhtml | 25 - groups/app/views/projects/list_files.rhtml | 3 +- groups/app/views/projects/roadmap.rhtml | 8 +- .../app/views/projects/settings/_repository.rhtml | 2 +- groups/app/views/projects/show.rhtml | 4 +- groups/app/views/queries/_filters.rhtml | 2 +- .../app/views/repositories/_dir_list_content.rhtml | 28 +- groups/app/views/repositories/_navigation.rhtml | 4 +- groups/app/views/repositories/_revisions.rhtml | 2 +- groups/app/views/repositories/browse.rhtml | 1 + groups/app/views/repositories/changes.rhtml | 13 +- groups/app/views/repositories/diff.rhtml | 82 +- groups/app/views/repositories/entry.rhtml | 12 +- groups/app/views/repositories/revision.rhtml | 14 +- groups/app/views/repositories/show.rhtml | 7 +- groups/app/views/repositories/stats.rhtml | 15 +- groups/app/views/roles/_form.rhtml | 4 +- groups/app/views/roles/report.rhtml | 18 +- groups/app/views/search/index.rhtml | 30 +- groups/app/views/settings/_mail_handler.rhtml | 18 + groups/app/views/settings/_notifications.rhtml | 10 +- groups/app/views/settings/_repositories.rhtml | 10 + groups/app/views/timelog/_list.rhtml | 4 +- groups/app/views/timelog/_report_criteria.rhtml | 2 +- groups/app/views/timelog/details.rhtml | 5 + groups/app/views/timelog/edit.rhtml | 5 +- groups/app/views/users/_form.rhtml | 9 +- groups/app/views/users/_general.rhtml | 4 + groups/app/views/users/_memberships.rhtml | 53 +- groups/app/views/users/edit.rhtml | 31 +- groups/app/views/users/list.rhtml | 10 +- groups/app/views/versions/show.rhtml | 2 +- groups/app/views/watchers/_watchers.rhtml | 25 + groups/app/views/welcome/index.rhtml | 6 +- groups/app/views/wiki/export.rhtml | 4 + groups/app/views/wiki/history.rhtml | 4 +- groups/app/views/wiki/rename.rhtml | 3 +- groups/app/views/wiki/show.rhtml | 10 +- groups/app/views/wiki/special_page_index.rhtml | 6 +- groups/config/boot.rb | 118 +- groups/config/database.yml.example | 3 + groups/config/email.yml.example | 21 + groups/config/environment.rb | 57 +- groups/config/environments/test.rb | 1 + groups/config/environments/test_pgsql.rb | 3 +- groups/config/environments/test_sqlite3.rb | 3 +- groups/config/initializers/10-patches.rb | 17 + groups/config/initializers/20-mime_types.rb | 4 + groups/config/initializers/30-redmine.rb | 7 + groups/config/initializers/40-email.rb | 17 + groups/config/routes.rb | 5 + groups/config/settings.yml | 18 +- groups/db/migrate/001_setup.rb | 19 +- groups/db/migrate/072_add_enumerations_position.rb | 2 +- .../db/migrate/078_add_custom_fields_position.rb | 2 +- groups/db/migrate/096_add_wiki_pages_protected.rb | 9 + .../migrate/097_change_projects_homepage_limit.rb | 9 + groups/db/migrate/098_add_wiki_pages_parent_id.rb | 9 + groups/doc/CHANGELOG | 80 + groups/doc/INSTALL | 40 +- groups/doc/README_FOR_APP | 5 + groups/doc/RUNNING_TESTS | 8 + groups/doc/UPGRADING | 8 +- groups/extra/mail_handler/rdm-mailhandler.rb | 125 + groups/extra/sample_plugin/app/models/meeting.rb | 11 + .../db/migrate/001_create_meetings.rb | 15 + .../db/migrate/001_create_some_models.rb | 13 - groups/extra/sample_plugin/init.rb | 5 + groups/extra/sample_plugin/lang/en.yml | 1 + groups/extra/sample_plugin/lang/fr.yml | 1 + groups/extra/svn/Redmine.pm | 137 +- groups/lang/bg.yml | 18 + groups/lang/cs.yml | 18 + groups/lang/da.yml | 18 + groups/lang/de.yml | 18 + groups/lang/en.yml | 20 +- groups/lang/es.yml | 18 + groups/lang/fi.yml | 176 +- groups/lang/fr.yml | 22 +- groups/lang/he.yml | 18 + groups/lang/hu.yml | 639 ++++ groups/lang/it.yml | 430 ++- groups/lang/ja.yml | 18 + groups/lang/ko.yml | 18 + groups/lang/lt.yml | 100 +- groups/lang/nl.yml | 18 + groups/lang/no.yml | 22 +- groups/lang/pl.yml | 52 +- groups/lang/pt-br.yml | 814 ++-- groups/lang/pt.yml | 20 +- groups/lang/ro.yml | 20 +- groups/lang/ru.yml | 44 +- groups/lang/sr.yml | 18 + groups/lang/sv.yml | 18 + groups/lang/th.yml | 641 ++++ groups/lang/uk.yml | 18 + groups/lang/zh-tw.yml | 66 +- groups/lang/zh.yml | 32 +- groups/lib/SVG/Graph/Graph.rb | 2 +- groups/lib/redcloth.rb | 56 +- groups/lib/redmine.rb | 35 +- groups/lib/redmine/activity.rb | 46 + groups/lib/redmine/activity/fetcher.rb | 79 + groups/lib/redmine/core_ext/string/conversions.rb | 2 +- groups/lib/redmine/imap.rb | 51 + groups/lib/redmine/menu_manager.rb | 64 +- groups/lib/redmine/platform.rb | 26 + groups/lib/redmine/plugin.rb | 26 + .../lib/redmine/scm/adapters/abstract_adapter.rb | 235 +- groups/lib/redmine/scm/adapters/bazaar_adapter.rb | 4 +- groups/lib/redmine/scm/adapters/cvs_adapter.rb | 16 +- groups/lib/redmine/scm/adapters/darcs_adapter.rb | 43 +- .../lib/redmine/scm/adapters/filesystem_adapter.rb | 93 + groups/lib/redmine/scm/adapters/git_adapter.rb | 47 +- .../scm/adapters/mercurial/hg-template-0.9.5.tmpl | 12 + .../scm/adapters/mercurial/hg-template-1.0.tmpl | 12 + .../lib/redmine/scm/adapters/mercurial_adapter.rb | 174 +- .../lib/redmine/scm/adapters/subversion_adapter.rb | 48 +- groups/lib/redmine/unified_diff.rb | 178 + groups/lib/redmine/wiki_formatting.rb | 46 +- groups/lib/redmine/wiki_formatting/macros.rb | 6 + groups/lib/tabular_form_builder.rb | 2 +- groups/lib/tasks/email.rake | 105 + groups/lib/tasks/migrate_from_trac.rake | 49 +- groups/lib/tasks/reminder.rake | 39 + groups/lib/tasks/testing.rake | 46 + groups/public/help/wiki_syntax.html | 26 +- groups/public/images/bullet_toggle_minus.png | Bin 0 -> 335 bytes groups/public/images/bullet_toggle_plus.png | Bin 0 -> 335 bytes groups/public/images/comment.png | Bin 0 -> 413 bytes groups/public/images/expand.png | Bin 266 -> 0 bytes groups/public/images/jstoolbar/bt_bq.png | Bin 0 -> 503 bytes groups/public/images/jstoolbar/bt_bq_remove.png | Bin 0 -> 501 bytes groups/public/images/locked.png | Bin 566 -> 1127 bytes groups/public/images/projects.png | Bin 690 -> 811 bytes groups/public/images/ticket_note.png | Bin 0 -> 784 bytes groups/public/images/unlock.png | Bin 643 -> 618 bytes groups/public/javascripts/application.js | 32 +- .../javascripts/calendar/lang/calendar-he.js | 2 +- .../javascripts/calendar/lang/calendar-hu.js | 127 + .../javascripts/calendar/lang/calendar-pt-br.js | 34 +- .../javascripts/calendar/lang/calendar-th.js | 127 + .../javascripts/calendar/lang/calendar-zh-tw.js | 12 +- .../javascripts/calendar/lang/calendar-zh.js | 40 +- groups/public/javascripts/context_menu.js | 11 +- groups/public/javascripts/controls.js | 838 +++-- groups/public/javascripts/dragdrop.js | 146 +- groups/public/javascripts/effects.js | 760 ++-- groups/public/javascripts/jstoolbar/jstoolbar.js | 33 +- .../javascripts/jstoolbar/lang/jstoolbar-bg.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-cs.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-da.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-de.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-en.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-es.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-fi.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-fr.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-he.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-hu.js | 16 + .../javascripts/jstoolbar/lang/jstoolbar-it.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-ja.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-ko.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-lt.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-nl.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-no.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-pl.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-pt-br.js | 30 +- .../javascripts/jstoolbar/lang/jstoolbar-pt.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-ro.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-ru.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-sr.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-sv.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-th.js | 16 + .../javascripts/jstoolbar/lang/jstoolbar-uk.js | 2 + .../javascripts/jstoolbar/lang/jstoolbar-zh-tw.js | 4 +- .../javascripts/jstoolbar/lang/jstoolbar-zh.js | 28 +- groups/public/javascripts/prototype.js | 3924 ++++++++++++++------ groups/public/stylesheets/application.css | 81 +- groups/public/stylesheets/context_menu.css | 10 +- groups/public/stylesheets/jstoolbar.css | 6 + groups/public/stylesheets/scm.css | 6 +- groups/script/dbconsole | 3 + groups/script/performance/request | 3 + groups/script/process/inspector | 3 + groups/test/fixtures/attachments.yml | 49 + groups/test/fixtures/changes.yml | 7 + groups/test/fixtures/custom_fields.yml | 13 +- groups/test/fixtures/enabled_modules.yml | 4 + groups/test/fixtures/enumerations.yml | 6 + .../test/fixtures/files/060719210727_archive.zip | Bin 0 -> 157 bytes .../fixtures/files/060719210727_changeset.diff | 13 + groups/test/fixtures/files/060719210727_source.rb | 10 + groups/test/fixtures/issue_categories.yml | 11 + groups/test/fixtures/issues.yml | 26 +- .../fixtures/mail_handler/add_note_to_issue.txt | 14 - .../mail_handler/ticket_on_given_project.eml | 42 + groups/test/fixtures/mail_handler/ticket_reply.eml | 73 + .../mail_handler/ticket_reply_with_status.eml | 75 + .../mail_handler/ticket_with_attachment.eml | 248 ++ .../mail_handler/ticket_with_attributes.eml | 43 + groups/test/fixtures/members.yml | 10 +- groups/test/fixtures/projects.yml | 14 +- groups/test/fixtures/projects_trackers.yml | 4 + .../repositories/filesystem_repository.tar.gz | Bin 0 -> 265 bytes groups/test/fixtures/roles.yml | 6 + groups/test/fixtures/versions.yml | 2 +- groups/test/fixtures/watchers.yml | 6 + groups/test/fixtures/wiki_contents.yml | 2 +- groups/test/fixtures/wiki_pages.yml | 8 + groups/test/functional/account_controller_test.rb | 11 + .../test/functional/attachments_controller_test.rb | 79 + .../test/functional/documents_controller_test.rb | 2 + groups/test/functional/enumerations_controller.rb | 61 + groups/test/functional/issues_controller_test.rb | 176 +- .../functional/mail_handler_controller_test.rb | 53 + groups/test/functional/messages_controller_test.rb | 16 + groups/test/functional/projects_controller_test.rb | 165 +- .../functional/repositories_controller_test.rb | 4 +- .../functional/repositories_cvs_controller_test.rb | 15 +- .../functional/repositories_git_controller_test.rb | 2 +- .../repositories_subversion_controller_test.rb | 53 + groups/test/functional/search_controller_test.rb | 30 +- groups/test/functional/timelog_controller_test.rb | 24 +- groups/test/functional/versions_controller_test.rb | 6 +- groups/test/functional/watchers_controller_test.rb | 70 + groups/test/functional/welcome_controller_test.rb | 14 + groups/test/functional/wiki_controller_test.rb | 99 +- groups/test/integration/account_test.rb | 56 +- groups/test/integration/admin_test.rb | 5 +- groups/test/integration/issues_test.rb | 21 + groups/test/test_helper.rb | 20 +- groups/test/unit/activity_test.rb | 71 + groups/test/unit/attachment_test.rb | 32 + groups/test/unit/changeset_test.rb | 11 + groups/test/unit/default_data_test.rb | 45 + groups/test/unit/enumeration_test.rb | 45 + groups/test/unit/filesystem_adapter_test.rb | 42 + .../test/unit/helpers/application_helper_test.rb | 160 +- groups/test/unit/issue_test.rb | 123 +- groups/test/unit/mail_handler_test.rb | 125 +- groups/test/unit/mailer_test.rb | 11 +- groups/test/unit/mercurial_adapter_test.rb | 53 + groups/test/unit/project_test.rb | 3 +- groups/test/unit/query_test.rb | 101 +- groups/test/unit/repository_cvs_test.rb | 2 +- groups/test/unit/repository_darcs_test.rb | 7 + groups/test/unit/repository_filesystem_test.rb | 54 + groups/test/unit/repository_git_test.rb | 2 +- groups/test/unit/repository_mercurial_test.rb | 20 + groups/test/unit/repository_test.rb | 20 + groups/test/unit/role_test.rb | 2 +- groups/test/unit/search_test.rb | 143 + groups/test/unit/subversion_adapter_test.rb | 33 + groups/test/unit/tracker_test.rb | 2 +- groups/test/unit/user_test.rb | 6 + groups/test/unit/wiki_page_test.rb | 44 + .../plugins/acts_as_event/lib/acts_as_event.rb | 15 +- .../acts_as_searchable/lib/acts_as_searchable.rb | 66 +- .../acts_as_tree/lib/active_record/acts/tree.rb | 7 + groups/vendor/plugins/acts_as_versioned/Rakefile | 362 +- .../acts_as_versioned/lib/acts_as_versioned.rb | 195 +- .../acts_as_versioned/test/abstract_unit.rb | 27 +- .../acts_as_versioned/test/fixtures/widget.rb | 2 +- .../acts_as_versioned/test/migration_test.rb | 22 +- .../acts_as_versioned/test/versioned_test.rb | 172 +- .../acts_as_watchable/lib/acts_as_watchable.rb | 18 +- .../coderay-0.7.6.227/lib/coderay/scanners/c.rb | 2 +- .../plugins/gloc-1.1.0/lib/gloc-rails-text.rb | 4 +- groups/vendor/plugins/gloc-1.1.0/lib/gloc-rails.rb | 2 +- groups/vendor/plugins/rfpdf/init.rb | 8 +- groups/vendor/plugins/rfpdf/lib/rfpdf/chinese.rb | 946 ++--- groups/vendor/plugins/rfpdf/lib/rfpdf/view.rb | 12 +- 399 files changed, 14439 insertions(+), 5386 deletions(-) create mode 100644 groups/app/controllers/mail_handler_controller.rb create mode 100644 groups/app/helpers/mail_handler_helper.rb create mode 100644 groups/app/models/repository/filesystem.rb create mode 100644 groups/app/models/time_entry_custom_field.rb create mode 100644 groups/app/views/attachments/diff.rhtml create mode 100644 groups/app/views/attachments/file.rhtml create mode 100644 groups/app/views/common/_diff.rhtml create mode 100644 groups/app/views/common/_file.rhtml create mode 100644 groups/app/views/enumerations/destroy.rhtml delete mode 100644 groups/app/views/issues/_pdf.rfpdf create mode 100644 groups/app/views/mailer/reminder.text.html.rhtml create mode 100644 groups/app/views/mailer/reminder.text.plain.rhtml create mode 100644 groups/app/views/projects/index.rhtml delete mode 100644 groups/app/views/projects/list.rhtml create mode 100644 groups/app/views/settings/_mail_handler.rhtml create mode 100644 groups/app/views/users/_general.rhtml create mode 100644 groups/app/views/watchers/_watchers.rhtml create mode 100644 groups/config/email.yml.example create mode 100644 groups/config/initializers/10-patches.rb create mode 100644 groups/config/initializers/20-mime_types.rb create mode 100644 groups/config/initializers/30-redmine.rb create mode 100644 groups/config/initializers/40-email.rb create mode 100644 groups/db/migrate/096_add_wiki_pages_protected.rb create mode 100644 groups/db/migrate/097_change_projects_homepage_limit.rb create mode 100644 groups/db/migrate/098_add_wiki_pages_parent_id.rb create mode 100644 groups/doc/README_FOR_APP create mode 100644 groups/extra/mail_handler/rdm-mailhandler.rb create mode 100644 groups/extra/sample_plugin/app/models/meeting.rb create mode 100644 groups/extra/sample_plugin/db/migrate/001_create_meetings.rb delete mode 100644 groups/extra/sample_plugin/db/migrate/001_create_some_models.rb create mode 100644 groups/lang/hu.yml create mode 100644 groups/lang/th.yml create mode 100644 groups/lib/redmine/activity.rb create mode 100644 groups/lib/redmine/activity/fetcher.rb create mode 100644 groups/lib/redmine/imap.rb create mode 100644 groups/lib/redmine/platform.rb create mode 100644 groups/lib/redmine/scm/adapters/filesystem_adapter.rb create mode 100644 groups/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl create mode 100644 groups/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl create mode 100644 groups/lib/redmine/unified_diff.rb create mode 100644 groups/lib/tasks/email.rake create mode 100644 groups/lib/tasks/reminder.rake create mode 100644 groups/lib/tasks/testing.rake create mode 100644 groups/public/images/bullet_toggle_minus.png create mode 100644 groups/public/images/bullet_toggle_plus.png create mode 100644 groups/public/images/comment.png delete mode 100644 groups/public/images/expand.png create mode 100644 groups/public/images/jstoolbar/bt_bq.png create mode 100644 groups/public/images/jstoolbar/bt_bq_remove.png create mode 100644 groups/public/images/ticket_note.png create mode 100644 groups/public/javascripts/calendar/lang/calendar-hu.js create mode 100644 groups/public/javascripts/calendar/lang/calendar-th.js create mode 100644 groups/public/javascripts/jstoolbar/lang/jstoolbar-hu.js create mode 100644 groups/public/javascripts/jstoolbar/lang/jstoolbar-th.js create mode 100644 groups/script/dbconsole create mode 100644 groups/script/performance/request create mode 100644 groups/script/process/inspector create mode 100644 groups/test/fixtures/files/060719210727_archive.zip create mode 100644 groups/test/fixtures/files/060719210727_changeset.diff create mode 100644 groups/test/fixtures/files/060719210727_source.rb delete mode 100644 groups/test/fixtures/mail_handler/add_note_to_issue.txt create mode 100644 groups/test/fixtures/mail_handler/ticket_on_given_project.eml create mode 100644 groups/test/fixtures/mail_handler/ticket_reply.eml create mode 100644 groups/test/fixtures/mail_handler/ticket_reply_with_status.eml create mode 100644 groups/test/fixtures/mail_handler/ticket_with_attachment.eml create mode 100644 groups/test/fixtures/mail_handler/ticket_with_attributes.eml create mode 100644 groups/test/fixtures/repositories/filesystem_repository.tar.gz create mode 100644 groups/test/fixtures/watchers.yml create mode 100644 groups/test/functional/attachments_controller_test.rb create mode 100644 groups/test/functional/enumerations_controller.rb create mode 100644 groups/test/functional/mail_handler_controller_test.rb create mode 100644 groups/test/functional/watchers_controller_test.rb create mode 100644 groups/test/unit/activity_test.rb create mode 100644 groups/test/unit/attachment_test.rb create mode 100644 groups/test/unit/default_data_test.rb create mode 100644 groups/test/unit/enumeration_test.rb create mode 100644 groups/test/unit/filesystem_adapter_test.rb create mode 100644 groups/test/unit/mercurial_adapter_test.rb create mode 100644 groups/test/unit/repository_filesystem_test.rb create mode 100644 groups/test/unit/search_test.rb create mode 100644 groups/test/unit/subversion_adapter_test.rb diff --git a/groups/app/controllers/account_controller.rb b/groups/app/controllers/account_controller.rb index b9224c158..4b2ec8317 100644 --- a/groups/app/controllers/account_controller.rb +++ b/groups/app/controllers/account_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class AccountController < ApplicationController - layout 'base' helper :custom_fields include CustomFieldsHelper @@ -26,7 +25,7 @@ class AccountController < ApplicationController # Show user's account def show @user = User.find_active(params[:id]) - @custom_values = @user.custom_values.find(:all, :include => :custom_field) + @custom_values = @user.custom_values # show only public projects and private projects that the logged in user is also a member of @memberships = @user.memberships.select do |membership| @@ -44,7 +43,16 @@ class AccountController < ApplicationController else # Authenticate user user = User.try_to_login(params[:username], params[:password]) - if user + if user.nil? + # Invalid credentials + flash.now[:error] = l(:notice_account_invalid_creditentials) + elsif user.new_record? + # Onthefly creation failed, display the registration form to fill/fix attributes + @user = user + session[:auth_source_registration] = {:login => user.login, :auth_source_id => user.auth_source_id } + render :action => 'register' + else + # Valid user self.logged_user = user # generate a key and set cookie if autologin if params[:autologin] && Setting.autologin? @@ -52,12 +60,8 @@ class AccountController < ApplicationController cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now } end redirect_back_or_default :controller => 'my', :action => 'page' - else - flash.now[:error] = l(:notice_account_invalid_creditentials) end end - rescue User::OnTheFlyCreationFailure - flash.now[:error] = 'Redmine could not retrieve the required information from the LDAP to create your account. Please, contact your Redmine administrator.' end # Log out current user and redirect to welcome page @@ -107,43 +111,52 @@ class AccountController < ApplicationController # User self-registration def register - redirect_to(home_url) && return unless Setting.self_registration? + redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration] if request.get? + session[:auth_source_registration] = nil @user = User.new(:language => Setting.default_language) - @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) } else @user = User.new(params[:user]) @user.admin = false - @user.login = params[:user][:login] @user.status = User::STATUS_REGISTERED - @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] - @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, - :customized => @user, - :value => (params["custom_fields"] ? params["custom_fields"][x.id.to_s] : nil)) } - @user.custom_values = @custom_values - case Setting.self_registration - when '1' - # Email activation - token = Token.new(:user => @user, :action => "register") - if @user.save and token.save - Mailer.deliver_register(token) - flash[:notice] = l(:notice_account_register_done) - redirect_to :action => 'login' - end - when '3' - # Automatic activation + if session[:auth_source_registration] @user.status = User::STATUS_ACTIVE + @user.login = session[:auth_source_registration][:login] + @user.auth_source_id = session[:auth_source_registration][:auth_source_id] if @user.save + session[:auth_source_registration] = nil + self.logged_user = @user flash[:notice] = l(:notice_account_activated) - redirect_to :action => 'login' + redirect_to :controller => 'my', :action => 'account' end else - # Manual activation by the administrator - if @user.save - # Sends an email to the administrators - Mailer.deliver_account_activation_request(@user) - flash[:notice] = l(:notice_account_pending) - redirect_to :action => 'login' + @user.login = params[:user][:login] + @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] + case Setting.self_registration + when '1' + # Email activation + token = Token.new(:user => @user, :action => "register") + if @user.save and token.save + Mailer.deliver_register(token) + flash[:notice] = l(:notice_account_register_done) + redirect_to :action => 'login' + end + when '3' + # Automatic activation + @user.status = User::STATUS_ACTIVE + if @user.save + self.logged_user = @user + flash[:notice] = l(:notice_account_activated) + redirect_to :controller => 'my', :action => 'account' + end + else + # Manual activation by the administrator + if @user.save + # Sends an email to the administrators + Mailer.deliver_account_activation_request(@user) + flash[:notice] = l(:notice_account_pending) + redirect_to :action => 'login' + end end end end diff --git a/groups/app/controllers/admin_controller.rb b/groups/app/controllers/admin_controller.rb index e002f3a27..a6df49dcd 100644 --- a/groups/app/controllers/admin_controller.rb +++ b/groups/app/controllers/admin_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class AdminController < ApplicationController - layout 'base' before_filter :require_admin helper :sort diff --git a/groups/app/controllers/application.rb b/groups/app/controllers/application.rb index abf621641..7a56e61f0 100644 --- a/groups/app/controllers/application.rb +++ b/groups/app/controllers/application.rb @@ -15,7 +15,11 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +require 'uri' + class ApplicationController < ActionController::Base + layout 'base' + before_filter :user_setup, :check_if_login_required, :set_localization filter_parameter_logging :password @@ -61,11 +65,11 @@ class ApplicationController < ActionController::Base def set_localization User.current.language = nil unless User.current.logged? lang = begin - if !User.current.language.blank? and GLoc.valid_languages.include? User.current.language.to_sym + if !User.current.language.blank? && GLoc.valid_language?(User.current.language) User.current.language elsif request.env['HTTP_ACCEPT_LANGUAGE'] - accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first - if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym + accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase + if !accept_lang.blank? && (GLoc.valid_language?(accept_lang) || GLoc.valid_language?(accept_lang = accept_lang.split('-').first)) User.current.language = accept_lang end end @@ -77,8 +81,7 @@ class ApplicationController < ActionController::Base def require_login if !User.current.logged? - store_location - redirect_to :controller => "account", :action => "login" + redirect_to :controller => "account", :action => "login", :back_url => request.request_uri return false end true @@ -115,20 +118,16 @@ class ApplicationController < ActionController::Base end end - # store current uri in session. - # return to this location by calling redirect_back_or_default - def store_location - session[:return_to_params] = params - end - - # move to the last store_location call or to the passed default one def redirect_back_or_default(default) - if session[:return_to_params].nil? - redirect_to default - else - redirect_to session[:return_to_params] - session[:return_to_params] = nil + back_url = params[:back_url] + if !back_url.blank? + uri = URI.parse(back_url) + # do not redirect user to another host + if uri.relative? || (uri.host == request.host) + redirect_to(back_url) and return + end end + redirect_to default end def render_403 diff --git a/groups/app/controllers/attachments_controller.rb b/groups/app/controllers/attachments_controller.rb index 4e87e5442..788bab94d 100644 --- a/groups/app/controllers/attachments_controller.rb +++ b/groups/app/controllers/attachments_controller.rb @@ -16,24 +16,40 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class AttachmentsController < ApplicationController - layout 'base' - before_filter :find_project, :check_project_privacy + before_filter :find_project + def show + if @attachment.is_diff? + @diff = File.new(@attachment.diskfile, "rb").read + render :action => 'diff' + elsif @attachment.is_text? + @content = File.new(@attachment.diskfile, "rb").read + render :action => 'file' + elsif + download + end + end + def download + @attachment.increment_download if @attachment.container.is_a?(Version) + # images are sent inline send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename), :type => @attachment.content_type, :disposition => (@attachment.image? ? 'inline' : 'attachment') - rescue - # in case the disk file was deleted - render_404 end private def find_project @attachment = Attachment.find(params[:id]) + # Show 404 if the filename in the url is wrong + raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename + @project = @attachment.project - rescue + permission = @attachment.container.is_a?(Version) ? :view_files : "view_#{@attachment.container.class.name.underscore.pluralize}".to_sym + allowed = User.current.allowed_to?(permission, @project) + allowed ? true : (User.current.logged? ? render_403 : require_login) + rescue ActiveRecord::RecordNotFound render_404 end end diff --git a/groups/app/controllers/auth_sources_controller.rb b/groups/app/controllers/auth_sources_controller.rb index b830f1970..981f29f03 100644 --- a/groups/app/controllers/auth_sources_controller.rb +++ b/groups/app/controllers/auth_sources_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class AuthSourcesController < ApplicationController - layout 'base' before_filter :require_admin def index diff --git a/groups/app/controllers/boards_controller.rb b/groups/app/controllers/boards_controller.rb index 5bf4499bd..4532a88fe 100644 --- a/groups/app/controllers/boards_controller.rb +++ b/groups/app/controllers/boards_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class BoardsController < ApplicationController - layout 'base' before_filter :find_project, :authorize helper :messages diff --git a/groups/app/controllers/custom_fields_controller.rb b/groups/app/controllers/custom_fields_controller.rb index 57f700dc6..4ef4e4a90 100644 --- a/groups/app/controllers/custom_fields_controller.rb +++ b/groups/app/controllers/custom_fields_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class CustomFieldsController < ApplicationController - layout 'base' before_filter :require_admin def index @@ -32,18 +31,20 @@ class CustomFieldsController < ApplicationController def new case params[:type] - when "IssueCustomField" - @custom_field = IssueCustomField.new(params[:custom_field]) - @custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids] - when "UserCustomField" - @custom_field = UserCustomField.new(params[:custom_field]) - when "ProjectCustomField" - @custom_field = ProjectCustomField.new(params[:custom_field]) - when "GroupCustomField" - @custom_field = GroupCustomField.new(params[:custom_field]) - else - render_404 - return + when "IssueCustomField" + @custom_field = IssueCustomField.new(params[:custom_field]) + @custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids] + when "UserCustomField" + @custom_field = UserCustomField.new(params[:custom_field]) + when "ProjectCustomField" + @custom_field = ProjectCustomField.new(params[:custom_field]) + when "TimeEntryCustomField" + @custom_field = TimeEntryCustomField.new(params[:custom_field]) + when "GroupCustomField" + @custom_field = GroupCustomField.new(params[:custom_field]) + else + redirect_to :action => 'list' + return end if request.post? and @custom_field.save flash[:notice] = l(:notice_successful_create) diff --git a/groups/app/controllers/documents_controller.rb b/groups/app/controllers/documents_controller.rb index 7e732b9b6..dbf9cd8e5 100644 --- a/groups/app/controllers/documents_controller.rb +++ b/groups/app/controllers/documents_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class DocumentsController < ApplicationController - layout 'base' before_filter :find_project, :only => [:index, :new] before_filter :find_document, :except => [:index, :new] before_filter :authorize @@ -65,15 +64,6 @@ class DocumentsController < ApplicationController @document.destroy redirect_to :controller => 'documents', :action => 'index', :project_id => @project end - - def download - @attachment = @document.attachments.find(params[:attachment_id]) - @attachment.increment_download - send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename), - :type => @attachment.content_type - rescue - render_404 - end def add_attachment attachments = attach_files(@document, params[:attachments]) diff --git a/groups/app/controllers/enumerations_controller.rb b/groups/app/controllers/enumerations_controller.rb index 7a7f1685a..50521bab8 100644 --- a/groups/app/controllers/enumerations_controller.rb +++ b/groups/app/controllers/enumerations_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class EnumerationsController < ApplicationController - layout 'base' before_filter :require_admin def index @@ -75,11 +74,20 @@ class EnumerationsController < ApplicationController end def destroy - Enumeration.find(params[:id]).destroy - flash[:notice] = l(:notice_successful_delete) - redirect_to :action => 'list' - rescue - flash[:error] = "Unable to delete enumeration" - redirect_to :action => 'list' + @enumeration = Enumeration.find(params[:id]) + if !@enumeration.in_use? + # No associated objects + @enumeration.destroy + redirect_to :action => 'index' + elsif params[:reassign_to_id] + if reassign_to = Enumeration.find_by_opt_and_id(@enumeration.opt, params[:reassign_to_id]) + @enumeration.destroy(reassign_to) + redirect_to :action => 'index' + end + end + @enumerations = Enumeration.get_values(@enumeration.opt) - [@enumeration] + #rescue + # flash[:error] = 'Unable to delete enumeration' + # redirect_to :action => 'index' end end diff --git a/groups/app/controllers/issue_categories_controller.rb b/groups/app/controllers/issue_categories_controller.rb index a73935b4f..8315d6eb8 100644 --- a/groups/app/controllers/issue_categories_controller.rb +++ b/groups/app/controllers/issue_categories_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class IssueCategoriesController < ApplicationController - layout 'base' menu_item :settings before_filter :find_project, :authorize diff --git a/groups/app/controllers/issue_relations_controller.rb b/groups/app/controllers/issue_relations_controller.rb index cb0ad552a..2ca3f0d68 100644 --- a/groups/app/controllers/issue_relations_controller.rb +++ b/groups/app/controllers/issue_relations_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class IssueRelationsController < ApplicationController - layout 'base' before_filter :find_project, :authorize def new diff --git a/groups/app/controllers/issue_statuses_controller.rb b/groups/app/controllers/issue_statuses_controller.rb index d0712e7c3..69d9db965 100644 --- a/groups/app/controllers/issue_statuses_controller.rb +++ b/groups/app/controllers/issue_statuses_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class IssueStatusesController < ApplicationController - layout 'base' before_filter :require_admin verify :method => :post, :only => [ :destroy, :create, :update, :move ], diff --git a/groups/app/controllers/issues_controller.rb b/groups/app/controllers/issues_controller.rb index 322958b8c..76b851111 100644 --- a/groups/app/controllers/issues_controller.rb +++ b/groups/app/controllers/issues_controller.rb @@ -16,10 +16,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class IssuesController < ApplicationController - layout 'base' menu_item :new_issue, :only => :new - before_filter :find_issue, :only => [:show, :edit, :destroy_attachment] + before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment] before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] before_filter :find_project, :only => [:new, :update_form, :preview] before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu] @@ -43,6 +42,7 @@ class IssuesController < ApplicationController helper :sort include SortHelper include IssuesHelper + helper :timelog def index sort_init "#{Issue.table_name}.id", "desc" @@ -65,7 +65,7 @@ class IssuesController < ApplicationController :offset => @issue_pages.current.offset respond_to do |format| format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? } - format.atom { render_feed(@issues, :title => l(:label_issue_plural)) } + format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') } format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') } end @@ -94,14 +94,13 @@ class IssuesController < ApplicationController end def show - @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) } @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") @journals.each_with_index {|j,i| j.indice = i+1} @journals.reverse! if User.current.wants_comments_in_reverse_order? @allowed_statuses = @issue.new_statuses_allowed_to(User.current) @edit_allowed = User.current.allowed_to?(:edit_issues, @project) - @activities = Enumeration::get_values('ACTI') @priorities = Enumeration::get_values('IPRI') + @time_entry = TimeEntry.new respond_to do |format| format.html { render :template => 'issues/show.rhtml' } format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' } @@ -112,15 +111,18 @@ class IssuesController < ApplicationController # Add a new issue # The new issue will be created from an existing one if copy_from parameter is given def new - @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue]) + @issue = Issue.new + @issue.copy_from(params[:copy_from]) if params[:copy_from] @issue.project = @project - @issue.author = User.current - @issue.tracker ||= @project.trackers.find(params[:tracker_id] ? params[:tracker_id] : :first) + # Tracker must be set before custom field values + @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first) if @issue.tracker.nil? flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.' render :nothing => true, :layout => true return end + @issue.attributes = params[:issue] + @issue.author = User.current default_status = IssueStatus.default unless default_status @@ -133,22 +135,15 @@ class IssuesController < ApplicationController if request.get? || request.xhr? @issue.start_date ||= Date.today - @custom_values = @issue.custom_values.empty? ? - @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } : - @issue.custom_values else requested_status = (params[:issue] && params[:issue][:status_id] ? IssueStatus.find_by_id(params[:issue][:status_id]) : default_status) # Check that the user is allowed to apply the requested status @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status - @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, - :customized => @issue, - :value => (params[:custom_fields] ? params[:custom_fields][x.id.to_s] : nil)) } - @issue.custom_values = @custom_values if @issue.save attach_files(@issue, params[:attachments]) flash[:notice] = l(:notice_successful_create) Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') - redirect_to :controller => 'issues', :action => 'show', :id => @issue, :project_id => @project + redirect_to :controller => 'issues', :action => 'show', :id => @issue return end end @@ -162,10 +157,9 @@ class IssuesController < ApplicationController def edit @allowed_statuses = @issue.new_statuses_allowed_to(User.current) - @activities = Enumeration::get_values('ACTI') @priorities = Enumeration::get_values('IPRI') - @custom_values = [] @edit_allowed = User.current.allowed_to?(:edit_issues, @project) + @time_entry = TimeEntry.new @notes = params[:notes] journal = @issue.init_journal(User.current, @notes) @@ -177,21 +171,14 @@ class IssuesController < ApplicationController @issue.attributes = attrs end - if request.get? - @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) } - else - # Update custom fields if user has :edit permission - if @edit_allowed && params[:custom_fields] - @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) } - @issue.custom_values = @custom_values - end + if request.post? + @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today) + @time_entry.attributes = params[:time_entry] attachments = attach_files(@issue, params[:attachments]) attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)} - if @issue.save + if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save # Log spend time if current_role.allowed_to?(:log_time) - @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today) - @time_entry.attributes = params[:time_entry] @time_entry.save end if !journal.new_record? @@ -207,6 +194,26 @@ class IssuesController < ApplicationController flash.now[:error] = l(:notice_locking_conflict) end + def reply + journal = Journal.find(params[:journal_id]) if params[:journal_id] + if journal + user = journal.user + text = journal.notes + else + user = @issue.author + text = @issue.description + end + content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> " + content << text.to_s.strip.gsub(%r{
((.|\s)*?)
}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n" + render(:update) { |page| + page.<< "$('notes').value = \"#{content}\";" + page.show 'update' + page << "Form.Element.focus('notes');" + page << "Element.scrollTo('update');" + page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;" + } + end + # Bulk edit a set of issues def bulk_edit if request.post? @@ -240,7 +247,7 @@ class IssuesController < ApplicationController else flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #')) end - redirect_to :controller => 'issues', :action => 'index', :project_id => @project + redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project}) return end # Find potential statuses the user could be allowed to switch issues to @@ -264,6 +271,7 @@ class IssuesController < ApplicationController new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id]) unsaved_issue_ids = [] @issues.each do |issue| + issue.init_journal(User.current) unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker) end if unsaved_issue_ids.empty? @@ -318,19 +326,22 @@ class IssuesController < ApplicationController if (@issues.size == 1) @issue = @issues.first @allowed_statuses = @issue.new_statuses_allowed_to(User.current) - @assignables = @issue.assignable_users - @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to) end projects = @issues.collect(&:project).compact.uniq @project = projects.first if projects.size == 1 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)), - :update => (@issue && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && !@allowed_statuses.empty?))), + :log_time => (@project && User.current.allowed_to?(:log_time, @project)), + :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))), :move => (@project && User.current.allowed_to?(:move_issues, @project)), :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), :delete => (@project && User.current.allowed_to?(:delete_issues, @project)) } - + if @project + @assignables = @project.assignable_users + @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to) + end + @priorities = Enumeration.get_values('IPRI').reverse @statuses = IssueStatus.find(:all, :order => 'position') @back = request.env['HTTP_REFERER'] diff --git a/groups/app/controllers/journals_controller.rb b/groups/app/controllers/journals_controller.rb index 758b8507f..6df54f098 100644 --- a/groups/app/controllers/journals_controller.rb +++ b/groups/app/controllers/journals_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class JournalsController < ApplicationController - layout 'base' before_filter :find_journal def edit diff --git a/groups/app/controllers/mail_handler_controller.rb b/groups/app/controllers/mail_handler_controller.rb new file mode 100644 index 000000000..8bcfce630 --- /dev/null +++ b/groups/app/controllers/mail_handler_controller.rb @@ -0,0 +1,44 @@ +# redMine - project management software +# Copyright (C) 2006-2008 Jean-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 MailHandlerController < ActionController::Base + before_filter :check_credential + + verify :method => :post, + :only => :index, + :render => { :nothing => true, :status => 405 } + + # Submits an incoming email to MailHandler + def index + options = params.dup + email = options.delete(:email) + if MailHandler.receive(email, options) + render :nothing => true, :status => :created + else + render :nothing => true, :status => :unprocessable_entity + end + end + + private + + def check_credential + User.current = nil + unless Setting.mail_handler_api_enabled? && params[:key] == Setting.mail_handler_api_key + render :nothing => true, :status => 403 + end + end +end diff --git a/groups/app/controllers/members_controller.rb b/groups/app/controllers/members_controller.rb index 6a42ed05e..fcaede0a5 100644 --- a/groups/app/controllers/members_controller.rb +++ b/groups/app/controllers/members_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class MembersController < ApplicationController - layout 'base' before_filter :find_member, :except => :new before_filter :find_project, :only => :new before_filter :authorize diff --git a/groups/app/controllers/messages_controller.rb b/groups/app/controllers/messages_controller.rb index 97cb2c3bf..554279d21 100644 --- a/groups/app/controllers/messages_controller.rb +++ b/groups/app/controllers/messages_controller.rb @@ -16,14 +16,15 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class MessagesController < ApplicationController - layout 'base' menu_item :boards before_filter :find_board, :only => [:new, :preview] before_filter :find_message, :except => [:new, :preview] before_filter :authorize, :except => :preview verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show } + verify :xhr => true, :only => :quote + helper :attachments include AttachmentsHelper @@ -83,6 +84,20 @@ class MessagesController < ApplicationController { :action => 'show', :id => @message.parent } end + def quote + user = @message.author + text = @message.content + content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> " + content << text.to_s.strip.gsub(%r{
((.|\s)*?)
}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n" + render(:update) { |page| + page.<< "$('message_content').value = \"#{content}\";" + page.show 'reply' + page << "Form.Element.focus('message_content');" + page << "Element.scrollTo('reply');" + page << "$('message_content').scrollTop = $('message_content').scrollHeight - $('message_content').clientHeight;" + } + end + def preview message = @board.messages.find_by_id(params[:id]) @attachements = message.attachments if message diff --git a/groups/app/controllers/my_controller.rb b/groups/app/controllers/my_controller.rb index ff3393e90..1cfa3e531 100644 --- a/groups/app/controllers/my_controller.rb +++ b/groups/app/controllers/my_controller.rb @@ -16,11 +16,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class MyController < ApplicationController - helper :issues - - layout 'base' before_filter :require_login + helper :issues + BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues, 'issuesreportedbyme' => :label_reported_issues, 'issueswatched' => :label_watched_issues, diff --git a/groups/app/controllers/news_controller.rb b/groups/app/controllers/news_controller.rb index c9ba6b991..b5f7ca1b2 100644 --- a/groups/app/controllers/news_controller.rb +++ b/groups/app/controllers/news_controller.rb @@ -16,9 +16,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class NewsController < ApplicationController - layout 'base' before_filter :find_news, :except => [:new, :index, :preview] - before_filter :find_project, :only => :new + before_filter :find_project, :only => [:new, :preview] before_filter :authorize, :except => [:index, :preview] before_filter :find_optional_project, :only => :index accept_key_auth :index diff --git a/groups/app/controllers/projects_controller.rb b/groups/app/controllers/projects_controller.rb index ba6955221..747c26bd2 100644 --- a/groups/app/controllers/projects_controller.rb +++ b/groups/app/controllers/projects_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class ProjectsController < ApplicationController - layout 'base' menu_item :overview menu_item :activity, :only => :activity menu_item :roadmap, :only => :roadmap @@ -44,37 +43,36 @@ class ProjectsController < ApplicationController include RepositoriesHelper include ProjectsHelper - def index - list - render :action => 'list' unless request.xhr? - end - # Lists visible projects - def list + def index projects = Project.find :all, :conditions => Project.visible_by(User.current), :include => :parent - @project_tree = projects.group_by {|p| p.parent || p} - @project_tree.each_key {|p| @project_tree[p] -= [p]} + respond_to do |format| + format.html { + @project_tree = projects.group_by {|p| p.parent || p} + @project_tree.keys.each {|p| @project_tree[p] -= [p]} + } + format.atom { + render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i), + :title => "#{Setting.app_title}: #{l(:label_project_latest)}") + } + end end # Add a new project def add - @custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") + @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @trackers = Tracker.all @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}", :order => 'name') @project = Project.new(params[:project]) if request.get? - @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project) } @project.trackers = Tracker.all @project.is_public = Setting.default_projects_public? @project.enabled_module_names = Redmine::AccessControl.available_project_modules else - @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids] - @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) } - @project.custom_values = @custom_values @project.enabled_module_names = params[:enabled_modules] if @project.save flash[:notice] = l(:notice_successful_create) @@ -85,8 +83,7 @@ class ProjectsController < ApplicationController # Show @project def show - @custom_values = @project.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position") - @subprojects = @project.active_children + @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current)) @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") @trackers = @project.rolled_up_trackers @@ -111,11 +108,10 @@ class ProjectsController < ApplicationController @root_projects = Project.find(:all, :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id], :order => 'name') - @custom_fields = IssueCustomField.find(:all) + @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @issue_category ||= IssueCategory.new @member ||= @project.members.new @trackers = Tracker.all - @custom_values ||= ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) } @repository ||= @project.repository @wiki ||= @project.wiki end @@ -123,10 +119,6 @@ class ProjectsController < ApplicationController # Edit @project def edit if request.post? - if params[:custom_fields] - @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) } - @project.custom_values = @custom_values - end @project.attributes = params[:project] if @project.save flash[:notice] = l(:notice_successful_update) @@ -232,91 +224,23 @@ class ProjectsController < ApplicationController @date_to ||= Date.today + 1 @date_from = @date_to - @days + @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') - @event_types = %w(issues news files documents changesets wiki_pages messages) - if @project - @event_types.delete('wiki_pages') unless @project.wiki - @event_types.delete('changesets') unless @project.repository - @event_types.delete('messages') unless @project.boards.any? - # only show what the user is allowed to view - @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)} - @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') - end - @scope = @event_types.select {|t| params["show_#{t}"]} - # default events if none is specified in parameters - @scope = (@event_types - %w(wiki_pages messages))if @scope.empty? - - @events = [] - - if @scope.include?('issues') - cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects)) - cond.add(["#{Issue.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to]) - @events += Issue.find(:all, :include => [:project, :author, :tracker], :conditions => cond.conditions) - - cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_issues, :project => @project, :with_subprojects => @with_subprojects)) - cond.add(["#{Journal.table_name}.journalized_type = 'Issue' AND #{JournalDetail.table_name}.prop_key = 'status_id' AND #{Journal.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to]) - @events += Journal.find(:all, :include => [{:issue => :project}, :details, :user], :conditions => cond.conditions) - end - - if @scope.include?('news') - cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_news, :project => @project, :with_subprojects => @with_subprojects)) - cond.add(["#{News.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to]) - @events += News.find(:all, :include => [:project, :author], :conditions => cond.conditions) - end - - if @scope.include?('files') - cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_files, :project => @project, :with_subprojects => @with_subprojects)) - cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to]) - @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*", - :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " + - "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id", - :conditions => cond.conditions) - end - - if @scope.include?('documents') - cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects)) - cond.add(["#{Document.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to]) - @events += Document.find(:all, :include => :project, :conditions => cond.conditions) - - cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_documents, :project => @project, :with_subprojects => @with_subprojects)) - cond.add(["#{Attachment.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to]) - @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*", - :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " + - "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id", - :conditions => cond.conditions) - end - - if @scope.include?('wiki_pages') - select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " + - "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " + - "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " + - "#{WikiContent.versioned_table_name}.id" - joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " + - "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " + - "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id" - - cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_wiki_pages, :project => @project, :with_subprojects => @with_subprojects)) - cond.add(["#{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?", @date_from, @date_to]) - @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => cond.conditions) - end + @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project, :with_subprojects => @with_subprojects) + @activity.scope_select {|t| !params["show_#{t}"].nil?} + @activity.default_scope! if @activity.scope.empty? - if @scope.include?('changesets') - cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_changesets, :project => @project, :with_subprojects => @with_subprojects)) - cond.add(["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to]) - @events += Changeset.find(:all, :include => {:repository => :project}, :conditions => cond.conditions) - end - - if @scope.include?('messages') - cond = ARCondition.new(Project.allowed_to_condition(User.current, :view_messages, :project => @project, :with_subprojects => @with_subprojects)) - cond.add(["#{Message.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to]) - @events += Message.find(:all, :include => [{:board => :project}, :author], :conditions => cond.conditions) - end - - @events_by_day = @events.group_by(&:event_date) + events = @activity.events(@date_from, @date_to) respond_to do |format| - format.html { render :layout => false if request.xhr? } - format.atom { render_feed(@events, :title => "#{@project || Setting.app_title}: #{l(:label_activity)}") } + format.html { + @events_by_day = events.group_by(&:event_date) + render :layout => false if request.xhr? + } + format.atom { + title = (@activity.scope.size == 1) ? l("label_#{@activity.scope.first.singularize}_plural") : l(:label_activity) + render_feed(events, :title => "#{@project || Setting.app_title}: #{title}") + } end end @@ -381,11 +305,18 @@ class ProjectsController < ApplicationController @events = [] @project.issues_with_subprojects(@with_subprojects) do + # Issues that have start and due dates @events += Issue.find(:all, :order => "start_date, due_date", :include => [:tracker, :status, :assigned_to, :priority, :project], :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to] ) unless @selected_tracker_ids.empty? + # Issues that don't have a due date but that are assigned to a version with a date + @events += Issue.find(:all, + :order => "start_date, effective_date", + :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version], + :conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date?)) and start_date is not null and due_date is null and effective_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to] + ) unless @selected_tracker_ids.empty? @events += Version.find(:all, :include => :project, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to]) end diff --git a/groups/app/controllers/queries_controller.rb b/groups/app/controllers/queries_controller.rb index da2c4a2c8..8500e853a 100644 --- a/groups/app/controllers/queries_controller.rb +++ b/groups/app/controllers/queries_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class QueriesController < ApplicationController - layout 'base' menu_item :issues before_filter :find_query, :except => :new before_filter :find_optional_project, :only => :new diff --git a/groups/app/controllers/reports_controller.rb b/groups/app/controllers/reports_controller.rb index 338059a50..dd3ece930 100644 --- a/groups/app/controllers/reports_controller.rb +++ b/groups/app/controllers/reports_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class ReportsController < ApplicationController - layout 'base' menu_item :issues before_filter :find_project, :authorize diff --git a/groups/app/controllers/repositories_controller.rb b/groups/app/controllers/repositories_controller.rb index 64eb05793..2f96e2d66 100644 --- a/groups/app/controllers/repositories_controller.rb +++ b/groups/app/controllers/repositories_controller.rb @@ -23,20 +23,21 @@ class ChangesetNotFound < Exception; end class InvalidRevisionParam < Exception; end class RepositoriesController < ApplicationController - layout 'base' menu_item :repository before_filter :find_repository, :except => :edit before_filter :find_project, :only => :edit before_filter :authorize accept_key_auth :revisions + rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed + def edit @repository = @project.repository if !@repository @repository = Repository.factory(params[:repository_scm]) - @repository.project = @project + @repository.project = @project if @repository end - if request.post? + if request.post? && @repository @repository.attributes = params[:repository] @repository.save end @@ -56,8 +57,6 @@ class RepositoriesController < ApplicationController # latest changesets @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC") show_error_not_found unless @entries || @changesets.any? - rescue Redmine::Scm::Adapters::CommandFailed => e - show_error_command_failed(e.message) end def browse @@ -66,18 +65,16 @@ class RepositoriesController < ApplicationController @entries ? render(:partial => 'dir_list_content') : render(:nothing => true) else show_error_not_found and return unless @entries + @properties = @repository.properties(@path, @rev) render :action => 'browse' end - rescue Redmine::Scm::Adapters::CommandFailed => e - show_error_command_failed(e.message) end def changes - @entry = @repository.scm.entry(@path, @rev) + @entry = @repository.entry(@path, @rev) show_error_not_found and return unless @entry @changesets = @repository.changesets_for_path(@path) - rescue Redmine::Scm::Adapters::CommandFailed => e - show_error_command_failed(e.message) + @properties = @repository.properties(@path, @rev) end def revisions @@ -96,13 +93,13 @@ class RepositoriesController < ApplicationController end def entry - @entry = @repository.scm.entry(@path, @rev) + @entry = @repository.entry(@path, @rev) show_error_not_found and return unless @entry # If the entry is a dir, show the browser browse and return if @entry.is_dir? - @content = @repository.scm.cat(@path, @rev) + @content = @repository.cat(@path, @rev) show_error_not_found and return unless @content if 'raw' == params[:format] || @content.is_binary_data? # Force the download if it's a binary file @@ -110,16 +107,12 @@ class RepositoriesController < ApplicationController else # Prevent empty lines when displaying a file with Windows style eol @content.gsub!("\r\n", "\n") - end - rescue Redmine::Scm::Adapters::CommandFailed => e - show_error_command_failed(e.message) + end end def annotate @annotate = @repository.scm.annotate(@path, @rev) render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty? - rescue Redmine::Scm::Adapters::CommandFailed => e - show_error_command_failed(e.message) end def revision @@ -137,27 +130,33 @@ class RepositoriesController < ApplicationController end rescue ChangesetNotFound show_error_not_found - rescue Redmine::Scm::Adapters::CommandFailed => e - show_error_command_failed(e.message) end def diff - @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' - @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) - - # Save diff type as user preference - if User.current.logged? && @diff_type != User.current.pref[:diff_type] - User.current.pref[:diff_type] = @diff_type - User.current.preference.save - end - - @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}") - unless read_fragment(@cache_key) - @diff = @repository.diff(@path, @rev, @rev_to, @diff_type) - show_error_not_found unless @diff + if params[:format] == 'diff' + @diff = @repository.diff(@path, @rev, @rev_to) + show_error_not_found and return unless @diff + filename = "changeset_r#{@rev}" + filename << "_r#{@rev_to}" if @rev_to + send_data @diff.join, :filename => "#{filename}.diff", + :type => 'text/x-patch', + :disposition => 'attachment' + else + @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' + @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) + + # Save diff type as user preference + if User.current.logged? && @diff_type != User.current.pref[:diff_type] + User.current.pref[:diff_type] = @diff_type + User.current.preference.save + end + + @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}") + unless read_fragment(@cache_key) + @diff = @repository.diff(@path, @rev, @rev_to) + show_error_not_found unless @diff + end end - rescue Redmine::Scm::Adapters::CommandFailed => e - show_error_command_failed(e.message) end def stats @@ -207,8 +206,9 @@ private render_error l(:error_scm_not_found) end - def show_error_command_failed(msg) - render_error l(:error_scm_command_failed, msg) + # Handler for Redmine::Scm::Adapters::CommandFailed exception + def show_error_command_failed(exception) + render_error l(:error_scm_command_failed, exception.message) end def graph_commits_per_month(repository) @@ -229,7 +229,7 @@ private graph = SVG::Graph::Bar.new( :height => 300, - :width => 500, + :width => 800, :fields => fields.reverse, :stack => :side, :scale_integers => true, @@ -271,8 +271,8 @@ private fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } graph = SVG::Graph::BarHorizontal.new( - :height => 300, - :width => 500, + :height => 400, + :width => 800, :fields => fields, :stack => :side, :scale_integers => true, diff --git a/groups/app/controllers/roles_controller.rb b/groups/app/controllers/roles_controller.rb index 9fdd9701b..72555e5b0 100644 --- a/groups/app/controllers/roles_controller.rb +++ b/groups/app/controllers/roles_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class RolesController < ApplicationController - layout 'base' before_filter :require_admin verify :method => :post, :only => [ :destroy, :move ], diff --git a/groups/app/controllers/search_controller.rb b/groups/app/controllers/search_controller.rb index f15653b63..e6e66f05c 100644 --- a/groups/app/controllers/search_controller.rb +++ b/groups/app/controllers/search_controller.rb @@ -16,8 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class SearchController < ApplicationController - layout 'base' - before_filter :find_optional_project helper :messages @@ -29,6 +27,18 @@ class SearchController < ApplicationController @all_words = params[:all_words] || (params[:submit] ? false : true) @titles_only = !params[:titles_only].nil? + projects_to_search = + case params[:scope] + when 'all' + nil + when 'my_projects' + User.current.memberships.collect(&:project) + when 'subprojects' + @project ? ([ @project ] + @project.active_children) : nil + else + @project + end + offset = nil begin; offset = params[:offset].to_time if params[:offset]; rescue; end @@ -38,16 +48,16 @@ class SearchController < ApplicationController return end - if @project + @object_types = %w(issues news documents changesets wiki_pages messages projects) + if projects_to_search.is_a? Project + # don't search projects + @object_types.delete('projects') # only show what the user is allowed to view - @object_types = %w(issues news documents changesets wiki_pages messages) - @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)} - - @scope = @object_types.select {|t| params[t]} - @scope = @object_types if @scope.empty? - else - @object_types = @scope = %w(projects) + @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)} end + + @scope = @object_types.select {|t| params[t]} + @scope = @object_types if @scope.empty? # extract tokens from the question # eg. hello "bye bye" => ["hello", "bye bye"] @@ -60,39 +70,34 @@ class SearchController < ApplicationController @tokens.slice! 5..-1 if @tokens.size > 5 # strings used in sql like statement like_tokens = @tokens.collect {|w| "%#{w.downcase}%"} + @results = [] + @results_by_type = Hash.new {|h,k| h[k] = 0} + limit = 10 - if @project - @scope.each do |s| - @results += s.singularize.camelcase.constantize.search(like_tokens, @project, - :all_words => @all_words, - :titles_only => @titles_only, - :limit => (limit+1), - :offset => offset, - :before => params[:previous].nil?) - end - @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime} - if params[:previous].nil? - @pagination_previous_date = @results[0].event_datetime if offset && @results[0] - if @results.size > limit - @pagination_next_date = @results[limit-1].event_datetime - @results = @results[0, limit] - end - else - @pagination_next_date = @results[-1].event_datetime if offset && @results[-1] - if @results.size > limit - @pagination_previous_date = @results[-(limit)].event_datetime - @results = @results[-(limit), limit] - end + @scope.each do |s| + r, c = s.singularize.camelcase.constantize.search(like_tokens, projects_to_search, + :all_words => @all_words, + :titles_only => @titles_only, + :limit => (limit+1), + :offset => offset, + :before => params[:previous].nil?) + @results += r + @results_by_type[s] += c + end + @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime} + if params[:previous].nil? + @pagination_previous_date = @results[0].event_datetime if offset && @results[0] + if @results.size > limit + @pagination_next_date = @results[limit-1].event_datetime + @results = @results[0, limit] end else - operator = @all_words ? ' AND ' : ' OR ' - @results += Project.find(:all, - :limit => limit, - :conditions => [ (["(#{Project.visible_by(User.current)}) AND (LOWER(name) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] - ) if @scope.include? 'projects' - # if only one project is found, user is redirected to its overview - redirect_to :controller => 'projects', :action => 'show', :id => @results.first and return if @results.size == 1 + @pagination_next_date = @results[-1].event_datetime if offset && @results[-1] + if @results.size > limit + @pagination_previous_date = @results[-(limit)].event_datetime + @results = @results[-(limit), limit] + end end else @question = "" diff --git a/groups/app/controllers/settings_controller.rb b/groups/app/controllers/settings_controller.rb index c7c8751dd..6482a3576 100644 --- a/groups/app/controllers/settings_controller.rb +++ b/groups/app/controllers/settings_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class SettingsController < ApplicationController - layout 'base' before_filter :require_admin def index @@ -39,6 +38,7 @@ class SettingsController < ApplicationController end @options = {} @options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] } + @deliveries = ActionMailer::Base.perform_deliveries end def plugin @@ -49,7 +49,7 @@ class SettingsController < ApplicationController flash[:notice] = l(:notice_successful_update) redirect_to :action => 'plugin', :id => params[:id] end - @partial = "../../vendor/plugins/#{plugin_id}/app/views/" + @plugin.settings[:partial] + @partial = @plugin.settings[:partial] @settings = Setting["plugin_#{plugin_id}"] end end diff --git a/groups/app/controllers/timelog_controller.rb b/groups/app/controllers/timelog_controller.rb index 29c2635d6..f331cdbe4 100644 --- a/groups/app/controllers/timelog_controller.rb +++ b/groups/app/controllers/timelog_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class TimelogController < ApplicationController - layout 'base' menu_item :issues before_filter :find_project, :authorize @@ -54,8 +53,15 @@ class TimelogController < ApplicationController } # Add list and boolean custom fields as available criterias - @project.all_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM custom_values c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = issues.id)", + @project.all_issue_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| + @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)", + :format => cf.field_format, + :label => cf.name} + end + + # Add list and boolean time entry custom fields + TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| + @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)", :format => cf.field_format, :label => cf.name} end @@ -154,6 +160,14 @@ class TimelogController < ApplicationController render :layout => !request.xhr? } + format.atom { + entries = TimeEntry.find(:all, + :include => [:project, :activity, :user, {:issue => :tracker}], + :conditions => cond.conditions, + :order => "#{TimeEntry.table_name}.created_on DESC", + :limit => Setting.feeds_limit.to_i) + render_feed(entries, :title => l(:label_spent_time)) + } format.csv { # Export all entries @entries = TimeEntry.find(:all, @@ -172,10 +186,9 @@ class TimelogController < ApplicationController @time_entry.attributes = params[:time_entry] if request.post? and @time_entry.save flash[:notice] = l(:notice_successful_update) - redirect_to(params[:back_url] || {:action => 'details', :project_id => @time_entry.project}) + redirect_to(params[:back_url].blank? ? {:action => 'details', :project_id => @time_entry.project} : params[:back_url]) return end - @activities = Enumeration::get_values('ACTI') end def destroy diff --git a/groups/app/controllers/trackers_controller.rb b/groups/app/controllers/trackers_controller.rb index 3d7dbd5c5..8c02f9474 100644 --- a/groups/app/controllers/trackers_controller.rb +++ b/groups/app/controllers/trackers_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class TrackersController < ApplicationController - layout 'base' before_filter :require_admin def index diff --git a/groups/app/controllers/users_controller.rb b/groups/app/controllers/users_controller.rb index 3cd66d6a4..5c933c7de 100644 --- a/groups/app/controllers/users_controller.rb +++ b/groups/app/controllers/users_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class UsersController < ApplicationController - layout 'base' before_filter :require_admin helper :sort @@ -53,15 +52,12 @@ class UsersController < ApplicationController def add if request.get? @user = User.new(:language => Setting.default_language) - @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user) } else @user = User.new(params[:user]) @user.admin = params[:user][:admin] || false @user.login = params[:user][:login] @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id @user.group_id = params[:user][:group_id] - @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) } - @user.custom_values = @custom_values if @user.save Mailer.deliver_account_information(@user, params[:password]) if params[:send_information] flash[:notice] = l(:notice_successful_create) @@ -74,17 +70,11 @@ class UsersController < ApplicationController def edit @user = User.find(params[:id]) - if request.get? - @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @user.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) } - else + if request.post? @user.admin = params[:user][:admin] if params[:user][:admin] @user.login = params[:user][:login] if params[:user][:login] @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id @user.group_id = params[:user][:group_id] if params[:user][:group_id] - if params[:custom_fields] - @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) } - @user.custom_values = @custom_values - end if @user.update_attributes(params[:user]) flash[:notice] = l(:notice_successful_update) # Give a string to redirect_to otherwise it would use status param as the response code @@ -96,16 +86,15 @@ class UsersController < ApplicationController @roles = Role.find_all_givable @projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects @membership ||= Member.new + @memberships = @user.memberships.select {|m| m.inherited_from.nil? } end def edit_membership @user = User.find(params[:id]) @membership = params[:membership_id] ? Member.find(params[:membership_id], :conditions => 'inherited_from IS NULL') : Member.new(:principal => @user) @membership.attributes = params[:membership] - if request.post? and @membership.save - flash[:notice] = l(:notice_successful_update) - end - redirect_to :action => 'edit', :id => @user and return + @membership.save if request.post? + redirect_to :action => 'edit', :id => @user, :tab => 'memberships' end def destroy_membership @@ -113,6 +102,6 @@ class UsersController < ApplicationController if request.post? and Member.find(params[:membership_id], :conditions => 'inherited_from IS NULL').destroy flash[:notice] = l(:notice_successful_update) end - redirect_to :action => 'edit', :id => @user and return + redirect_to :action => 'edit', :id => @user, :tab => 'memberships' end end diff --git a/groups/app/controllers/versions_controller.rb b/groups/app/controllers/versions_controller.rb index aeb802ccb..ab2ccb773 100644 --- a/groups/app/controllers/versions_controller.rb +++ b/groups/app/controllers/versions_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class VersionsController < ApplicationController - layout 'base' menu_item :roadmap before_filter :find_project, :authorize @@ -37,15 +36,6 @@ class VersionsController < ApplicationController flash[:error] = "Unable to delete version" redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project end - - def download - @attachment = @version.attachments.find(params[:attachment_id]) - @attachment.increment_download - send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename), - :type => @attachment.content_type - rescue - render_404 - end def destroy_file @version.attachments.find(params[:attachment_id]).destroy diff --git a/groups/app/controllers/watchers_controller.rb b/groups/app/controllers/watchers_controller.rb index 206dc0843..8e6ee3a9e 100644 --- a/groups/app/controllers/watchers_controller.rb +++ b/groups/app/controllers/watchers_controller.rb @@ -16,27 +16,38 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class WatchersController < ApplicationController - layout 'base' - before_filter :require_login, :find_project, :check_project_privacy + before_filter :find_project + before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch] + before_filter :authorize, :only => :new - def add - user = User.current - @watched.add_watcher(user) - respond_to do |format| - format.html { render :text => 'Watcher added.', :layout => true } - format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} } - end + verify :method => :post, + :only => [ :watch, :unwatch ], + :render => { :nothing => true, :status => :method_not_allowed } + + def watch + set_watcher(User.current, true) end - def remove - user = User.current - @watched.remove_watcher(user) + def unwatch + set_watcher(User.current, false) + end + + def new + @watcher = Watcher.new(params[:watcher]) + @watcher.watchable = @watched + @watcher.save if request.post? respond_to do |format| - format.html { render :text => 'Watcher removed.', :layout => true } - format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} } + format.html { redirect_to :back } + format.js do + render :update do |page| + page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched} + end + end end + rescue ::ActionController::RedirectBackError + render :text => 'Watcher added.', :layout => true end - + private def find_project klass = Object.const_get(params[:object_type].camelcase) @@ -46,4 +57,14 @@ private rescue render_404 end + + def set_watcher(user, watching) + @watched.set_watcher(user, watching) + respond_to do |format| + format.html { redirect_to :back } + format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} } + end + rescue ::ActionController::RedirectBackError + render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true + end end diff --git a/groups/app/controllers/welcome_controller.rb b/groups/app/controllers/welcome_controller.rb index b4be7fb1c..b8108e8ac 100644 --- a/groups/app/controllers/welcome_controller.rb +++ b/groups/app/controllers/welcome_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class WelcomeController < ApplicationController - layout 'base' def index @news = News.latest User.current diff --git a/groups/app/controllers/wiki_controller.rb b/groups/app/controllers/wiki_controller.rb index 53c5ec53b..46df2931e 100644 --- a/groups/app/controllers/wiki_controller.rb +++ b/groups/app/controllers/wiki_controller.rb @@ -18,10 +18,9 @@ require 'diff' class WikiController < ApplicationController - layout 'base' before_filter :find_wiki, :authorize - verify :method => :post, :only => [:destroy, :destroy_attachment], :redirect_to => { :action => :index } + verify :method => :post, :only => [:destroy, :destroy_attachment, :protect], :redirect_to => { :action => :index } helper :attachments include AttachmentsHelper @@ -48,12 +47,14 @@ class WikiController < ApplicationController send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt") return end + @editable = editable? render :action => 'show' end # edit an existing page or a new one def edit @page = @wiki.find_or_new_page(params[:page]) + return render_403 unless editable? @page.content = WikiContent.new(:page => @page) if @page.new_record? @content = @page.content_for_version(params[:version]) @@ -82,7 +83,8 @@ class WikiController < ApplicationController # rename a page def rename - @page = @wiki.find_page(params[:page]) + @page = @wiki.find_page(params[:page]) + return render_403 unless editable? @page.redirect_existing_links = true # used to display the *original* title if some AR validation errors occur @original_title = @page.pretty_title @@ -92,6 +94,12 @@ class WikiController < ApplicationController end end + def protect + page = @wiki.find_page(params[:page]) + page.update_attribute :protected, params[:protected] + redirect_to :action => 'index', :id => @project, :page => page.title + end + # show page history def history @page = @wiki.find_page(params[:page]) @@ -122,6 +130,7 @@ class WikiController < ApplicationController # remove a wiki page and its history def destroy @page = @wiki.find_page(params[:page]) + return render_403 unless editable? @page.destroy if @page redirect_to :action => 'special', :id => @project, :page => 'Page_index' end @@ -137,6 +146,7 @@ class WikiController < ApplicationController :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id", :order => 'title' @pages_by_date = @pages.group_by {|p| p.updated_on.to_date} + @pages_by_parent_id = @pages.group_by(&:parent_id) # export wiki to a single html file when 'export' @pages = @wiki.pages.find :all, :order => 'title' @@ -152,19 +162,26 @@ class WikiController < ApplicationController def preview page = @wiki.find_page(params[:page]) - @attachements = page.attachments if page + # page is nil when previewing a new page + return render_403 unless page.nil? || editable?(page) + if page + @attachements = page.attachments + @previewed = page.content + end @text = params[:content][:text] render :partial => 'common/preview' end def add_attachment @page = @wiki.find_page(params[:page]) + return render_403 unless editable? attach_files(@page, params[:attachments]) redirect_to :action => 'index', :page => @page.title end def destroy_attachment @page = @wiki.find_page(params[:page]) + return render_403 unless editable? @page.attachments.find(params[:attachment_id]).destroy redirect_to :action => 'index', :page => @page.title end @@ -178,4 +195,9 @@ private rescue ActiveRecord::RecordNotFound render_404 end + + # Returns true if the current user is allowed to edit the page, otherwise false + def editable?(page = @page) + page.editable_by?(User.current) + end end diff --git a/groups/app/controllers/wikis_controller.rb b/groups/app/controllers/wikis_controller.rb index 6054abd9a..215d39f4b 100644 --- a/groups/app/controllers/wikis_controller.rb +++ b/groups/app/controllers/wikis_controller.rb @@ -16,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class WikisController < ApplicationController - layout 'base' menu_item :settings before_filter :find_project, :authorize diff --git a/groups/app/helpers/application_helper.rb b/groups/app/helpers/application_helper.rb index 47a251053..78e5bdc65 100644 --- a/groups/app/helpers/application_helper.rb +++ b/groups/app/helpers/application_helper.rb @@ -15,6 +15,9 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +require 'coderay' +require 'coderay/helpers/file_type' + module ApplicationHelper include Redmine::WikiFormatting::Macros::Definitions @@ -31,6 +34,12 @@ module ApplicationHelper def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) end + + # Display a link to remote if user is authorized + def link_to_remote_if_authorized(name, options = {}, html_options = nil) + url = options[:url] || {} + link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action]) + end # Display a link to user's account page def link_to_user(user) @@ -38,9 +47,23 @@ module ApplicationHelper end def link_to_issue(issue, options={}) + options[:class] ||= '' + options[:class] << ' issue' + options[:class] << ' closed' if issue.closed? link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options end + # Generates a link to an attachment. + # Options: + # * :text - Link text (default to attachment filename) + # * :download - Force download (default: false) + def link_to_attachment(attachment, options={}) + text = options.delete(:text) || attachment.filename + action = options.delete(:download) ? 'download' : 'show' + + link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options) + end + def toggle_link(name, id, options={}) onclick = "Element.toggle('#{id}'); " onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ") @@ -48,14 +71,6 @@ module ApplicationHelper link_to(name, "#", :onclick => onclick) end - def show_and_goto_link(name, id, options={}) - onclick = "Element.show('#{id}'); " - onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ") - onclick << "Element.scrollTo('#{id}'); " - onclick << "return false;" - link_to(name, "#", options.merge(:onclick => onclick)) - end - def image_to_function(name, function, html_options = {}) html_options.symbolize_keys! tag(:input, html_options.merge({ @@ -80,23 +95,25 @@ module ApplicationHelper return nil unless time time = time.to_time if time.is_a?(String) zone = User.current.time_zone - if time.utc? - local = zone ? zone.adjust(time) : time.getlocal - else - local = zone ? zone.adjust(time.getutc) : time - end + local = zone ? time.in_time_zone(zone) : (time.utc? ? time.utc_to_local : time) @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format) @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format) include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format) end + # Truncates and returns the string as a single line + def truncate_single_line(string, *args) + truncate(string, *args).gsub(%r{[\r\n]+}m, ' ') + end + def html_hours(text) text.gsub(%r{(\d+)\.(\d+)}, '\1.\2') end def authoring(created, author) time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created)) - l(:label_added_time_by, author || 'Anonymous', time_tag) + author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous') + l(:label_added_time_by, author_tag, time_tag) end def l_or_humanize(s) @@ -111,6 +128,15 @@ module ApplicationHelper l(:actionview_datehelper_select_month_names).split(',')[month-1] end + def syntax_highlight(name, content) + type = CodeRay::FileType[name] + type ? CodeRay.scan(content, type).html : h(content) + end + + def to_path_param(path) + path.to_s.split(%r{[/\\]}).select {|p| !p.blank?} + end + def pagination_links_full(paginator, count=nil, options={}) page_param = options.delete(:page_param) || :page url_param = params.dup @@ -157,7 +183,8 @@ module ApplicationHelper end def breadcrumb(*args) - content_tag('p', args.join(' » ') + ' » ', :class => 'breadcrumb') + elements = args.flatten + elements.any? ? content_tag('p', args.join(' » ') + ' » ', :class => 'breadcrumb') : nil end def html_title(*args) @@ -185,7 +212,7 @@ module ApplicationHelper options = args.last.is_a?(Hash) ? args.pop : {} case args.size when 1 - obj = nil + obj = options[:object] text = args.shift when 2 obj = args.shift @@ -225,12 +252,12 @@ module ApplicationHelper case options[:wiki_links] when :local # used for local links to html files - format_wiki_link = Proc.new {|project, title| "#{title}.html" } + format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" } when :anchor # used for single-file wiki export - format_wiki_link = Proc.new {|project, title| "##{title}" } + format_wiki_link = Proc.new {|project, title, anchor| "##{title}" } else - format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) } + format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) } end project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) @@ -256,9 +283,14 @@ module ApplicationHelper end if link_project && link_project.wiki + # extract anchor + anchor = nil + if page =~ /^(.+?)\#(.+)$/ + page, anchor = $1, $2 + end # check if page exists wiki_page = link_project.wiki.find_page(page) - link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)), + link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor), :class => ('wiki-page' + (wiki_page ? '' : ' new'))) else # project or wiki doesn't exist @@ -293,7 +325,9 @@ module ApplicationHelper # source:some/file#L120 -> Link to line 120 of the file # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 # export:some/file -> Force the download of the file - text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m| + # Forum messages: + # message#1218 -> Link to message with id 1218 + text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m| leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8 link = nil if esc.nil? @@ -301,7 +335,7 @@ module ApplicationHelper if project && (changeset = project.changesets.find_by_revision(oid)) link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid}, :class => 'changeset', - :title => truncate(changeset.comments, 100)) + :title => truncate_single_line(changeset.comments, 100)) end elsif sep == '#' oid = oid.to_i @@ -323,6 +357,16 @@ module ApplicationHelper link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, :class => 'version' end + when 'message' + if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current)) + link = link_to h(truncate(message.subject, 60)), {:only_path => only_path, + :controller => 'messages', + :action => 'show', + :board_id => message.board, + :id => message.root, + :anchor => (message.parent ? "message-#{message.id}" : nil)}, + :class => 'message' + end end elsif sep == ':' # removes the double quotes if any @@ -340,13 +384,16 @@ module ApplicationHelper end when 'commit' if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"])) - link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, :class => 'changeset', :title => truncate(changeset.comments, 100) + link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, + :class => 'changeset', + :title => truncate_single_line(changeset.comments, 100) end when 'source', 'export' if project && project.repository name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} path, rev, anchor = $1, $3, $5 - link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :path => path, + link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, + :path => to_path_param(path), :rev => rev, :anchor => anchor, :format => (prefix == 'export' ? 'raw' : nil)}, @@ -428,7 +475,8 @@ module ApplicationHelper end def back_url_hidden_field_tag - hidden_field_tag 'back_url', (params[:back_url] || request.env['HTTP_REFERER']) + back_url = params[:back_url] || request.env['HTTP_REFERER'] + hidden_field_tag('back_url', back_url) unless back_url.blank? end def check_all_links(form_name) diff --git a/groups/app/helpers/attachments_helper.rb b/groups/app/helpers/attachments_helper.rb index 989cd3e66..ebf417bab 100644 --- a/groups/app/helpers/attachments_helper.rb +++ b/groups/app/helpers/attachments_helper.rb @@ -22,4 +22,8 @@ module AttachmentsHelper render :partial => 'attachments/links', :locals => {:attachments => attachments, :options => options} end end + + def to_utf8(str) + str + end end diff --git a/groups/app/helpers/custom_fields_helper.rb b/groups/app/helpers/custom_fields_helper.rb index aae105b72..ed0c4126f 100644 --- a/groups/app/helpers/custom_fields_helper.rb +++ b/groups/app/helpers/custom_fields_helper.rb @@ -19,6 +19,7 @@ module CustomFieldsHelper def custom_fields_tabs tabs = [{:name => 'IssueCustomField', :label => :label_issue_plural}, + {:name => 'TimeEntryCustomField', :label => :label_spent_time}, {:name => 'ProjectCustomField', :label => :label_project_plural}, {:name => 'UserCustomField', :label => :label_user_plural}, {:name => 'GroupCustomField', :label => :label_group_plural} @@ -26,37 +27,40 @@ module CustomFieldsHelper end # Return custom field html tag corresponding to its format - def custom_field_tag(custom_value) + def custom_field_tag(name, custom_value) custom_field = custom_value.custom_field - field_name = "custom_fields[#{custom_field.id}]" - field_id = "custom_fields_#{custom_field.id}" + field_name = "#{name}[custom_field_values][#{custom_field.id}]" + field_id = "#{name}_custom_field_values_#{custom_field.id}" case custom_field.field_format when "date" - text_field('custom_value', 'value', :name => field_name, :id => field_id, :size => 10) + + text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) + calendar_for(field_id) when "text" - text_area 'custom_value', 'value', :name => field_name, :id => field_id, :rows => 3, :style => 'width:99%' + text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%') when "bool" - check_box 'custom_value', 'value', :name => field_name, :id => field_id + check_box_tag(field_name, '1', custom_value.true?, :id => field_id) + hidden_field_tag(field_name, '0') when "list" - select 'custom_value', 'value', custom_field.possible_values, { :include_blank => true }, :name => field_name, :id => field_id + blank_option = custom_field.is_required? ? + (custom_field.default_value.blank? ? "" : '') : + '' + select_tag(field_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id) else - text_field 'custom_value', 'value', :name => field_name, :id => field_id + text_field_tag(field_name, custom_value.value, :id => field_id) end end # Return custom field label tag - def custom_field_label_tag(custom_value) + def custom_field_label_tag(name, custom_value) content_tag "label", custom_value.custom_field.name + (custom_value.custom_field.is_required? ? " *" : ""), - :for => "custom_fields_#{custom_value.custom_field.id}", + :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}", :class => (custom_value.errors.empty? ? nil : "error" ) end # Return custom field tag with its label tag - def custom_field_tag_with_label(custom_value) - custom_field_label_tag(custom_value) + custom_field_tag(custom_value) + def custom_field_tag_with_label(name, custom_value) + custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value) end # Return a string used to display a custom value diff --git a/groups/app/helpers/issues_helper.rb b/groups/app/helpers/issues_helper.rb index 6013f1ec8..c6de00c10 100644 --- a/groups/app/helpers/issues_helper.rb +++ b/groups/app/helpers/issues_helper.rb @@ -54,9 +54,15 @@ module IssuesHelper when 'due_date', 'start_date' value = format_date(detail.value.to_date) if detail.value old_value = format_date(detail.old_value.to_date) if detail.old_value + when 'project_id' + p = Project.find_by_id(detail.value) and value = p.name if detail.value + p = Project.find_by_id(detail.old_value) and old_value = p.name if detail.old_value when 'status_id' s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value + when 'tracker_id' + t = Tracker.find_by_id(detail.value) and value = t.name if detail.value + t = Tracker.find_by_id(detail.old_value) and old_value = t.name if detail.old_value when 'assigned_to_id' u = User.find_by_id(detail.value) and value = u.name if detail.value u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value @@ -69,6 +75,9 @@ module IssuesHelper when 'fixed_version_id' v = Version.find_by_id(detail.value) and value = v.name if detail.value v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value + when 'estimated_hours' + value = "%0.02f" % detail.value.to_f unless detail.value.blank? + old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank? end when 'cf' custom_field = CustomField.find_by_id(detail.prop_key) @@ -89,9 +98,9 @@ module IssuesHelper label = content_tag('strong', label) old_value = content_tag("i", h(old_value)) if detail.old_value old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?) - if detail.property == 'attachment' && !value.blank? && Attachment.find_by_id(detail.prop_key) + if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key) # Link to the attachment if it has not been removed - value = link_to(value, :controller => 'attachments', :action => 'download', :id => detail.prop_key) + value = link_to_attachment(a) else value = content_tag("i", h(value)) if value end @@ -120,6 +129,7 @@ module IssuesHelper def issues_to_csv(issues, project = nil) ic = Iconv.new(l(:general_csv_encoding), 'UTF-8') + decimal_separator = l(:general_csv_decimal_separator) export = StringIO.new CSV::Writer.generate(export, l(:general_csv_separator)) do |csv| # csv header fields @@ -142,7 +152,7 @@ module IssuesHelper ] # Export project custom fields if project is given # otherwise export custom fields marked as "For all projects" - custom_fields = project.nil? ? IssueCustomField.for_all : project.all_custom_fields + custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields custom_fields.each {|f| headers << f.name} # Description in the last column headers << l(:field_description) @@ -162,7 +172,7 @@ module IssuesHelper format_date(issue.start_date), format_date(issue.due_date), issue.done_ratio, - issue.estimated_hours, + issue.estimated_hours.to_s.gsub('.', decimal_separator), format_time(issue.created_on), format_time(issue.updated_on) ] diff --git a/groups/app/helpers/journals_helper.rb b/groups/app/helpers/journals_helper.rb index 234bfabc0..45579f771 100644 --- a/groups/app/helpers/journals_helper.rb +++ b/groups/app/helpers/journals_helper.rb @@ -19,13 +19,16 @@ module JournalsHelper def render_notes(journal, options={}) content = '' editable = journal.editable_by?(User.current) - if editable && !journal.notes.blank? - links = [] + links = [] + if !journal.notes.blank? + links << link_to_remote(image_tag('comment.png'), + { :url => {:controller => 'issues', :action => 'reply', :id => journal.journalized, :journal_id => journal} }, + :title => l(:button_quote)) if options[:reply_links] links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes", { :controller => 'journals', :action => 'edit', :id => journal }, - :title => l(:button_edit)) - content << content_tag('div', links.join(' '), :class => 'contextual') + :title => l(:button_edit)) if editable end + content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty? content << textilizable(journal, :notes) content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => (editable ? 'wiki editable' : 'wiki')) end diff --git a/groups/app/helpers/mail_handler_helper.rb b/groups/app/helpers/mail_handler_helper.rb new file mode 100644 index 000000000..a29a6dd5a --- /dev/null +++ b/groups/app/helpers/mail_handler_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software +# Copyright (C) 2006-2008 Jean-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 MailHandlerHelper +end diff --git a/groups/app/helpers/projects_helper.rb b/groups/app/helpers/projects_helper.rb index 0a076be99..23e4ae6c7 100644 --- a/groups/app/helpers/projects_helper.rb +++ b/groups/app/helpers/projects_helper.rb @@ -21,18 +21,22 @@ module ProjectsHelper link_to h(version.name), { :controller => 'versions', :action => 'show', :id => version }, options end + def format_activity_title(text) + h(truncate_single_line(text, 100)) + end + def format_activity_day(date) date == Date.today ? l(:label_today).titleize : format_date(date) end def format_activity_description(text) - h(truncate(text, 250)) + h(truncate(text.to_s, 250).gsub(%r{<(pre|code)>.*$}m, '...')) end # Renders the member list displayed on project overview def render_member_list(project) parts = [] - memberships_by_role = project.memberships.find(:all, :include => :role, :order => 'position').group_by {|m| m.role} + memberships_by_role = project.memberships.find(:all, :include => :role, :order => "#{Role.table_name}.position").group_by {|m| m.role} memberships_by_role.keys.sort.each do |role| role_parts = [] # Display group name (with its 5 first users) or user name diff --git a/groups/app/helpers/repositories_helper.rb b/groups/app/helpers/repositories_helper.rb index 22bdec9df..852ed18d7 100644 --- a/groups/app/helpers/repositories_helper.rb +++ b/groups/app/helpers/repositories_helper.rb @@ -15,20 +15,23 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'coderay' -require 'coderay/helpers/file_type' require 'iconv' module RepositoriesHelper - def syntax_highlight(name, content) - type = CodeRay::FileType[name] - type ? CodeRay.scan(content, type).html : h(content) - end - def format_revision(txt) txt.to_s[0,8] end + def render_properties(properties) + unless properties.nil? || properties.empty? + content = '' + properties.keys.sort.each do |property| + content << content_tag('li', "#{h property}: #{h properties[property]}") + end + content_tag('ul', content, :class => 'properties') + end + end + def to_utf8(str) return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip) @@ -48,10 +51,13 @@ module RepositoriesHelper end def scm_select_tag(repository) - container = [[]] - REDMINE_SUPPORTED_SCM.each {|scm| container << ["Repository::#{scm}".constantize.scm_name, scm]} + scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']] + REDMINE_SUPPORTED_SCM.each do |scm| + scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm) + end + select_tag('repository_scm', - options_for_select(container, repository.class.name.demodulize), + options_for_select(scm_options, repository.class.name.demodulize), :disabled => (repository && !repository.new_record?), :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)") ) @@ -95,4 +101,8 @@ module RepositoriesHelper def bazaar_field_tags(form, repository) content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?))) end + + def filesystem_field_tags(form, repository) + content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) + end end diff --git a/groups/app/helpers/search_helper.rb b/groups/app/helpers/search_helper.rb index ed2f40b69..cd96dbd3f 100644 --- a/groups/app/helpers/search_helper.rb +++ b/groups/app/helpers/search_helper.rb @@ -18,7 +18,8 @@ module SearchHelper def highlight_tokens(text, tokens) return text unless text && tokens && !tokens.empty? - regexp = Regexp.new "(#{tokens.join('|')})", Regexp::IGNORECASE + re_tokens = tokens.collect {|t| Regexp.escape(t)} + regexp = Regexp.new "(#{re_tokens.join('|')})", Regexp::IGNORECASE result = '' text.split(regexp).each_with_index do |words, i| if result.length > 1200 @@ -35,4 +36,28 @@ module SearchHelper end result end + + def type_label(t) + l("label_#{t.singularize}_plural") + end + + def project_select_tag + options = [[l(:label_project_all), 'all']] + options << [l(:label_my_projects), 'my_projects'] unless User.current.memberships.empty? + options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.active_children.empty? + options << [@project.name, ''] unless @project.nil? + select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1 + end + + def render_results_by_type(results_by_type) + links = [] + # Sorts types by results count + results_by_type.keys.sort {|a, b| results_by_type[b] <=> results_by_type[a]}.each do |t| + c = results_by_type[t] + next if c == 0 + text = "#{type_label(t)} (#{c})" + links << link_to(text, :q => params[:q], :titles_only => params[:title_only], :all_words => params[:all_words], :scope => params[:scope], t => 1) + end + ('') unless links.empty? + end end diff --git a/groups/app/helpers/settings_helper.rb b/groups/app/helpers/settings_helper.rb index f4ec5a7a7..d88269f7d 100644 --- a/groups/app/helpers/settings_helper.rb +++ b/groups/app/helpers/settings_helper.rb @@ -21,6 +21,7 @@ module SettingsHelper {:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication}, {:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking}, {:name => 'notifications', :partial => 'settings/notifications', :label => l(:field_mail_notification)}, + {:name => 'mail_handler', :partial => 'settings/mail_handler', :label => l(:label_incoming_emails)}, {:name => 'repositories', :partial => 'settings/repositories', :label => :label_repository_plural} ] end diff --git a/groups/app/helpers/sort_helper.rb b/groups/app/helpers/sort_helper.rb index f16ff3c7d..9ca5c11bd 100644 --- a/groups/app/helpers/sort_helper.rb +++ b/groups/app/helpers/sort_helper.rb @@ -83,7 +83,7 @@ module SortHelper # Use this to sort the controller's table items collection. # def sort_clause() - session[@sort_name][:key] + ' ' + session[@sort_name][:order] + session[@sort_name][:key] + ' ' + (session[@sort_name][:order] || 'ASC') end # Returns a link which sorts by the named column. diff --git a/groups/app/helpers/timelog_helper.rb b/groups/app/helpers/timelog_helper.rb index db13556a1..2c1225a7c 100644 --- a/groups/app/helpers/timelog_helper.rb +++ b/groups/app/helpers/timelog_helper.rb @@ -16,6 +16,14 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module TimelogHelper + def activity_collection_for_select_options + activities = Enumeration::get_values('ACTI') + collection = [] + collection << [ "--- #{l(:actionview_instancetag_blank_option)} ---", '' ] unless activities.detect(&:is_default) + activities.each { |a| collection << [a.name, a.id] } + collection + end + def select_hours(data, criteria, value) data.select {|row| row[criteria] == value} end @@ -44,6 +52,8 @@ module TimelogHelper def entries_to_csv(entries) ic = Iconv.new(l(:general_csv_encoding), 'UTF-8') + decimal_separator = l(:general_csv_decimal_separator) + custom_fields = TimeEntryCustomField.find(:all) export = StringIO.new CSV::Writer.generate(export, l(:general_csv_separator)) do |csv| # csv header fields @@ -57,6 +67,9 @@ module TimelogHelper l(:field_hours), l(:field_comments) ] + # Export custom fields + headers += custom_fields.collect(&:name) + csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end } # csv lines entries.each do |entry| @@ -67,9 +80,11 @@ module TimelogHelper (entry.issue ? entry.issue.id : nil), (entry.issue ? entry.issue.tracker : nil), (entry.issue ? entry.issue.subject : nil), - entry.hours, + entry.hours.to_s.gsub('.', decimal_separator), entry.comments ] + fields += custom_fields.collect {|f| show_value(entry.custom_value_for(f)) } + csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end } end end diff --git a/groups/app/helpers/users_helper.rb b/groups/app/helpers/users_helper.rb index 250ed8ce8..5b113e880 100644 --- a/groups/app/helpers/users_helper.rb +++ b/groups/app/helpers/users_helper.rb @@ -16,11 +16,26 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module UsersHelper - def status_options_for_select(selected) + def users_status_options_for_select(selected) + user_count_by_status = User.count(:group => 'status').to_hash options_for_select([[l(:label_all), ''], - [l(:status_active), 1], - [l(:status_registered), 2], - [l(:status_locked), 3]], selected) + ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", 1], + ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", 2], + ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", 3]], selected) + end + + # Options for the new membership projects combo-box + def projects_options_for_select(projects) + options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") + projects_by_root = projects.group_by(&:root) + projects_by_root.keys.sort.each do |root| + options << content_tag('option', h(root.name), :value => root.id, :disabled => (!projects.include?(root))) + projects_by_root[root].sort.each do |project| + next if project == root + options << content_tag('option', '» ' + h(project.name), :value => project.id) + end + end + options end def change_status_link(user) @@ -30,8 +45,14 @@ module UsersHelper link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :post, :class => 'icon icon-unlock' elsif user.registered? link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :post, :class => 'icon icon-unlock' - else + elsif user != User.current link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :post, :class => 'icon icon-lock' end end + + def user_settings_tabs + tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general}, + {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural} + ] + end end diff --git a/groups/app/helpers/watchers_helper.rb b/groups/app/helpers/watchers_helper.rb index c83c785fc..f4767ebed 100644 --- a/groups/app/helpers/watchers_helper.rb +++ b/groups/app/helpers/watchers_helper.rb @@ -24,7 +24,7 @@ module WatchersHelper return '' unless user && user.logged? && object.respond_to?('watched_by?') watched = object.watched_by?(user) url = {:controller => 'watchers', - :action => (watched ? 'remove' : 'add'), + :action => (watched ? 'unwatch' : 'watch'), :object_type => object.class.to_s.underscore, :object_id => object.id} link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)), @@ -33,4 +33,9 @@ module WatchersHelper :class => (watched ? 'icon icon-fav' : 'icon icon-fav-off')) end + + # Returns a comma separated list of users watching the given object + def watchers_list(object) + object.watcher_users.collect {|u| content_tag('span', link_to_user(u), :class => 'user') }.join(",\n") + end end diff --git a/groups/app/helpers/wiki_helper.rb b/groups/app/helpers/wiki_helper.rb index 980035bd4..0a6b810de 100644 --- a/groups/app/helpers/wiki_helper.rb +++ b/groups/app/helpers/wiki_helper.rb @@ -17,6 +17,22 @@ module WikiHelper + def render_page_hierarchy(pages, node=nil) + content = '' + if pages[node] + content << "\n" + end + content + end + def html_diff(wdiff) words = wdiff.words.collect{|word| h(word)} words_add = 0 diff --git a/groups/app/models/attachment.rb b/groups/app/models/attachment.rb index 08f440816..95ba8491f 100644 --- a/groups/app/models/attachment.rb +++ b/groups/app/models/attachment.rb @@ -26,7 +26,19 @@ class Attachment < ActiveRecord::Base validates_length_of :disk_filename, :maximum => 255 acts_as_event :title => :filename, - :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id}} + :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}} + + acts_as_activity_provider :type => 'files', + :permission => :view_files, + :find_options => {:select => "#{Attachment.table_name}.*", + :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " + + "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id"} + + acts_as_activity_provider :type => 'documents', + :permission => :view_documents, + :find_options => {:select => "#{Attachment.table_name}.*", + :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " + + "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"} cattr_accessor :storage_path @@storage_path = "#{RAILS_ROOT}/files" @@ -40,7 +52,7 @@ class Attachment < ActiveRecord::Base @temp_file = incoming_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.disk_filename = Attachment.disk_filename(filename) self.content_type = @temp_file.content_type.to_s.chomp self.filesize = @temp_file.size end @@ -68,9 +80,7 @@ class Attachment < ActiveRecord::Base # Deletes file on the disk def after_destroy - if self.filename? - File.delete(diskfile) if File.exist?(diskfile) - end + File.delete(diskfile) if !filename.blank? && File.exist?(diskfile) end # Returns file's location on disk @@ -90,6 +100,14 @@ class Attachment < ActiveRecord::Base self.filename =~ /\.(jpe?g|gif|png)$/i end + def is_text? + Redmine::MimeType.is_type?('text', filename) + end + + def is_diff? + self.filename =~ /\.(patch|diff)$/i + end + private def sanitize_filename(value) # get only the filename, not the whole path @@ -100,4 +118,17 @@ private # Finally, replace all non alphanumeric, hyphens or periods with underscore @filename = just_filename.gsub(/[^\w\.\-]/,'_') end + + # Returns an ASCII or hashed filename + def self.disk_filename(filename) + df = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + if filename =~ %r{^[a-zA-Z0-9_\.\-]*$} + df << filename + else + df << Digest::MD5.hexdigest(filename) + # keep the extension if any + df << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$} + end + df + end end diff --git a/groups/app/models/auth_source.rb b/groups/app/models/auth_source.rb index 47c121a13..a0a2cdc5f 100644 --- a/groups/app/models/auth_source.rb +++ b/groups/app/models/auth_source.rb @@ -20,10 +20,7 @@ class AuthSource < ActiveRecord::Base validates_presence_of :name validates_uniqueness_of :name - validates_length_of :name, :host, :maximum => 60 - validates_length_of :account_password, :maximum => 60, :allow_nil => true - validates_length_of :account, :base_dn, :maximum => 255 - validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30 + validates_length_of :name, :maximum => 60 def authenticate(login, password) end diff --git a/groups/app/models/auth_source_ldap.rb b/groups/app/models/auth_source_ldap.rb index a438bd3c7..655ffd6d5 100644 --- a/groups/app/models/auth_source_ldap.rb +++ b/groups/app/models/auth_source_ldap.rb @@ -20,7 +20,10 @@ require 'iconv' class AuthSourceLdap < AuthSource validates_presence_of :host, :port, :attr_login - validates_presence_of :attr_firstname, :attr_lastname, :attr_mail, :if => Proc.new { |a| a.onthefly_register? } + validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true + validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true + validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true + validates_numericality_of :port, :only_integer => true def after_initialize self.port = 389 if self.port == 0 diff --git a/groups/app/models/change.rb b/groups/app/models/change.rb index d14f435a4..385fe5acb 100644 --- a/groups/app/models/change.rb +++ b/groups/app/models/change.rb @@ -19,4 +19,8 @@ class Change < ActiveRecord::Base belongs_to :changeset validates_presence_of :changeset_id, :action, :path + + def relative_path + changeset.repository.relative_path(path) + end end diff --git a/groups/app/models/changeset.rb b/groups/app/models/changeset.rb index 3e95ce111..c4258c88b 100644 --- a/groups/app/models/changeset.rb +++ b/groups/app/models/changeset.rb @@ -15,6 +15,8 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +require 'iconv' + class Changeset < ActiveRecord::Base belongs_to :repository has_many :changes, :dependent => :delete_all @@ -27,9 +29,12 @@ class Changeset < ActiveRecord::Base :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}} acts_as_searchable :columns => 'comments', - :include => :repository, + :include => {:repository => :project}, :project_key => "#{Repository.table_name}.project_id", :date_column => 'committed_on' + + acts_as_activity_provider :timestamp => "#{table_name}.committed_on", + :find_options => {:include => {:repository => :project}} validates_presence_of :repository_id, :revision, :committed_on, :commit_date validates_uniqueness_of :revision, :scope => :repository_id @@ -40,7 +45,7 @@ class Changeset < ActiveRecord::Base end def comments=(comment) - write_attribute(:comments, comment.strip) + write_attribute(:comments, Changeset.normalize_comments(comment)) end def committed_on=(date) @@ -75,7 +80,7 @@ class Changeset < ActiveRecord::Base if ref_keywords.delete('*') # find any issue ID in the comments target_issue_ids = [] - comments.scan(%r{([\s\(,-^])#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] } + comments.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] } referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids) end @@ -128,4 +133,24 @@ class Changeset < ActiveRecord::Base def next @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC') end + + # Strips and reencodes a commit log before insertion into the database + def self.normalize_comments(str) + to_utf8(str.to_s.strip) + end + + private + + def self.to_utf8(str) + return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii + encoding = Setting.commit_logs_encoding.to_s.strip + unless encoding.blank? || encoding == 'UTF-8' + begin + return Iconv.conv('UTF-8', encoding, str) + rescue Iconv::Failure + # do nothing here + end + end + str + end end diff --git a/groups/app/models/custom_field.rb b/groups/app/models/custom_field.rb index 990adf9e2..4759b714b 100644 --- a/groups/app/models/custom_field.rb +++ b/groups/app/models/custom_field.rb @@ -30,9 +30,9 @@ class CustomField < ActiveRecord::Base }.freeze validates_presence_of :name, :field_format - validates_uniqueness_of :name + validates_uniqueness_of :name, :scope => :type validates_length_of :name, :maximum => 30 - validates_format_of :name, :with => /^[\w\s\'\-]*$/i + validates_format_of :name, :with => /^[\w\s\.\'\-]*$/i validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys def initialize(attributes = nil) @@ -66,7 +66,7 @@ class CustomField < ActiveRecord::Base # to move in project_custom_field def self.for_all - find(:all, :conditions => ["is_for_all=?", true]) + find(:all, :conditions => ["is_for_all=?", true], :order => 'position') end def type_name diff --git a/groups/app/models/custom_value.rb b/groups/app/models/custom_value.rb index 98ce6b168..1d453baf0 100644 --- a/groups/app/models/custom_value.rb +++ b/groups/app/models/custom_value.rb @@ -25,6 +25,11 @@ class CustomValue < ActiveRecord::Base end end + # Returns true if the boolean custom value is true + def true? + self.value == '1' + end + protected def validate if value.blank? diff --git a/groups/app/models/document.rb b/groups/app/models/document.rb index 7a432b46b..627a2418f 100644 --- a/groups/app/models/document.rb +++ b/groups/app/models/document.rb @@ -20,11 +20,12 @@ class Document < ActiveRecord::Base belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id" has_many :attachments, :as => :container, :dependent => :destroy - acts_as_searchable :columns => ['title', 'description'] + acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"}, :author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC")) ? a.author : nil }, :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}} - + acts_as_activity_provider :find_options => {:include => :project} + validates_presence_of :project, :title, :category validates_length_of :title, :maximum => 60 end diff --git a/groups/app/models/enumeration.rb b/groups/app/models/enumeration.rb index 400681a43..d32a0c049 100644 --- a/groups/app/models/enumeration.rb +++ b/groups/app/models/enumeration.rb @@ -23,12 +23,12 @@ class Enumeration < ActiveRecord::Base validates_presence_of :opt, :name validates_uniqueness_of :name, :scope => [:opt] validates_length_of :name, :maximum => 30 - validates_format_of :name, :with => /^[\w\s\'\-]*$/i + # Single table inheritance would be an option OPTIONS = { - "IPRI" => :enumeration_issue_priorities, - "DCAT" => :enumeration_doc_categories, - "ACTI" => :enumeration_activities + "IPRI" => {:label => :enumeration_issue_priorities, :model => Issue, :foreign_key => :priority_id}, + "DCAT" => {:label => :enumeration_doc_categories, :model => Document, :foreign_key => :category_id}, + "ACTI" => {:label => :enumeration_activities, :model => TimeEntry, :foreign_key => :activity_id} }.freeze def self.get_values(option) @@ -40,13 +40,32 @@ class Enumeration < ActiveRecord::Base end def option_name - OPTIONS[self.opt] + OPTIONS[self.opt][:label] end def before_save Enumeration.update_all("is_default = #{connection.quoted_false}", {:opt => opt}) if is_default? end + def objects_count + OPTIONS[self.opt][:model].count(:conditions => "#{OPTIONS[self.opt][:foreign_key]} = #{id}") + end + + def in_use? + self.objects_count != 0 + end + + alias :destroy_without_reassign :destroy + + # Destroy the enumeration + # If a enumeration is specified, objects are reassigned + def destroy(reassign_to = nil) + if reassign_to && reassign_to.is_a?(Enumeration) + OPTIONS[self.opt][:model].update_all("#{OPTIONS[self.opt][:foreign_key]} = #{reassign_to.id}", "#{OPTIONS[self.opt][:foreign_key]} = #{id}") + end + destroy_without_reassign + end + def <=>(enumeration) position <=> enumeration.position end @@ -55,13 +74,6 @@ class Enumeration < ActiveRecord::Base private def check_integrity - case self.opt - when "IPRI" - raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id]) - when "DCAT" - raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id]) - when "ACTI" - raise "Can't delete enumeration" if TimeEntry.find(:first, :conditions => ["activity_id=?", self.id]) - end + raise "Can't delete enumeration" if self.in_use? end end diff --git a/groups/app/models/issue.rb b/groups/app/models/issue.rb index 8082e43b7..4701e41f1 100644 --- a/groups/app/models/issue.rb +++ b/groups/app/models/issue.rb @@ -28,23 +28,26 @@ class Issue < ActiveRecord::Base has_many :journals, :as => :journalized, :dependent => :destroy has_many :attachments, :as => :container, :dependent => :destroy has_many :time_entries, :dependent => :delete_all - has_many :custom_values, :dependent => :delete_all, :as => :customized - has_many :custom_fields, :through => :custom_values - has_and_belongs_to_many :changesets, :order => "revision ASC" + has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC" has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all + acts_as_customizable acts_as_watchable - acts_as_searchable :columns => ['subject', 'description'], :with => {:journal => :issue} + acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"], + :include => [:project, :journals], + # sort by id so that limited eager loading doesn't break with postgresql + :order_column => "#{table_name}.id" acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"}, :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}} + acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]} + validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status validates_length_of :subject, :maximum => 255 validates_inclusion_of :done_ratio, :in => 0..100 validates_numericality_of :estimated_hours, :allow_nil => true - validates_associated :custom_values, :on => :update def after_initialize if new_record? @@ -54,6 +57,11 @@ class Issue < ActiveRecord::Base end end + # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields + def available_custom_fields + (project && tracker) ? project.all_issue_custom_fields.select {|c| tracker.custom_fields.include? c } : [] + end + def copy_from(arg) issue = arg.is_a?(Issue) ? arg : Issue.find(arg) self.attributes = issue.attributes.dup @@ -71,7 +79,9 @@ class Issue < ActiveRecord::Base self.relations_to.clear end # issue is moved to another project - self.category = nil + # reassign to the category with same name if any + new_category = category.nil? ? nil : new_project.issue_categories.find_by_name(category.name) + self.category = new_category self.fixed_version = nil self.project = new_project end @@ -168,17 +178,14 @@ class Issue < ActiveRecord::Base end end - def custom_value_for(custom_field) - self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id } - return nil - end - def init_journal(user, notes = "") @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes) @issue_before_change = self.clone @issue_before_change.status = self.status @custom_values_before_change = {} self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value } + # Make sure updated_on is updated when adding a note. + updated_on_will_change! @current_journal end @@ -225,9 +232,15 @@ class Issue < ActiveRecord::Base dependencies end - # Returns an array of the duplicate issues + # Returns an array of issues that duplicate this one def duplicates - relations.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.other_issue(self)} + relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from} + end + + # Returns the due date or the target due date if any + # Used on gantt chart + def due_before + due_date || (fixed_version ? fixed_version.effective_date : nil) end def duration diff --git a/groups/app/models/issue_relation.rb b/groups/app/models/issue_relation.rb index 07e940b85..49329e0bb 100644 --- a/groups/app/models/issue_relation.rb +++ b/groups/app/models/issue_relation.rb @@ -25,7 +25,7 @@ class IssueRelation < ActiveRecord::Base TYPE_PRECEDES = "precedes" TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 }, - TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicates, :order => 2 }, + TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 }, TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 }, TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 }, }.freeze diff --git a/groups/app/models/journal.rb b/groups/app/models/journal.rb index 1376d349e..71a51290b 100644 --- a/groups/app/models/journal.rb +++ b/groups/app/models/journal.rb @@ -25,17 +25,18 @@ class Journal < ActiveRecord::Base has_many :details, :class_name => "JournalDetail", :dependent => :delete_all attr_accessor :indice - acts_as_searchable :columns => 'notes', - :include => :issue, - :project_key => "#{Issue.table_name}.project_id", - :date_column => "#{Issue.table_name}.created_on" - - acts_as_event :title => Proc.new {|o| "#{o.issue.tracker.name} ##{o.issue.id}: #{o.issue.subject}" + ((s = o.new_status) ? " (#{s})" : '') }, + acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" }, :description => :notes, :author => :user, - :type => Proc.new {|o| (s = o.new_status) && s.is_closed? ? 'issue-closed' : 'issue-edit' }, + :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' }, :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}} + acts_as_activity_provider :type => 'issues', + :permission => :view_issues, + :find_options => {:include => [{:issue => :project}, :details, :user], + :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" + + " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} + def save # Do not save an empty journal (details.empty? && notes.blank?) ? false : super diff --git a/groups/app/models/mail_handler.rb b/groups/app/models/mail_handler.rb index 7a1d73244..2f1eba3e9 100644 --- a/groups/app/models/mail_handler.rb +++ b/groups/app/models/mail_handler.rb @@ -16,25 +16,138 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class MailHandler < ActionMailer::Base + + class UnauthorizedAction < StandardError; end + class MissingInformation < StandardError; end + + attr_reader :email, :user + + def self.receive(email, options={}) + @@handler_options = options.dup + + @@handler_options[:issue] ||= {} + + @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String) + @@handler_options[:allow_override] ||= [] + # Project needs to be overridable if not specified + @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project) + # Status needs to be overridable if not specified + @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status) + super email + end # Processes incoming emails - # Currently, it only supports adding a note to an existing issue - # by replying to the initial notification message def receive(email) - # find related issue by parsing the subject - m = email.subject.match %r{\[.*#(\d+)\]} - return unless m - issue = Issue.find_by_id(m[1]) - return unless issue - - # find user - user = User.find_active(:first, :conditions => {:mail => email.from.first}) - return unless user + @email = email + @user = User.find_active(:first, :conditions => {:mail => email.from.first}) + unless @user + # Unknown user => the email is ignored + # TODO: ability to create the user's account + logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info + return false + end + User.current = @user + dispatch + end + + private + + ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]} + + def dispatch + if m = email.subject.match(ISSUE_REPLY_SUBJECT_RE) + receive_issue_update(m[1].to_i) + else + receive_issue + end + rescue ActiveRecord::RecordInvalid => e + # TODO: send a email to the user + logger.error e.message if logger + false + rescue MissingInformation => e + logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger + false + rescue UnauthorizedAction => e + logger.error "MailHandler: unauthorized attempt from #{user}" if logger + false + end + + # Creates a new issue + def receive_issue + project = target_project + tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first) + category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category))) + priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority))) + status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status))) || IssueStatus.default + # check permission - return unless user.allowed_to?(:add_issue_notes, issue.project) + raise UnauthorizedAction unless user.allowed_to?(:add_issues, project) + issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority, :status => status) + issue.subject = email.subject.chomp + issue.description = email.plain_text_body.chomp + issue.save! + add_attachments(issue) + logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info + Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added') + issue + end + + def target_project + # TODO: other ways to specify project: + # * parse the email To field + # * specific project (eg. Setting.mail_handler_target_project) + target = Project.find_by_identifier(get_keyword(:project)) + raise MissingInformation.new('Unable to determine target project') if target.nil? + target + end + + # Adds a note to an existing issue + def receive_issue_update(issue_id) + status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status))) + issue = Issue.find_by_id(issue_id) + return unless issue + # check permission + raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project) + raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project) + # add the note - issue.init_journal(user, email.body.chomp) - issue.save + journal = issue.init_journal(user, email.plain_text_body.chomp) + add_attachments(issue) + issue.status = status unless status.nil? + issue.save! + logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info + Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated') + journal + end + + def add_attachments(obj) + if email.has_attachments? + email.attachments.each do |attachment| + Attachment.create(:container => obj, + :file => attachment, + :author => user, + :content_type => attachment.content_type) + end + end + end + + def get_keyword(attr) + if @@handler_options[:allow_override].include?(attr.to_s) && email.plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i + $1.strip + elsif !@@handler_options[:issue][attr].blank? + @@handler_options[:issue][attr] + end end end + +class TMail::Mail + # Returns body of the first plain text part found if any + def plain_text_body + return @plain_text_body unless @plain_text_body.nil? + p = self.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten + plain = p.detect {|c| c.content_type == 'text/plain'} + @plain_text_body = plain.nil? ? self.body : plain.body + end +end + diff --git a/groups/app/models/mailer.rb b/groups/app/models/mailer.rb index 6fc879a15..61e5d596c 100644 --- a/groups/app/models/mailer.rb +++ b/groups/app/models/mailer.rb @@ -51,6 +51,15 @@ class Mailer < ActionMailer::Base :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue) end + def reminder(user, issues, days) + set_language_if_valid user.language + recipients user.mail + subject l(:mail_subject_reminder, issues.size) + body :issues => issues, + :days => days, + :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'issues.due_date', :sort_order => 'asc') + end + def document_added(document) redmine_headers 'Project' => document.project.identifier recipients document.project.recipients @@ -144,6 +153,30 @@ class Mailer < ActionMailer::Base (bcc.nil? || bcc.empty?) super end + + # Sends reminders to issue assignees + # Available options: + # * :days => how many days in the future to remind about (defaults to 7) + # * :tracker => id of tracker for filtering issues (defaults to all trackers) + # * :project => id or identifier of project to process (defaults to all projects) + def self.reminders(options={}) + days = options[:days] || 7 + project = options[:project] ? Project.find(options[:project]) : nil + tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil + + s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date] + s << "#{Issue.table_name}.assigned_to_id IS NOT NULL" + s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}" + s << "#{Issue.table_name}.project_id = #{project.id}" if project + s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker + + issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker], + :conditions => s.conditions + ).group_by(&:assigned_to) + issues_by_assignee.each do |assignee, issues| + deliver_reminder(assignee, issues, days) unless assignee.nil? + end + end private def initialize_defaults(method_name) diff --git a/groups/app/models/message.rb b/groups/app/models/message.rb index a18d126c9..80df7a33a 100644 --- a/groups/app/models/message.rb +++ b/groups/app/models/message.rb @@ -23,14 +23,17 @@ class Message < ActiveRecord::Base belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id' acts_as_searchable :columns => ['subject', 'content'], - :include => :board, + :include => {:board, :project}, :project_key => 'project_id', - :date_column => 'created_on' + :date_column => "#{table_name}.created_on" acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, :description => :content, :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'}, - :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id, :id => o.id}} - + :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} : + {:id => o.parent_id, :anchor => "message-#{o.id}"})} + + acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]} + attr_protected :locked, :sticky validates_presence_of :subject, :content validates_length_of :subject, :maximum => 255 diff --git a/groups/app/models/news.rb b/groups/app/models/news.rb index 3d8c4d661..4c4943b78 100644 --- a/groups/app/models/news.rb +++ b/groups/app/models/news.rb @@ -24,9 +24,10 @@ class News < ActiveRecord::Base validates_length_of :title, :maximum => 60 validates_length_of :summary, :maximum => 255 - acts_as_searchable :columns => ['title', 'description'] + acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}} - + acts_as_activity_provider :find_options => {:include => [:project, :author]} + # returns latest news for projects visible by user def self.latest(user=nil, count=5) find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") diff --git a/groups/app/models/project.rb b/groups/app/models/project.rb index 3deac3231..f7feb7349 100644 --- a/groups/app/models/project.rb +++ b/groups/app/models/project.rb @@ -23,7 +23,6 @@ class Project < ActiveRecord::Base has_many :members, :include => :user, :conditions => "#{Member.table_name}.principal_type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}" has_many :memberships, :class_name => 'Member' has_many :users, :through => :members, :uniq => true - has_many :custom_values, :dependent => :delete_all, :as => :customized has_many :enabled_modules, :dependent => :delete_all has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position" has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker] @@ -39,7 +38,7 @@ class Project < ActiveRecord::Base has_many :changesets, :through => :repository has_one :wiki, :dependent => :destroy # Custom field for the project issues - has_and_belongs_to_many :custom_fields, + has_and_belongs_to_many :issue_custom_fields, :class_name => 'IssueCustomField', :order => "#{CustomField.table_name}.position", :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", @@ -47,18 +46,19 @@ class Project < ActiveRecord::Base acts_as_tree :order => "name", :counter_cache => true - acts_as_searchable :columns => ['name', 'description'], :project_key => 'id' + acts_as_customizable + acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"}, - :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}} + :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}}, + :author => nil attr_protected :status, :enabled_module_names validates_presence_of :name, :identifier validates_uniqueness_of :name, :identifier - validates_associated :custom_values, :on => :update validates_associated :repository, :wiki validates_length_of :name, :maximum => 30 - validates_length_of :homepage, :maximum => 60 + validates_length_of :homepage, :maximum => 255 validates_length_of :identifier, :in => 3..20 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/ @@ -74,9 +74,9 @@ class Project < ActiveRecord::Base def issues_with_subprojects(include_subprojects=false) conditions = nil - if include_subprojects && !active_children.empty? - ids = [id] + active_children.collect {|c| c.id} - conditions = ["#{Project.table_name}.id IN (#{ids.join(',')})"] + if include_subprojects + ids = [id] + child_ids + conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"] end conditions ||= ["#{Project.table_name}.id = ?", id] # Quick and dirty fix for Rails 2 compatibility @@ -94,6 +94,7 @@ class Project < ActiveRecord::Base end def self.visible_by(user=nil) + user ||= User.current if user && user.admin? return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" elsif user && user.memberships.any? @@ -113,16 +114,18 @@ class Project < ActiveRecord::Base end if user.admin? # no restriction - elsif user.logged? - statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission) - allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id} - statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any? - elsif Role.anonymous.allowed_to?(permission) - # anonymous user allowed on public project - statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" else - # anonymous user is not authorized statements << "1=0" + if user.logged? + statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission) + allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id} + statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any? + elsif Role.anonymous.allowed_to?(permission) + # anonymous user allowed on public project + statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" + else + # anonymous user is not authorized + end end statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))" end @@ -144,7 +147,8 @@ class Project < ActiveRecord::Base end def to_param - identifier + # id is used for projects with a numeric identifier (compatibility) + @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier) end def active? @@ -194,12 +198,12 @@ class Project < ActiveRecord::Base # Returns an array of all custom fields enabled for project issues # (explictly associated custom fields and custom fields enabled for all projects) - def custom_fields_for_issues(tracker) - all_custom_fields.select {|c| tracker.custom_fields.include? c } + def all_issue_custom_fields + @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort end - def all_custom_fields - @all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq + def project + self end def <=>(project) diff --git a/groups/app/models/query.rb b/groups/app/models/query.rb index 641c0d17b..0ce9a6a21 100644 --- a/groups/app/models/query.rb +++ b/groups/app/models/query.rb @@ -88,7 +88,7 @@ class Query < ActiveRecord::Base :date_past => [ ">t-", " [ "=", "~", "!", "!~" ], :text => [ "~", "!~" ], - :integer => [ "=", ">=", "<=" ] } + :integer => [ "=", ">=", "<=", "!*", "*" ] } cattr_reader :operators_by_filter_type @@ -125,7 +125,7 @@ class Query < ActiveRecord::Base filters.each_key do |field| errors.add label_for(field), :activerecord_error_blank unless # filter requires one or more values - (values_for(field) and !values_for(field).first.empty?) or + (values_for(field) and !values_for(field).first.blank?) or # filter doesn't require any value ["o", "c", "!*", "*", "t", "w"].include? operator_for(field) end if filters @@ -152,7 +152,8 @@ class Query < ActiveRecord::Base "updated_on" => { :type => :date_past, :order => 10 }, "start_date" => { :type => :date, :order => 11 }, "due_date" => { :type => :date, :order => 12 }, - "done_ratio" => { :type => :integer, :order => 13 }} + "estimated_hours" => { :type => :integer, :order => 13 }, + "done_ratio" => { :type => :integer, :order => 14 }} user_values = [] user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? @@ -166,29 +167,20 @@ class Query < ActiveRecord::Base @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty? if project - # project specific filters - @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } } - @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } } + # project specific filters + unless @project.issue_categories.empty? + @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } } + end + unless @project.versions.empty? + @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } } + end unless @project.active_children.empty? @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } } end - @project.all_custom_fields.select(&:is_filter?).each do |field| - case field.field_format - when "text" - options = { :type => :text, :order => 20 } - when "list" - options = { :type => :list_optional, :values => field.possible_values, :order => 20} - when "date" - options = { :type => :date, :order => 20 } - when "bool" - options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } - else - options = { :type => :string, :order => 20 } - end - @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name }) - end - # remove category filter if no category defined - @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty? + add_custom_fields_filters(@project.all_issue_custom_fields) + else + # global filters for cross project issue list + add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true})) end @available_filters end @@ -227,7 +219,7 @@ class Query < ActiveRecord::Base end def label_for(field) - label = @available_filters[field][:name] if @available_filters.has_key?(field) + label = available_filters[field][:name] if available_filters.has_key?(field) label ||= field.gsub(/\_id$/, "") end @@ -235,7 +227,7 @@ class Query < ActiveRecord::Base return @available_columns if @available_columns @available_columns = Query.available_columns @available_columns += (project ? - project.all_custom_fields : + project.all_issue_custom_fields : IssueCustomField.find(:all, :conditions => {:is_for_all => true}) ).collect {|cf| QueryCustomFieldColumn.new(cf) } end @@ -265,7 +257,7 @@ class Query < ActiveRecord::Base def statement # project/subprojects clause - clause = '' + project_clauses = [] if project && !@project.active_children.empty? ids = [project.id] if has_filter?("subproject_id") @@ -277,17 +269,16 @@ class Query < ActiveRecord::Base # main project only else # all subprojects - ids += project.active_children.collect{|p| p.id} + ids += project.child_ids end elsif Setting.display_subprojects_issues? - ids += project.active_children.collect{|p| p.id} + ids += project.child_ids end - clause << "#{Issue.table_name}.project_id IN (%s)" % ids.join(',') + project_clauses << "#{Issue.table_name}.project_id IN (%s)" % ids.join(',') elsif project - clause << "#{Issue.table_name}.project_id = %d" % project.id - else - clause << Project.visible_by(User.current) + project_clauses << "#{Issue.table_name}.project_id = %d" % project.id end + project_clauses << Project.visible_by(User.current) # filters clauses filters_clauses = [] @@ -296,11 +287,13 @@ class Query < ActiveRecord::Base v = values_for(field).clone next unless v and !v.empty? - sql = '' + sql = '' + is_custom_filter = false if field =~ /^cf_(\d+)$/ # custom field db_table = CustomValue.table_name db_field = 'value' + is_custom_filter = true sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE " else # regular field @@ -320,9 +313,11 @@ class Query < ActiveRecord::Base when "!" sql = sql + "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" when "!*" - sql = sql + "#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} = ''" + sql = sql + "#{db_table}.#{db_field} IS NULL" + sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter when "*" - sql = sql + "#{db_table}.#{db_field} IS NOT NULL AND #{db_table}.#{db_field} <> ''" + sql = sql + "#{db_table}.#{db_field} IS NOT NULL" + sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter when ">=" sql = sql + "#{db_table}.#{db_field} >= #{v.first.to_i}" when "<=" @@ -361,8 +356,28 @@ class Query < ActiveRecord::Base filters_clauses << sql end if filters and valid? - clause << ' AND ' unless clause.empty? - clause << filters_clauses.join(' AND ') unless filters_clauses.empty? - clause + (project_clauses + filters_clauses).join(' AND ') + end + + private + + def add_custom_fields_filters(custom_fields) + @available_filters ||= {} + + custom_fields.select(&:is_filter?).each do |field| + case field.field_format + when "text" + options = { :type => :text, :order => 20 } + when "list" + options = { :type => :list_optional, :values => field.possible_values, :order => 20} + when "date" + options = { :type => :date, :order => 20 } + when "bool" + options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } + else + options = { :type => :string, :order => 20 } + end + @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name }) + end end end diff --git a/groups/app/models/repository.rb b/groups/app/models/repository.rb index 8b1f8d0af..9768e3e3c 100644 --- a/groups/app/models/repository.rb +++ b/groups/app/models/repository.rb @@ -17,9 +17,16 @@ class Repository < ActiveRecord::Base belongs_to :project - has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" + has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" has_many :changes, :through => :changesets - + + # Raw SQL to delete changesets and changes in the database + # has_many :changesets, :dependent => :destroy is too slow for big repositories + before_destroy :clear_changesets + + # Checks if the SCM is enabled when creating a repository + validate_on_create { |r| r.errors.add(:type, :activerecord_error_invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) } + # Removes leading and trailing whitespace def url=(arg) write_attribute(:url, arg ? arg.to_s.strip : nil) @@ -48,12 +55,24 @@ class Repository < ActiveRecord::Base scm.supports_annotate? end + def entry(path=nil, identifier=nil) + scm.entry(path, identifier) + end + def entries(path=nil, identifier=nil) scm.entries(path, identifier) end - def diff(path, rev, rev_to, type) - scm.diff(path, rev, rev_to, type) + def properties(path, identifier=nil) + scm.properties(path, identifier) + end + + def cat(path, identifier=nil) + scm.cat(path, identifier) + end + + def diff(path, rev, rev_to) + scm.diff(path, rev, rev_to) end # Default behaviour: we search in cached changesets @@ -64,6 +83,11 @@ class Repository < ActiveRecord::Base :order => "committed_on DESC, #{Changeset.table_name}.id DESC").collect(&:changeset) end + # Returns a path relative to the url of the repository + def relative_path(path) + path + end + def latest_changeset @latest_changeset ||= changesets.find(:first) end @@ -107,4 +131,9 @@ class Repository < ActiveRecord::Base root_url.strip! true end + + def clear_changesets + connection.delete("DELETE FROM changes WHERE changes.changeset_id IN (SELECT changesets.id FROM changesets WHERE changesets.repository_id = #{id})") + connection.delete("DELETE FROM changesets WHERE changesets.repository_id = #{id}") + end end diff --git a/groups/app/models/repository/bazaar.rb b/groups/app/models/repository/bazaar.rb index 1b75066c2..ec953bd45 100644 --- a/groups/app/models/repository/bazaar.rb +++ b/groups/app/models/repository/bazaar.rb @@ -34,6 +34,11 @@ class Repository::Bazaar < Repository if entries entries.each do |e| next if e.lastrev.revision.blank? + # Set the filesize unless browsing a specific revision + if identifier.nil? && e.is_file? + full_path = File.join(root_url, e.path) + e.size = File.stat(full_path).size if File.file?(full_path) + end c = Change.find(:first, :include => :changeset, :conditions => ["#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", e.lastrev.revision, id], diff --git a/groups/app/models/repository/cvs.rb b/groups/app/models/repository/cvs.rb index c2d8be977..82082b3d6 100644 --- a/groups/app/models/repository/cvs.rb +++ b/groups/app/models/repository/cvs.rb @@ -29,9 +29,9 @@ class Repository::Cvs < Repository 'CVS' end - def entry(path, identifier) - e = entries(path, identifier) - e ? e.first : nil + def entry(path=nil, identifier=nil) + rev = identifier.nil? ? nil : changesets.find_by_revision(identifier) + scm.entry(path, rev.nil? ? nil : rev.committed_on) end def entries(path=nil, identifier=nil) @@ -53,7 +53,12 @@ class Repository::Cvs < Repository entries end - def diff(path, rev, rev_to, type) + def cat(path, identifier=nil) + rev = identifier.nil? ? nil : changesets.find_by_revision(identifier) + scm.cat(path, rev.nil? ? nil : rev.committed_on) + end + + def diff(path, rev, rev_to) #convert rev to revision. CVS can't handle changesets here diff=[] changeset_from=changesets.find_by_revision(rev) @@ -76,7 +81,8 @@ class Repository::Cvs < Repository unless revision_to revision_to=scm.get_previous_revision(revision_from) end - diff=diff+scm.diff(change_from.path, revision_from, revision_to, type) + file_diff = scm.diff(change_from.path, revision_from, revision_to) + diff = diff + file_diff unless file_diff.nil? end end return diff @@ -103,7 +109,7 @@ class Repository::Cvs < Repository cs = changesets.find(:first, :conditions=>{ :committed_on=>revision.time-time_delta..revision.time+time_delta, :committer=>revision.author, - :comments=>revision.message + :comments=>Changeset.normalize_comments(revision.message) }) # create a new changeset.... diff --git a/groups/app/models/repository/darcs.rb b/groups/app/models/repository/darcs.rb index c7c14a397..855a403fc 100644 --- a/groups/app/models/repository/darcs.rb +++ b/groups/app/models/repository/darcs.rb @@ -28,6 +28,11 @@ class Repository::Darcs < Repository 'Darcs' end + def entry(path=nil, identifier=nil) + patch = identifier.nil? ? nil : changesets.find_by_revision(identifier) + scm.entry(path, patch.nil? ? nil : patch.scmid) + end + def entries(path=nil, identifier=nil) patch = identifier.nil? ? nil : changesets.find_by_revision(identifier) entries = scm.entries(path, patch.nil? ? nil : patch.scmid) @@ -46,14 +51,19 @@ class Repository::Darcs < Repository entries end - def diff(path, rev, rev_to, type) + def cat(path, identifier=nil) + patch = identifier.nil? ? nil : changesets.find_by_revision(identifier) + scm.cat(path, patch.nil? ? nil : patch.scmid) + end + + def diff(path, rev, rev_to) patch_from = changesets.find_by_revision(rev) return nil if patch_from.nil? patch_to = changesets.find_by_revision(rev_to) if rev_to if path.blank? path = patch_from.changes.collect{|change| change.path}.join(' ') end - patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil, type) : nil + patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil) : nil end def fetch_changesets diff --git a/groups/app/models/repository/filesystem.rb b/groups/app/models/repository/filesystem.rb new file mode 100644 index 000000000..da096cc09 --- /dev/null +++ b/groups/app/models/repository/filesystem.rb @@ -0,0 +1,43 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# +# FileSystem adapter +# File written by Paul Rivier, at Demotera. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/filesystem_adapter' + +class Repository::Filesystem < Repository + attr_protected :root_url + validates_presence_of :url + + def scm_adapter + Redmine::Scm::Adapters::FilesystemAdapter + end + + def self.scm_name + 'Filesystem' + end + + def entries(path=nil, identifier=nil) + scm.entries(path, identifier) + end + + def fetch_changesets + nil + end + +end diff --git a/groups/app/models/repository/git.rb b/groups/app/models/repository/git.rb index 7213588ac..2f440fe29 100644 --- a/groups/app/models/repository/git.rb +++ b/groups/app/models/repository/git.rb @@ -44,10 +44,8 @@ class Repository::Git < Repository scm_revision = scm_info.lastrev.scmid unless changesets.find_by_scmid(scm_revision) - - revisions = scm.revisions('', db_revision, nil) - transaction do - revisions.reverse_each do |revision| + scm.revisions('', db_revision, nil, :reverse => true) do |revision| + transaction do changeset = Changeset.create(:repository => self, :revision => revision.identifier, :scmid => revision.scmid, diff --git a/groups/app/models/repository/subversion.rb b/groups/app/models/repository/subversion.rb index 0c2239c43..3981d6f4c 100644 --- a/groups/app/models/repository/subversion.rb +++ b/groups/app/models/repository/subversion.rb @@ -35,6 +35,11 @@ class Repository::Subversion < Repository revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC") : [] end + # Returns a path relative to the url of the repository + def relative_path(path) + path.gsub(Regexp.new("^\/?#{Regexp.escape(relative_url)}"), '') + end + def fetch_changesets scm_info = scm.info if scm_info @@ -71,4 +76,14 @@ class Repository::Subversion < Repository end end end + + private + + # Returns the relative url of the repository + # Eg: root_url = file:///var/svn/foo + # url = file:///var/svn/foo/bar + # => returns /bar + def relative_url + @relative_url ||= url.gsub(Regexp.new("^#{Regexp.escape(root_url)}"), '') + end end diff --git a/groups/app/models/setting.rb b/groups/app/models/setting.rb index 185991d9b..072afa0db 100644 --- a/groups/app/models/setting.rb +++ b/groups/app/models/setting.rb @@ -33,6 +33,45 @@ class Setting < ActiveRecord::Base '%H:%M', '%I:%M %p' ] + + ENCODINGS = %w(US-ASCII + windows-1250 + windows-1251 + windows-1252 + windows-1253 + windows-1254 + windows-1255 + windows-1256 + windows-1257 + windows-1258 + windows-31j + ISO-2022-JP + ISO-2022-KR + ISO-8859-1 + ISO-8859-2 + ISO-8859-3 + ISO-8859-4 + ISO-8859-5 + ISO-8859-6 + ISO-8859-7 + ISO-8859-8 + ISO-8859-9 + ISO-8859-13 + ISO-8859-15 + KOI8-R + UTF-8 + UTF-16 + UTF-16BE + UTF-16LE + EUC-JP + Shift_JIS + GB18030 + GBK + ISCII91 + EUC-KR + Big5 + Big5-HKSCS + TIS-620) cattr_accessor :available_settings @@available_settings = YAML::load(File.open("#{RAILS_ROOT}/config/settings.yml")) diff --git a/groups/app/models/time_entry.rb b/groups/app/models/time_entry.rb index ddaff2b60..57a75604d 100644 --- a/groups/app/models/time_entry.rb +++ b/groups/app/models/time_entry.rb @@ -24,11 +24,25 @@ class TimeEntry < ActiveRecord::Base belongs_to :activity, :class_name => 'Enumeration', :foreign_key => :activity_id attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek + + acts_as_customizable + acts_as_event :title => Proc.new {|o| "#{o.user}: #{lwr(:label_f_hour, o.hours)} (#{(o.issue || o.project).event_title})"}, + :url => Proc.new {|o| {:controller => 'timelog', :action => 'details', :project_id => o.project}}, + :author => :user, + :description => :comments validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on validates_numericality_of :hours, :allow_nil => true - validates_length_of :comments, :maximum => 255 + validates_length_of :comments, :maximum => 255, :allow_nil => true + def after_initialize + if new_record? && self.activity.nil? + if default_activity = Enumeration.default('ACTI') + self.activity_id = default_activity.id + end + end + end + def before_validation self.project = issue.project if issue && project.nil? end diff --git a/groups/app/models/time_entry_custom_field.rb b/groups/app/models/time_entry_custom_field.rb new file mode 100644 index 000000000..2ec3d27be --- /dev/null +++ b/groups/app/models/time_entry_custom_field.rb @@ -0,0 +1,23 @@ +# redMine - project management software +# Copyright (C) 2008 Jean-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 TimeEntryCustomField < CustomField + def type_name + :label_spent_time + end +end + diff --git a/groups/app/models/user.rb b/groups/app/models/user.rb index 3743fcb3f..0e02cb78c 100644 --- a/groups/app/models/user.rb +++ b/groups/app/models/user.rb @@ -19,8 +19,6 @@ require "digest/sha1" class User < ActiveRecord::Base - class OnTheFlyCreationFailure < Exception; end - # Account statuses STATUS_ANONYMOUS = 0 STATUS_ACTIVE = 1 @@ -39,17 +37,17 @@ class User < ActiveRecord::Base :as => :principal, :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", - :order => "#{Project.table_name}.name, inherited_from ASC", - :dependent => :delete_all - + :order => "#{Project.table_name}.name, inherited_from ASC" + has_many :members, :as => :principal, :dependent => :delete_all has_many :projects, :through => :memberships - has_many :custom_values, :dependent => :delete_all, :as => :customized has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'" belongs_to :auth_source belongs_to :group + acts_as_customizable + attr_accessor :password, :password_confirmation attr_accessor :last_before_login_on # Prevents unauthorized assignments @@ -61,13 +59,12 @@ class User < ActiveRecord::Base # Login must contain lettres, numbers, underscores only validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i validates_length_of :login, :maximum => 30 - validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-]*$/i + validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i validates_length_of :firstname, :lastname, :maximum => 30 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true validates_length_of :mail, :maximum => 60, :allow_nil => true validates_length_of :password, :minimum => 4, :allow_nil => true validates_confirmation_of :password, :allow_nil => true - validates_associated :custom_values, :on => :update def before_create self.mail_notification = false @@ -96,6 +93,7 @@ class User < ActiveRecord::Base def group_id=(gid) @group_changed = true unless gid == group_id + group_id_will_change! write_attribute(:group_id, gid) end @@ -130,19 +128,16 @@ class User < ActiveRecord::Base # user is not yet registered, try to authenticate with available sources attrs = AuthSource.authenticate(login, password) if attrs - onthefly = new(*attrs) - onthefly.login = login - onthefly.language = Setting.default_language - if onthefly.save - user = find(:first, :conditions => ["login=?", login]) + user = new(*attrs) + user.login = login + user.language = Setting.default_language + if user.save + user.reload logger.info("User '#{user.login}' created from the LDAP") if logger - else - logger.error("User '#{onthefly.login}' found in LDAP but could not be created (#{onthefly.errors.full_messages.join(', ')})") if logger - raise OnTheFlyCreationFailure.new end end end - user.update_attribute(:last_login_on, Time.now) if user + user.update_attribute(:last_login_on, Time.now) if user && !user.new_record? user rescue => text raise text @@ -228,6 +223,10 @@ class User < ActiveRecord::Base true end + def anonymous? + !logged? + end + # Return user's role for project def role_for_project(project) # No role on archived projects @@ -285,13 +284,12 @@ class User < ActiveRecord::Base end def self.anonymous - return @anonymous_user if @anonymous_user anonymous_user = AnonymousUser.find(:first) if anonymous_user.nil? anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0) raise 'Unable to create the anonymous user.' if anonymous_user.new_record? end - @anonymous_user = anonymous_user + anonymous_user end private @@ -308,6 +306,10 @@ class AnonymousUser < User errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first) end + def available_custom_fields + [] + end + # Overrides a few properties def logged?; false end def admin; false end diff --git a/groups/app/models/user_preference.rb b/groups/app/models/user_preference.rb index 73e4a50c6..3daa7a740 100644 --- a/groups/app/models/user_preference.rb +++ b/groups/app/models/user_preference.rb @@ -42,8 +42,10 @@ class UserPreference < ActiveRecord::Base if attribute_present? attr_name super else - self.others ||= {} - self.others.store attr_name, value + h = read_attribute(:others).dup || {} + h.update(attr_name => value) + write_attribute(:others, h) + value end end diff --git a/groups/app/models/watcher.rb b/groups/app/models/watcher.rb index cb6ff52ea..38110c584 100644 --- a/groups/app/models/watcher.rb +++ b/groups/app/models/watcher.rb @@ -19,5 +19,12 @@ class Watcher < ActiveRecord::Base belongs_to :watchable, :polymorphic => true belongs_to :user + validates_presence_of :user validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id] + + protected + + def validate + errors.add :user_id, :activerecord_error_invalid unless user.nil? || user.active? + end end diff --git a/groups/app/models/wiki.rb b/groups/app/models/wiki.rb index b6d6a9b50..3432a2bc7 100644 --- a/groups/app/models/wiki.rb +++ b/groups/app/models/wiki.rb @@ -17,7 +17,7 @@ class Wiki < ActiveRecord::Base belongs_to :project - has_many :pages, :class_name => 'WikiPage', :dependent => :destroy + has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title' has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all validates_presence_of :start_page diff --git a/groups/app/models/wiki_content.rb b/groups/app/models/wiki_content.rb index 724354ad6..f2ee39c4d 100644 --- a/groups/app/models/wiki_content.rb +++ b/groups/app/models/wiki_content.rb @@ -35,6 +35,17 @@ class WikiContent < ActiveRecord::Base :type => 'wiki-page', :url => Proc.new {|o| {:controller => 'wiki', :id => o.page.wiki.project_id, :page => o.page.title, :version => o.version}} + acts_as_activity_provider :type => 'wiki_pages', + :timestamp => "#{WikiContent.versioned_table_name}.updated_on", + :permission => :view_wiki_pages, + :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " + + "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " + + "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " + + "#{WikiContent.versioned_table_name}.id", + :joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " + + "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " + + "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"} + def text=(plain) case Setting.wiki_compression when 'gzip' diff --git a/groups/app/models/wiki_page.rb b/groups/app/models/wiki_page.rb index 8ce71cb80..2416fab74 100644 --- a/groups/app/models/wiki_page.rb +++ b/groups/app/models/wiki_page.rb @@ -22,14 +22,15 @@ class WikiPage < ActiveRecord::Base belongs_to :wiki has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy has_many :attachments, :as => :container, :dependent => :destroy - + acts_as_tree :order => 'title' + acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"}, :description => :text, :datetime => :created_on, :url => Proc.new {|o| {:controller => 'wiki', :id => o.wiki.project_id, :page => o.title}} acts_as_searchable :columns => ['title', 'text'], - :include => [:wiki, :content], + :include => [{:wiki => :project}, :content], :project_key => "#{Wiki.table_name}.project_id" attr_accessor :redirect_existing_links @@ -105,6 +106,29 @@ class WikiPage < ActiveRecord::Base def text content.text if content end + + # Returns true if usr is allowed to edit the page, otherwise false + def editable_by?(usr) + !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project) + end + + def parent_title + @parent_title || (self.parent && self.parent.pretty_title) + end + + def parent_title=(t) + @parent_title = t + parent_page = t.blank? ? nil : self.wiki.find_page(t) + self.parent = parent_page + end + + protected + + def validate + errors.add(:parent_title, :activerecord_error_invalid) if !@parent_title.blank? && parent.nil? + errors.add(:parent_title, :activerecord_error_circular_dependency) if parent && (parent == self || parent.ancestors.include?(self)) + errors.add(:parent_title, :activerecord_error_not_same_project) if parent && (parent.wiki_id != wiki_id) + end end class WikiDiff diff --git a/groups/app/views/account/login.rhtml b/groups/app/views/account/login.rhtml index ea1a1cd44..d8c1f313f 100644 --- a/groups/app/views/account/login.rhtml +++ b/groups/app/views/account/login.rhtml @@ -1,5 +1,6 @@
<% form_tag({:action=> "login"}) do %> +<%= back_url_hidden_field_tag %> diff --git a/groups/app/views/account/register.rhtml b/groups/app/views/account/register.rhtml index 7cf4b6da3..755a7ad4b 100644 --- a/groups/app/views/account/register.rhtml +++ b/groups/app/views/account/register.rhtml @@ -5,8 +5,9 @@
+<% if @user.auth_source_id.nil? %>

-<%= text_field 'user', 'login', :size => 25 %>

+<%= text_field 'user', 'login', :size => 25 %>

<%= password_field_tag 'password', nil, :size => 25 %>
@@ -14,6 +15,7 @@

<%= password_field_tag 'password_confirmation', nil, :size => 25 %>

+<% end %>

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

@@ -27,8 +29,8 @@

<%= select("user", "language", lang_options_for_select) %>

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

<%= custom_field_tag_with_label @custom_value %>

+<% @user.custom_field_values.each do |value| %> +

<%= custom_field_tag_with_label :user, value %>

<% end %>
diff --git a/groups/app/views/account/show.rhtml b/groups/app/views/account/show.rhtml index 97212b377..1160a5d8c 100644 --- a/groups/app/views/account/show.rhtml +++ b/groups/app/views/account/show.rhtml @@ -1,7 +1,11 @@ +
+<%= link_to(l(:button_edit), {:controller => 'users', :action => 'edit', :id => @user}, :class => 'icon icon-edit') if User.current.admin? %> +
+

<%=h @user.name %>

-<%= mail_to @user.mail unless @user.pref.hide_mail %> +<%= mail_to(h(@user.mail)) unless @user.pref.hide_mail %>

  • <%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %>
  • <% for custom_value in @custom_values %> @@ -16,8 +20,8 @@

    <%=l(:label_project_plural)%>

      <% for membership in @memberships %> -
    • <%= link_to membership.project.name, :controller => 'projects', :action => 'show', :id => membership.project %> - (<%= membership.role.name %>, <%= format_date(membership.created_on) %>)
    • +
    • <%= link_to(h(membership.project.name), :controller => 'projects', :action => 'show', :id => membership.project) %> + (<%=h membership.role.name %>, <%= format_date(membership.created_on) %>)
    • <% end %>
    <% end %> diff --git a/groups/app/views/attachments/_links.rhtml b/groups/app/views/attachments/_links.rhtml index 4d485548b..9aae909fe 100644 --- a/groups/app/views/attachments/_links.rhtml +++ b/groups/app/views/attachments/_links.rhtml @@ -1,6 +1,6 @@
    <% for attachment in attachments %> -

    <%= link_to attachment.filename, {:controller => 'attachments', :action => 'download', :id => attachment }, :class => 'icon icon-attachment' -%> +

    <%= link_to_attachment attachment, :class => 'icon icon-attachment' -%> <%= h(" - #{attachment.description}") unless attachment.description.blank? %> (<%= number_to_human_size attachment.filesize %>) <% if options[:delete_url] %> diff --git a/groups/app/views/attachments/diff.rhtml b/groups/app/views/attachments/diff.rhtml new file mode 100644 index 000000000..7b64dca17 --- /dev/null +++ b/groups/app/views/attachments/diff.rhtml @@ -0,0 +1,15 @@ +

    <%=h @attachment.filename %>

    + +
    +

    <%= h("#{@attachment.description} - ") unless @attachment.description.blank? %> + <%= @attachment.author %>, <%= format_time(@attachment.created_on) %>

    +

    <%= link_to_attachment @attachment, :text => l(:button_download), :download => true -%> + (<%= number_to_human_size @attachment.filesize %>)

    + +
    +  +<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type} %> + +<% content_for :header_tags do -%> + <%= stylesheet_link_tag "scm" -%> +<% end -%> diff --git a/groups/app/views/attachments/file.rhtml b/groups/app/views/attachments/file.rhtml new file mode 100644 index 000000000..468c6b666 --- /dev/null +++ b/groups/app/views/attachments/file.rhtml @@ -0,0 +1,15 @@ +

    <%=h @attachment.filename %>

    + +
    +

    <%= h("#{@attachment.description} - ") unless @attachment.description.blank? %> + <%= @attachment.author %>, <%= format_time(@attachment.created_on) %>

    +

    <%= link_to_attachment @attachment, :text => l(:button_download), :download => true -%> + (<%= number_to_human_size @attachment.filesize %>)

    + +
    +  +<%= render :partial => 'common/file', :locals => {:content => @content, :filename => @attachment.filename} %> + +<% content_for :header_tags do -%> + <%= stylesheet_link_tag "scm" -%> +<% end -%> diff --git a/groups/app/views/auth_sources/_form.rhtml b/groups/app/views/auth_sources/_form.rhtml index 3d148c11f..9ffffafc7 100644 --- a/groups/app/views/auth_sources/_form.rhtml +++ b/groups/app/views/auth_sources/_form.rhtml @@ -22,14 +22,12 @@

    <%= text_field 'auth_source', 'base_dn', :size => 60 %>

    -
    -

    <%= check_box 'auth_source', 'onthefly_register' %>

    +
    -

    -

    <%=l(:label_attribute_plural)%> +
    <%=l(:label_attribute_plural)%>

    <%= text_field 'auth_source', 'attr_login', :size => 20 %>

    @@ -42,7 +40,5 @@

    <%= text_field 'auth_source', 'attr_mail', :size => 20 %>

    -

    - diff --git a/groups/app/views/boards/index.rhtml b/groups/app/views/boards/index.rhtml index 8d4560653..655352a96 100644 --- a/groups/app/views/boards/index.rhtml +++ b/groups/app/views/boards/index.rhtml @@ -38,3 +38,5 @@ <% content_for :header_tags do %> <%= auto_discovery_link_tag(:atom, {:controller => 'projects', :action => 'activity', :id => @project, :format => 'atom', :show_messages => 1, :key => User.current.rss_key}) %> <% end %> + +<% html_title l(:label_board_plural) %> diff --git a/groups/app/views/boards/show.rhtml b/groups/app/views/boards/show.rhtml index 26d17ae56..96818df34 100644 --- a/groups/app/views/boards/show.rhtml +++ b/groups/app/views/boards/show.rhtml @@ -33,7 +33,7 @@
<%= sort_header_tag("#{Message.table_name}.created_on", :caption => l(:field_created_on)) %> - + <%= sort_header_tag("#{Message.table_name}.replies_count", :caption => l(:label_reply_plural)) %> <%= sort_header_tag("#{Message.table_name}.updated_on", :caption => l(:label_message_last)) %> @@ -57,3 +57,5 @@ <% else %>

<%= l(:label_no_data) %>

<% end %> + +<% html_title h(@board.name) %> diff --git a/groups/app/views/common/_diff.rhtml b/groups/app/views/common/_diff.rhtml new file mode 100644 index 000000000..0b28101b7 --- /dev/null +++ b/groups/app/views/common/_diff.rhtml @@ -0,0 +1,64 @@ +<% Redmine::UnifiedDiff.new(diff, diff_type).each do |table_file| -%> +
+<% if diff_type == 'sbs' -%> +
<%= l(:field_subject) %> <%= l(:field_author) %><%= l(:label_reply_plural) %>
+ + + + +<% prev_line_left, prev_line_right = nil, nil -%> +<% table_file.keys.sort.each do |key| -%> +<% if prev_line_left && prev_line_right && (table_file[key].nb_line_left != prev_line_left+1) && (table_file[key].nb_line_right != prev_line_right+1) -%> + + +<% end -%> + + + + + + +<% prev_line_left, prev_line_right = table_file[key].nb_line_left.to_i, table_file[key].nb_line_right.to_i -%> +<% end -%> + +
<%= table_file.file_name %>
......
<%= table_file[key].nb_line_left %> +
<%=to_utf8 table_file[key].line_left %>
+
<%= table_file[key].nb_line_right %> +
<%=to_utf8 table_file[key].line_right %>
+
+ +<% else -%> + + + + + +<% prev_line_left, prev_line_right = nil, nil -%> +<% table_file.keys.sort.each do |key, line| %> +<% if prev_line_left && prev_line_right && (table_file[key].nb_line_left != prev_line_left+1) && (table_file[key].nb_line_right != prev_line_right+1) -%> + + + +<% end -%> + + + + <% if table_file[key].line_left.empty? -%> + + <% else -%> + + <% end -%> + +<% prev_line_left = table_file[key].nb_line_left.to_i if table_file[key].nb_line_left.to_i > 0 -%> +<% prev_line_right = table_file[key].nb_line_right.to_i if table_file[key].nb_line_right.to_i > 0 -%> +<% end -%> + +
<%= table_file.file_name %>
......
<%= table_file[key].nb_line_left %><%= table_file[key].nb_line_right %> +
<%=to_utf8 table_file[key].line_right %>
+
+
<%=to_utf8 table_file[key].line_left %>
+
+<% end -%> + +
+<% end -%> diff --git a/groups/app/views/common/_file.rhtml b/groups/app/views/common/_file.rhtml new file mode 100644 index 000000000..43f5c6c4b --- /dev/null +++ b/groups/app/views/common/_file.rhtml @@ -0,0 +1,11 @@ +
+ + +<% line_num = 1 %> +<% syntax_highlight(filename, to_utf8(content)).each_line do |line| %> + +<% line_num += 1 %> +<% end %> + +
<%= line_num %>
<%= line %>
+
diff --git a/groups/app/views/common/_preview.rhtml b/groups/app/views/common/_preview.rhtml index e3bfc3a25..fd95f1188 100644 --- a/groups/app/views/common/_preview.rhtml +++ b/groups/app/views/common/_preview.rhtml @@ -1,3 +1,3 @@
<%= l(:label_preview) %> -<%= textilizable @text, :attachments => @attachements %> +<%= textilizable @text, :attachments => @attachements, :object => @previewed %>
diff --git a/groups/app/views/common/feed.atom.rxml b/groups/app/views/common/feed.atom.rxml index b5cbeeed9..c1b88a28e 100644 --- a/groups/app/views/common/feed.atom.rxml +++ b/groups/app/views/common/feed.atom.rxml @@ -1,6 +1,6 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do - xml.title @title + xml.title truncate_single_line(@title, 100) xml.link "rel" => "self", "href" => url_for(params.merge({:format => nil, :only_path => false})) xml.link "rel" => "alternate", "href" => url_for(:controller => 'welcome', :only_path => false) xml.id url_for(:controller => 'welcome', :only_path => false) @@ -10,11 +10,15 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do @items.each do |item| xml.entry do url = url_for(item.event_url(:only_path => false)) - xml.title truncate(item.event_title, 100) + if @project + xml.title truncate_single_line(item.event_title, 100) + else + xml.title truncate_single_line("#{item.project} - #{item.event_title}", 100) + end xml.link "rel" => "alternate", "href" => url xml.id url xml.updated item.event_datetime.xmlschema - author = item.event_author if item.respond_to?(:author) + author = item.event_author if item.respond_to?(:event_author) xml.author do xml.name(author) xml.email(author.mail) if author.respond_to?(:mail) && !author.mail.blank? diff --git a/groups/app/views/custom_fields/_form.rhtml b/groups/app/views/custom_fields/_form.rhtml index b3731fac7..db87c9217 100644 --- a/groups/app/views/custom_fields/_form.rhtml +++ b/groups/app/views/custom_fields/_form.rhtml @@ -102,6 +102,9 @@ when "IssueCustomField" %> <% else %>

<%= f.check_box :is_required %>

+<% when "TimeEntryCustomField" %> +

<%= f.check_box :is_required %>

+ <% end %> <%= javascript_tag "toggle_custom_field_format();" %> diff --git a/groups/app/views/enumerations/destroy.rhtml b/groups/app/views/enumerations/destroy.rhtml new file mode 100644 index 000000000..657df8322 --- /dev/null +++ b/groups/app/views/enumerations/destroy.rhtml @@ -0,0 +1,12 @@ +

<%= l(@enumeration.option_name) %>: <%=h @enumeration %>

+ +<% form_tag({}) do %> +
+

<%= l(:text_enumeration_destroy_question, @enumeration.objects_count) %>

+

<%= l(:text_enumeration_category_reassign_to) %> +<%= select_tag 'reassign_to_id', ("" + options_from_collection_for_select(@enumerations, 'id', 'name')) %>

+
+ +<%= submit_tag l(:button_apply) %> +<%= link_to l(:button_cancel), :controller => 'enumerations', :action => 'index' %> +<% end %> diff --git a/groups/app/views/enumerations/list.rhtml b/groups/app/views/enumerations/list.rhtml index 9de9bf37c..7f3886b44 100644 --- a/groups/app/views/enumerations/list.rhtml +++ b/groups/app/views/enumerations/list.rhtml @@ -1,14 +1,14 @@

<%=l(:label_enumerations)%>

-<% Enumeration::OPTIONS.each do |option, name| %> -

<%= l(name) %>

+<% Enumeration::OPTIONS.each do |option, params| %> +

<%= l(params[:label]) %>

<% enumerations = Enumeration.get_values(option) %> <% if enumerations.any? %> <% enumerations.each do |enumeration| %> - + + <% end %>
<%= link_to enumeration.name, :action => 'edit', :id => enumeration %><%= link_to h(enumeration), :action => 'edit', :id => enumeration %> <%= image_tag('true.png') if enumeration.is_default? %> <%= link_to image_tag('2uparrow.png', :alt => l(:label_sort_highest)), {:action => 'move', :id => enumeration, :position => 'highest'}, :method => :post, :title => l(:label_sort_highest) %> @@ -16,6 +16,9 @@ <%= link_to image_tag('1downarrow.png', :alt => l(:label_sort_lower)), {:action => 'move', :id => enumeration, :position => 'lower'}, :method => :post, :title => l(:label_sort_lower) %> <%= link_to image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), {:action => 'move', :id => enumeration, :position => 'lowest'}, :method => :post, :title => l(:label_sort_lowest) %> + <%= link_to l(:button_delete), { :action => 'destroy', :id => enumeration }, :method => :post, :confirm => l(:text_are_you_sure), :class => "icon icon-del" %> +
diff --git a/groups/app/views/issues/_edit.rhtml b/groups/app/views/issues/_edit.rhtml index 2e00ab520..2c7a4286e 100644 --- a/groups/app/views/issues/_edit.rhtml +++ b/groups/app/views/issues/_edit.rhtml @@ -4,6 +4,7 @@ :class => nil, :multipart => true} do |f| %> <%= error_messages_for 'issue' %> + <%= error_messages_for 'time_entry' %>
<% if @edit_allowed || !@allowed_statuses.empty? %>
<%= l(:label_change_properties) %> @@ -21,9 +22,12 @@

<%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %>

-

<%= time_entry.text_field :comments, :size => 40 %>

-

<%= time_entry.select :activity_id, (@activities.collect {|p| [p.name, p.id]}) %>

+

<%= time_entry.select :activity_id, activity_collection_for_select_options %>

+

<%= time_entry.text_field :comments, :size => 60 %>

+ <% @time_entry.custom_field_values.each do |value| %> +

<%= custom_field_tag_with_label :time_entry, value %>

+ <% end %> <% end %> <% end %> diff --git a/groups/app/views/issues/_form.rhtml b/groups/app/views/issues/_form.rhtml index 9bb74fd34..4eca3cb4a 100644 --- a/groups/app/views/issues/_form.rhtml +++ b/groups/app/views/issues/_form.rhtml @@ -42,7 +42,7 @@
-<%= render :partial => 'form_custom_fields', :locals => {:values => @custom_values} %> +<%= render :partial => 'form_custom_fields' %> <% if @issue.new_record? %>

<%= render :partial => 'attachments/form' %>

diff --git a/groups/app/views/issues/_form_custom_fields.rhtml b/groups/app/views/issues/_form_custom_fields.rhtml index 1268bb1f9..752fb4d37 100644 --- a/groups/app/views/issues/_form_custom_fields.rhtml +++ b/groups/app/views/issues/_form_custom_fields.rhtml @@ -1,11 +1,12 @@
-<% i = 1 %> -<% for @custom_value in values %> -

<%= custom_field_tag_with_label @custom_value %>

- <% if i == values.size / 2 %> +<% i = 0 %> +<% split_on = @issue.custom_field_values.size / 2 %> +<% @issue.custom_field_values.each do |value| %> +

<%= custom_field_tag_with_label :issue, value %>

+<% if i == split_on -%>
- <% end %> - <% i += 1 %> -<% end %> +<% end -%> +<% i += 1 -%> +<% end -%>
diff --git a/groups/app/views/issues/_history.rhtml b/groups/app/views/issues/_history.rhtml index f29a44daf..b8efdb400 100644 --- a/groups/app/views/issues/_history.rhtml +++ b/groups/app/views/issues/_history.rhtml @@ -1,3 +1,4 @@ +<% reply_links = authorize_for('issues', 'edit') -%> <% for journal in journals %>

<%= link_to "##{journal.indice}", :anchor => "note-#{journal.indice}" %>
@@ -8,6 +9,6 @@
  • <%= show_detail(detail) %>
  • <% end %> - <%= render_notes(journal) unless journal.notes.blank? %> + <%= render_notes(journal, :reply_links => reply_links) unless journal.notes.blank? %>

    <% end %> diff --git a/groups/app/views/issues/_list.rhtml b/groups/app/views/issues/_list.rhtml index 000f79853..b42357894 100644 --- a/groups/app/views/issues/_list.rhtml +++ b/groups/app/views/issues/_list.rhtml @@ -1,7 +1,7 @@ <% form_tag({}) do -%> - <%= sort_header_tag("#{Issue.table_name}.id", :caption => '#', :default_order => 'desc') %> diff --git a/groups/app/views/issues/_pdf.rfpdf b/groups/app/views/issues/_pdf.rfpdf deleted file mode 100644 index 6830506f6..000000000 --- a/groups/app/views/issues/_pdf.rfpdf +++ /dev/null @@ -1,118 +0,0 @@ -<% pdf.SetFontStyle('B',11) - pdf.Cell(190,10, "#{issue.project.name} - #{issue.tracker.name} # #{issue.id}: #{issue.subject}") - pdf.Ln - - y0 = pdf.GetY - - pdf.SetFontStyle('B',9) - pdf.Cell(35,5, l(:field_status) + ":","LT") - pdf.SetFontStyle('',9) - pdf.Cell(60,5, issue.status.name,"RT") - pdf.SetFontStyle('B',9) - pdf.Cell(35,5, l(:field_priority) + ":","LT") - pdf.SetFontStyle('',9) - pdf.Cell(60,5, issue.priority.name,"RT") - pdf.Ln - - pdf.SetFontStyle('B',9) - pdf.Cell(35,5, l(:field_author) + ":","L") - pdf.SetFontStyle('',9) - pdf.Cell(60,5, issue.author.name,"R") - pdf.SetFontStyle('B',9) - pdf.Cell(35,5, l(:field_category) + ":","L") - pdf.SetFontStyle('',9) - pdf.Cell(60,5, (issue.category ? issue.category.name : "-"),"R") - pdf.Ln - - pdf.SetFontStyle('B',9) - pdf.Cell(35,5, l(:field_created_on) + ":","L") - pdf.SetFontStyle('',9) - pdf.Cell(60,5, format_date(issue.created_on),"R") - pdf.SetFontStyle('B',9) - pdf.Cell(35,5, l(:field_assigned_to) + ":","L") - pdf.SetFontStyle('',9) - pdf.Cell(60,5, (issue.assigned_to ? issue.assigned_to.name : "-"),"R") - pdf.Ln - - pdf.SetFontStyle('B',9) - pdf.Cell(35,5, l(:field_updated_on) + ":","LB") - pdf.SetFontStyle('',9) - pdf.Cell(60,5, format_date(issue.updated_on),"RB") - pdf.SetFontStyle('B',9) - pdf.Cell(35,5, l(:field_due_date) + ":","LB") - pdf.SetFontStyle('',9) - pdf.Cell(60,5, format_date(issue.due_date),"RB") - pdf.Ln - - for custom_value in issue.custom_values - pdf.SetFontStyle('B',9) - pdf.Cell(35,5, custom_value.custom_field.name + ":","L") - pdf.SetFontStyle('',9) - pdf.MultiCell(155,5, (show_value custom_value),"R") - end - - pdf.SetFontStyle('B',9) - pdf.Cell(35,5, l(:field_subject) + ":","LTB") - pdf.SetFontStyle('',9) - pdf.Cell(155,5, issue.subject,"RTB") - pdf.Ln - - pdf.SetFontStyle('B',9) - pdf.Cell(35,5, l(:field_description) + ":") - pdf.SetFontStyle('',9) - pdf.MultiCell(155,5, issue.description,"BR") - - pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY) - pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY) - - pdf.Ln - - if @issue.changesets.any? && User.current.allowed_to?(:view_changesets, issue.project) - pdf.SetFontStyle('B',9) - pdf.Cell(190,5, l(:label_associated_revisions), "B") - pdf.Ln - for changeset in @issue.changesets - pdf.SetFontStyle('B',8) - pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.committer) - pdf.Ln - unless changeset.comments.blank? - pdf.SetFontStyle('',8) - pdf.MultiCell(190,5, changeset.comments) - end - pdf.Ln - end - end - - pdf.SetFontStyle('B',9) - pdf.Cell(190,5, l(:label_history), "B") - pdf.Ln - for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") - pdf.SetFontStyle('B',8) - pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name) - pdf.Ln - pdf.SetFontStyle('I',8) - for detail in journal.details - pdf.Cell(190,5, "- " + show_detail(detail, true)) - pdf.Ln - end - if journal.notes? - pdf.SetFontStyle('',8) - pdf.MultiCell(190,5, journal.notes) - end - pdf.Ln - end - - if issue.attachments.any? - pdf.SetFontStyle('B',9) - pdf.Cell(190,5, l(:label_attachment_plural), "B") - pdf.Ln - for attachment in issue.attachments - pdf.SetFontStyle('',8) - pdf.Cell(80,5, attachment.filename) - pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R") - pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R") - pdf.Cell(65,5, attachment.author.name,0,0,"R") - pdf.Ln - end - end -%> diff --git a/groups/app/views/issues/context_menu.rhtml b/groups/app/views/issues/context_menu.rhtml index f42f254e8..671655db7 100644 --- a/groups/app/views/issues/context_menu.rhtml +++ b/groups/app/views/issues/context_menu.rhtml @@ -6,47 +6,83 @@ <%= l(:field_status) %> +<% else %> +
  • <%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)}, + :class => 'icon-edit', :disabled => !@can[:edit] %>
  • +<% end %> +
  • <%= l(:field_priority) %>
      <% @priorities.each do |p| -%> -
    • <%= context_menu_link p.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[priority_id]' => p, :back_to => @back}, :method => :post, - :selected => (p == @issue.priority), :disabled => !@can[:edit] %>
    • +
    • <%= context_menu_link p.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'priority_id' => p, :back_to => @back}, :method => :post, + :selected => (@issue && p == @issue.priority), :disabled => !@can[:edit] %>
    • + <% end -%> +
    +
  • + <% unless @project.nil? || @project.versions.empty? -%> +
  • + <%= l(:field_fixed_version) %> +
      + <% @project.versions.sort.each do |v| -%> +
    • <%= context_menu_link v.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => v, :back_to => @back}, :method => :post, + :selected => (@issue && v == @issue.fixed_version), :disabled => !@can[:update] %>
    • <% end -%> +
    • <%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => 'none', :back_to => @back}, :method => :post, + :selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %>
  • + <% end %> + <% unless @assignables.nil? || @assignables.empty? -%>
  • <%= l(:field_assigned_to) %>
      <% @assignables.each do |u| -%> -
    • <%= context_menu_link u.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => u, :back_to => @back}, :method => :post, - :selected => (u == @issue.assigned_to), :disabled => !@can[:update] %>
    • +
    • <%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'assigned_to_id' => u, :back_to => @back}, :method => :post, + :selected => (@issue && u == @issue.assigned_to), :disabled => !@can[:update] %>
    • <% end -%> -
    • <%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => '', :back_to => @back}, :method => :post, - :selected => @issue.assigned_to.nil?, :disabled => !@can[:update] %>
    • +
    • <%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'assigned_to_id' => 'none', :back_to => @back}, :method => :post, + :selected => (@issue && @issue.assigned_to.nil?), :disabled => !@can[:update] %>
  • + <% end %> + <% unless @project.nil? || @project.issue_categories.empty? -%> +
  • + <%= l(:field_category) %> +
      + <% @project.issue_categories.each do |u| -%> +
    • <%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'category_id' => u, :back_to => @back}, :method => :post, + :selected => (@issue && u == @issue.category), :disabled => !@can[:update] %>
    • + <% end -%> +
    • <%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'category_id' => 'none', :back_to => @back}, :method => :post, + :selected => (@issue && @issue.category.nil?), :disabled => !@can[:update] %>
    • +
    +
  • + <% end -%>
  • <%= l(:field_done_ratio) %>
      <% (0..10).map{|x|x*10}.each do |p| -%> -
    • <%= context_menu_link "#{p}%", {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[done_ratio]' => p, :back_to => @back}, :method => :post, - :selected => (p == @issue.done_ratio), :disabled => !@can[:edit] %>
    • +
    • <%= context_menu_link "#{p}%", {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'done_ratio' => p, :back_to => @back}, :method => :post, + :selected => (@issue && p == @issue.done_ratio), :disabled => !@can[:edit] %>
    • <% end -%>
  • + +<% if !@issue.nil? %>
  • <%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue}, :class => 'icon-copy', :disabled => !@can[:copy] %>
  • -<% else -%> -
  • <%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)}, - :class => 'icon-edit', :disabled => !@can[:edit] %>
  • -<% end -%> - + <% if @can[:log_time] -%> +
  • <%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, + :class => 'icon-time' %>
  • + <% end %> +<% end %> +
  • <%= context_menu_link l(:button_move), {:controller => 'issues', :action => 'move', :ids => @issues.collect(&:id)}, :class => 'icon-move', :disabled => !@can[:move] %>
  • <%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id)}, diff --git a/groups/app/views/issues/index.rhtml b/groups/app/views/issues/index.rhtml index 027f3f006..973f3eb25 100644 --- a/groups/app/views/issues/index.rhtml +++ b/groups/app/views/issues/index.rhtml @@ -45,7 +45,7 @@

    <%= l(:label_export_to) %> -<%= link_to 'Atom', {:format => 'atom', :key => User.current.rss_key}, :class => 'feed' %> +<%= link_to 'Atom', {:query_id => @query, :format => 'atom', :key => User.current.rss_key}, :class => 'feed' %> <%= link_to 'CSV', {:format => 'csv'}, :class => 'csv' %> <%= link_to 'PDF', {:format => 'pdf'}, :class => 'pdf' %>

    diff --git a/groups/app/views/issues/show.rfpdf b/groups/app/views/issues/show.rfpdf index 08f2cb92d..73d9d66b5 100644 --- a/groups/app/views/issues/show.rfpdf +++ b/groups/app/views/issues/show.rfpdf @@ -4,7 +4,123 @@ pdf.footer_date = format_date(Date.today) pdf.AddPage - render :partial => 'issues/pdf', :locals => { :pdf => pdf, :issue => @issue } + pdf.SetFontStyle('B',11) + pdf.Cell(190,10, "#{@issue.project} - #{@issue.tracker} # #{@issue.id}: #{@issue.subject}") + pdf.Ln + + y0 = pdf.GetY + + pdf.SetFontStyle('B',9) + pdf.Cell(35,5, l(:field_status) + ":","LT") + pdf.SetFontStyle('',9) + pdf.Cell(60,5, @issue.status.name,"RT") + pdf.SetFontStyle('B',9) + pdf.Cell(35,5, l(:field_priority) + ":","LT") + pdf.SetFontStyle('',9) + pdf.Cell(60,5, @issue.priority.name,"RT") + pdf.Ln + + pdf.SetFontStyle('B',9) + pdf.Cell(35,5, l(:field_author) + ":","L") + pdf.SetFontStyle('',9) + pdf.Cell(60,5, @issue.author.name,"R") + pdf.SetFontStyle('B',9) + pdf.Cell(35,5, l(:field_category) + ":","L") + pdf.SetFontStyle('',9) + pdf.Cell(60,5, (@issue.category ? @issue.category.name : "-"),"R") + pdf.Ln + + pdf.SetFontStyle('B',9) + pdf.Cell(35,5, l(:field_created_on) + ":","L") + pdf.SetFontStyle('',9) + pdf.Cell(60,5, format_date(@issue.created_on),"R") + pdf.SetFontStyle('B',9) + pdf.Cell(35,5, l(:field_assigned_to) + ":","L") + pdf.SetFontStyle('',9) + pdf.Cell(60,5, (@issue.assigned_to ? @issue.assigned_to.name : "-"),"R") + pdf.Ln + + pdf.SetFontStyle('B',9) + pdf.Cell(35,5, l(:field_updated_on) + ":","LB") + pdf.SetFontStyle('',9) + pdf.Cell(60,5, format_date(@issue.updated_on),"RB") + pdf.SetFontStyle('B',9) + pdf.Cell(35,5, l(:field_due_date) + ":","LB") + pdf.SetFontStyle('',9) + pdf.Cell(60,5, format_date(@issue.due_date),"RB") + pdf.Ln + + for custom_value in @issue.custom_values + pdf.SetFontStyle('B',9) + pdf.Cell(35,5, custom_value.custom_field.name + ":","L") + pdf.SetFontStyle('',9) + pdf.MultiCell(155,5, (show_value custom_value),"R") + end + + pdf.SetFontStyle('B',9) + pdf.Cell(35,5, l(:field_subject) + ":","LTB") + pdf.SetFontStyle('',9) + pdf.Cell(155,5, @issue.subject,"RTB") + pdf.Ln + + pdf.SetFontStyle('B',9) + pdf.Cell(35,5, l(:field_description) + ":") + pdf.SetFontStyle('',9) + pdf.MultiCell(155,5, @issue.description,"BR") + + pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY) + pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY) + + pdf.Ln + + if @issue.changesets.any? && User.current.allowed_to?(:view_changesets, @issue.project) + pdf.SetFontStyle('B',9) + pdf.Cell(190,5, l(:label_associated_revisions), "B") + pdf.Ln + for changeset in @issue.changesets + pdf.SetFontStyle('B',8) + pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.committer) + pdf.Ln + unless changeset.comments.blank? + pdf.SetFontStyle('',8) + pdf.MultiCell(190,5, changeset.comments) + end + pdf.Ln + end + end + + pdf.SetFontStyle('B',9) + pdf.Cell(190,5, l(:label_history), "B") + pdf.Ln + for journal in @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") + pdf.SetFontStyle('B',8) + pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name) + pdf.Ln + pdf.SetFontStyle('I',8) + for detail in journal.details + pdf.Cell(190,5, "- " + show_detail(detail, true)) + pdf.Ln + end + if journal.notes? + pdf.SetFontStyle('',8) + pdf.MultiCell(190,5, journal.notes) + end + pdf.Ln + end + + if @issue.attachments.any? + pdf.SetFontStyle('B',9) + pdf.Cell(190,5, l(:label_attachment_plural), "B") + pdf.Ln + for attachment in @issue.attachments + pdf.SetFontStyle('',8) + pdf.Cell(80,5, attachment.filename) + pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R") + pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R") + pdf.Cell(65,5, attachment.author.name,0,0,"R") + pdf.Ln + end + end %> <%= pdf.Output %> diff --git a/groups/app/views/issues/show.rhtml b/groups/app/views/issues/show.rhtml index f788d0ec8..2dd1bacaa 100644 --- a/groups/app/views/issues/show.rhtml +++ b/groups/app/views/issues/show.rhtml @@ -1,5 +1,5 @@
    -<%= show_and_goto_link(l(:button_update), 'update', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) if authorize_for('issues', 'edit') %> +<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %> <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %> <%= watcher_tag(@issue, User.current) %> <%= link_to_if_authorized l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %> @@ -18,34 +18,34 @@
  • <%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(this.up("form")); return false;', + <%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;', :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
    - - + + - - + + - - + + - + <% if User.current.allowed_to?(:view_time_entries, @project) %> - + <% end %> - + <% if @issue.estimated_hours %> - + <% end %> -<% n = 0 -for custom_value in @custom_values %> - +<% n = 0 -%> +<% @issue.custom_values.each do |value| -%> + <% n = n + 1 if (n > 1) n = 0 %> @@ -56,6 +56,10 @@ end %>
    <%=l(:field_status)%> :<%= @issue.status.name %><%=l(:field_start_date)%> :<%= format_date(@issue.start_date) %><%=l(:field_status)%>:<%= @issue.status.name %><%=l(:field_start_date)%>:<%= format_date(@issue.start_date) %>
    <%=l(:field_priority)%> :<%= @issue.priority.name %><%=l(:field_due_date)%> :<%= format_date(@issue.due_date) %><%=l(:field_priority)%>:<%= @issue.priority.name %><%=l(:field_due_date)%>:<%= format_date(@issue.due_date) %>
    <%=l(:field_assigned_to)%> :<%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %><%=l(:field_done_ratio)%> :<%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %><%=l(:field_assigned_to)%>:<%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %><%=l(:field_done_ratio)%>:<%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %>
    <%=l(:field_category)%> :<%=h @issue.category ? @issue.category.name : "-" %><%=l(:field_category)%>:<%=h @issue.category ? @issue.category.name : "-" %><%=l(:label_spent_time)%> :<%=l(:label_spent_time)%>: <%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time') : "-" %>
    <%=l(:field_fixed_version)%> :<%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %><%=l(:field_fixed_version)%>:<%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %><%=l(:field_estimated_hours)%> :<%= lwr(:label_f_hour, @issue.estimated_hours) %><%=l(:field_estimated_hours)%>:<%= lwr(:label_f_hour, @issue.estimated_hours) %>
    <%= custom_value.custom_field.name %> :<%= simple_format(h(show_value(custom_value))) %><%=h value.custom_field.name %>:<%= simple_format(h(show_value(value))) %>

    +
    +<%= link_to_remote_if_authorized l(:button_quote), { :url => {:action => 'reply', :id => @issue} }, :class => 'icon icon-comment' %> +
    +

    <%=l(:field_description)%>

    <%= textilizable @issue, :description, :attachments => @issue.attachments %> @@ -72,6 +76,14 @@ end %>
    <% end %> +<% if User.current.allowed_to?(:add_issue_watchers, @project) || + (@issue.watchers.any? && User.current.allowed_to?(:view_issue_watchers, @project)) %> +
    +
    +<%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %> +
    +<% end %> + <% if @issue.changesets.any? && User.current.allowed_to?(:view_changesets, @project) %> @@ -110,4 +122,5 @@ end %> <% content_for :header_tags do %> <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %> + <%= stylesheet_link_tag 'scm' %> <% end %> diff --git a/groups/app/views/layouts/base.rhtml b/groups/app/views/layouts/base.rhtml index 0b9d31512..62d542b7b 100644 --- a/groups/app/views/layouts/base.rhtml +++ b/groups/app/views/layouts/base.rhtml @@ -36,7 +36,7 @@ <%= render :partial => 'layouts/project_selector' if User.current.memberships.any? %> -

    <%= h(@project ? @project.name : Setting.app_title) %>

    +

    <%= h(@project && !@project.new_record? ? @project.name : Setting.app_title) %>


    +<% unless @replies.empty? %>

    <%= l(:label_reply_plural) %>

    <% @replies.each do |message| %> ">
    + <%= link_to_remote_if_authorized image_tag('comment.png'), { :url => {:action => 'quote', :id => message} }, :title => l(:button_quote) %> <%= link_to_if_authorized image_tag('edit.png'), {:action => 'edit', :id => message}, :title => l(:button_edit) %> <%= link_to_if_authorized image_tag('delete.png'), {:action => 'destroy', :id => message}, :method => :post, :confirm => l(:text_are_you_sure), :title => l(:button_delete) %>
    @@ -30,6 +33,7 @@ <%= link_to_attachments message.attachments, :no_author => true %> <% end %> +<% end %> <% if !@topic.locked? && authorize_for('messages', 'reply') %>

    <%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %>

    @@ -48,3 +52,9 @@
    <% end %> + +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'scm' %> +<% end %> + +<% html_title h(@topic.subject) %> diff --git a/groups/app/views/my/blocks/_documents.rhtml b/groups/app/views/my/blocks/_documents.rhtml index a34be936f..d222e4203 100644 --- a/groups/app/views/my/blocks/_documents.rhtml +++ b/groups/app/views/my/blocks/_documents.rhtml @@ -1,8 +1,9 @@

    <%=l(:label_document_plural)%>

    +<% project_ids = @user.projects.select {|p| @user.allowed_to?(:view_documents, p)}.collect(&:id) %> <%= render(:partial => 'documents/document', :collection => Document.find(:all, :limit => 10, :order => "#{Document.table_name}.created_on DESC", - :conditions => "#{Document.table_name}.project_id in (#{@user.projects.collect{|m| m.id}.join(',')})", - :include => [:project])) unless @user.projects.empty? %> \ No newline at end of file + :conditions => "#{Document.table_name}.project_id in (#{project_ids.join(',')})", + :include => [:project])) unless project_ids.empty? %> \ No newline at end of file diff --git a/groups/app/views/news/edit.rhtml b/groups/app/views/news/edit.rhtml index a7e5e6e36..4be566e0b 100644 --- a/groups/app/views/news/edit.rhtml +++ b/groups/app/views/news/edit.rhtml @@ -5,7 +5,7 @@ <%= render :partial => 'form', :locals => { :f => f } %> <%= submit_tag l(:button_save) %> <%= link_to_remote l(:label_preview), - { :url => { :controller => 'news', :action => 'preview' }, + { :url => { :controller => 'news', :action => 'preview', :project_id => @project }, :method => 'post', :update => 'preview', :with => "Form.serialize('news-form')" diff --git a/groups/app/views/news/index.rhtml b/groups/app/views/news/index.rhtml index 87db8a5f7..9cac39002 100644 --- a/groups/app/views/news/index.rhtml +++ b/groups/app/views/news/index.rhtml @@ -12,7 +12,7 @@ <%= render :partial => 'news/form', :locals => { :f => f } %> <%= submit_tag l(:button_create) %> <%= link_to_remote l(:label_preview), - { :url => { :controller => 'news', :action => 'preview' }, + { :url => { :controller => 'news', :action => 'preview', :project_id => @project }, :method => 'post', :update => 'preview', :with => "Form.serialize('news-form')" diff --git a/groups/app/views/news/new.rhtml b/groups/app/views/news/new.rhtml index 9208d8840..a4d29a0a9 100644 --- a/groups/app/views/news/new.rhtml +++ b/groups/app/views/news/new.rhtml @@ -5,7 +5,7 @@ <%= render :partial => 'news/form', :locals => { :f => f } %> <%= submit_tag l(:button_create) %> <%= link_to_remote l(:label_preview), - { :url => { :controller => 'news', :action => 'preview' }, + { :url => { :controller => 'news', :action => 'preview', :project_id => @project }, :method => 'post', :update => 'preview', :with => "Form.serialize('news-form')" diff --git a/groups/app/views/news/show.rhtml b/groups/app/views/news/show.rhtml index a55b56f0b..78be9c247 100644 --- a/groups/app/views/news/show.rhtml +++ b/groups/app/views/news/show.rhtml @@ -15,7 +15,7 @@ <%= render :partial => 'form', :locals => { :f => f } %> <%= submit_tag l(:button_save) %> <%= link_to_remote l(:label_preview), - { :url => { :controller => 'news', :action => 'preview' }, + { :url => { :controller => 'news', :action => 'preview', :project_id => @project }, :method => 'post', :update => 'preview', :with => "Form.serialize('news-form')" @@ -55,3 +55,7 @@ <% end %> <% html_title @news.title -%> + +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'scm' %> +<% end %> diff --git a/groups/app/views/projects/_form.rhtml b/groups/app/views/projects/_form.rhtml index 32e4dcd44..11f7e3933 100644 --- a/groups/app/views/projects/_form.rhtml +++ b/groups/app/views/projects/_form.rhtml @@ -13,12 +13,12 @@ <% unless @project.identifier_frozen? %>
    <%= l(:text_length_between, 3, 20) %> <%= l(:text_project_identifier_info) %> <% end %>

    -

    <%= f.text_field :homepage, :size => 40 %>

    +

    <%= f.text_field :homepage, :size => 60 %>

    <%= f.check_box :is_public %>

    <%= wikitoolbar_for 'project_description' %> -<% for @custom_value in @custom_values %> -

    <%= custom_field_tag_with_label @custom_value %>

    +<% @project.custom_field_values.each do |value| %> +

    <%= custom_field_tag_with_label :project, value %>

    <% end %> @@ -34,15 +34,15 @@ <% end %> -<% unless @custom_fields.empty? %> +<% unless @issue_custom_fields.empty? %>
    <%=l(:label_custom_field_plural)%> -<% for custom_field in @custom_fields %> +<% @issue_custom_fields.each do |custom_field| %> <% end %> -<%= hidden_field_tag 'project[custom_field_ids][]', '' %> +<%= hidden_field_tag 'project[issue_custom_field_ids][]', '' %>
    <% end %> diff --git a/groups/app/views/projects/activity.rhtml b/groups/app/views/projects/activity.rhtml index c2f2f9ebd..fa25812ac 100644 --- a/groups/app/views/projects/activity.rhtml +++ b/groups/app/views/projects/activity.rhtml @@ -6,11 +6,11 @@

    <%= format_activity_day(day) %>

    <% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> -
    <%= format_time(e.event_datetime, false) %> - <%= content_tag('span', h(e.project), :class => 'project') if @project.nil? || @project != e.project %> <%= link_to h(truncate(e.event_title, 100)), e.event_url %>
    -
    <% unless e.event_description.blank? -%> - <%= format_activity_description(e.event_description) %>
    - <% end %> +
    + <%= format_time(e.event_datetime, false) %> + <%= content_tag('span', h(e.project), :class => 'project') if @project.nil? || @project != e.project %> + <%= link_to format_activity_title(e.event_title), e.event_url %>
    +
    <%= format_activity_description(e.event_description) %> <%= e.event_author if e.respond_to?(:event_author) %>
    <% end -%>
    @@ -44,8 +44,8 @@ <% content_for :sidebar do %> <% form_tag({}, :method => :get) do %>

    <%= l(:label_activity) %>

    -

    <% @event_types.each do |t| %> -
    +

    <% @activity.event_types.each do |t| %> +
    <% end %>

    <% if @project && @project.active_children.any? %>

    diff --git a/groups/app/views/projects/calendar.rhtml b/groups/app/views/projects/calendar.rhtml index 743721cb3..048d8a5df 100644 --- a/groups/app/views/projects/calendar.rhtml +++ b/groups/app/views/projects/calendar.rhtml @@ -23,7 +23,7 @@ <% content_for :sidebar do %>

    <%= l(:label_calendar) %>

    - <% form_tag() do %> + <% form_tag({}, :method => :get) do %>

    <%= select_month(@month, :prefix => "month", :discard_type => true) %> <%= select_year(@year, :prefix => "year", :discard_type => true) %>

    diff --git a/groups/app/views/projects/gantt.rfpdf b/groups/app/views/projects/gantt.rfpdf index a293906ba..e94fc5814 100644 --- a/groups/app/views/projects/gantt.rfpdf +++ b/groups/app/views/projects/gantt.rfpdf @@ -124,9 +124,9 @@ pdf.SetFontStyle('B',7) if i.is_a? Issue i_start_date = (i.start_date >= @date_from ? i.start_date : @date_from ) - i_end_date = (i.due_date <= @date_to ? i.due_date : @date_to ) + i_end_date = (i.due_before <= @date_to ? i.due_before : @date_to ) - i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor + i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date ) i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date ) diff --git a/groups/app/views/projects/gantt.rhtml b/groups/app/views/projects/gantt.rhtml index d941d2777..b18bca34c 100644 --- a/groups/app/views/projects/gantt.rhtml +++ b/groups/app/views/projects/gantt.rhtml @@ -166,9 +166,9 @@ top = headers_height + 10 @events.each do |i| if i.is_a? Issue i_start_date = (i.start_date >= @date_from ? i.start_date : @date_from ) - i_end_date = (i.due_date <= @date_to ? i.due_date : @date_to ) + i_end_date = (i.due_before <= @date_to ? i.due_before : @date_to ) - i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor + i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date ) i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date ) @@ -190,7 +190,6 @@ top = headers_height + 10 <%= i.status.name %> <%= (i.done_ratio).to_i %>% - <% # === tooltip === %>
    <%= render_issue_tooltip i %> @@ -235,7 +234,7 @@ if Date.today >= @date_from and Date.today <= @date_to %> <% content_for :sidebar do %>

    <%= l(:label_gantt) %>

    - <% form_tag(params.merge(:tracker_ids => nil, :with_subprojects => nil)) do %> + <% form_tag(params.merge(:tracker_ids => nil, :with_subprojects => nil), :method => :get) do %> <% @trackers.each do |tracker| %>
    <% end %> @@ -243,7 +242,7 @@ if Date.today >= @date_from and Date.today <= @date_to %>
    <%= hidden_field_tag 'with_subprojects', 0 %> <% end %> -

    <%= submit_tag l(:button_apply), :class => 'button-small' %>

    +

    <%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %>

    <% end %> <% end %> diff --git a/groups/app/views/projects/index.rhtml b/groups/app/views/projects/index.rhtml new file mode 100644 index 000000000..4c68717f5 --- /dev/null +++ b/groups/app/views/projects/index.rhtml @@ -0,0 +1,31 @@ +
    + <%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add') + ' |' if User.current.admin? %> + <%= link_to l(:label_issue_view_all), { :controller => 'issues' } %> | + <%= link_to l(:label_overall_activity), { :controller => 'projects', :action => 'activity' }%> +
    + +

    <%=l(:label_project_plural)%>

    + +<% @project_tree.keys.sort.each do |project| %> +

    <%= link_to h(project.name), {:action => 'show', :id => project}, :class => (User.current.member_of?(project) ? "icon icon-fav" : "") %>

    +<%= textilizable(project.short_description, :project => project) %> + +<% if @project_tree[project].any? %> +

    <%= l(:label_subproject_plural) %>: + <%= @project_tree[project].sort.collect {|subproject| + link_to(h(subproject.name), {:action => 'show', :id => subproject}, :class => (User.current.member_of?(subproject) ? "icon icon-fav" : ""))}.join(', ') %>

    +<% end %> +<% end %> + +<% if User.current.logged? %> +

    +<%= l(:label_my_projects) %> +

    +<% end %> + +

    +<%= l(:label_export_to) %> +<%= link_to 'Atom', {:format => 'atom', :key => User.current.rss_key}, :class => 'feed' %> +

    + +<% html_title(l(:label_project_plural)) -%> diff --git a/groups/app/views/projects/list.rhtml b/groups/app/views/projects/list.rhtml deleted file mode 100644 index b8bb62ebb..000000000 --- a/groups/app/views/projects/list.rhtml +++ /dev/null @@ -1,25 +0,0 @@ -
    - <%= link_to l(:label_issue_view_all), { :controller => 'issues' } %> | - <%= link_to l(:label_overall_activity), { :controller => 'projects', :action => 'activity' }%> -
    - -

    <%=l(:label_project_plural)%>

    - -<% @project_tree.keys.sort.each do |project| %> -

    <%= link_to h(project.name), {:action => 'show', :id => project}, :class => (User.current.member_of?(project) ? "icon icon-fav" : "") %>

    -<%= textilizable(project.short_description, :project => project) %> - -<% if @project_tree[project].any? %> -

    <%= l(:label_subproject_plural) %>: - <%= @project_tree[project].sort.collect {|subproject| - link_to(h(subproject.name), {:action => 'show', :id => subproject}, :class => (User.current.member_of?(subproject) ? "icon icon-fav" : ""))}.join(', ') %>

    -<% end %> -<% end %> - -<% if User.current.logged? %> -
    -<%= l(:label_my_projects) %> -
    -<% end %> - -<% html_title(l(:label_project_plural)) -%> diff --git a/groups/app/views/projects/list_files.rhtml b/groups/app/views/projects/list_files.rhtml index f385229ae..79e41f16d 100644 --- a/groups/app/views/projects/list_files.rhtml +++ b/groups/app/views/projects/list_files.rhtml @@ -23,8 +23,7 @@ <% for file in version.attachments %> "> - <%= link_to(file.filename, {:controller => 'versions', :action => 'download', :id => version, :attachment_id => file}, - :title => file.description) %> + <%= link_to_attachment file, :download => true, :title => file.description %> <%= format_time(file.created_on) %> <%= number_to_human_size(file.filesize) %> <%= file.downloads %> diff --git a/groups/app/views/projects/roadmap.rhtml b/groups/app/views/projects/roadmap.rhtml index d9329d109..0778d8138 100644 --- a/groups/app/views/projects/roadmap.rhtml +++ b/groups/app/views/projects/roadmap.rhtml @@ -20,7 +20,7 @@ @@ -30,7 +30,7 @@ <% end %> <% content_for :sidebar do %> -<% form_tag do %> +<% form_tag({}, :method => :get) do %>

    <%= l(:label_roadmap) %>

    <% @trackers.each do |tracker| %>
    -<%= submit_tag((@repository.nil? || @repository.new_record?) ? l(:button_create) : l(:button_save)) %> +<%= submit_tag((@repository.nil? || @repository.new_record?) ? l(:button_create) : l(:button_save), :disabled => @repository.nil?) %> <% end %> diff --git a/groups/app/views/projects/show.rhtml b/groups/app/views/projects/show.rhtml index 66c4838d6..778c8c220 100644 --- a/groups/app/views/projects/show.rhtml +++ b/groups/app/views/projects/show.rhtml @@ -3,14 +3,14 @@
    <%= textilizable @project.description %>
      - <% unless @project.homepage.blank? %>
    • <%=l(:field_homepage)%>: <%= auto_link @project.homepage %>
    • <% end %> + <% unless @project.homepage.blank? %>
    • <%=l(:field_homepage)%>: <%= auto_link(h(@project.homepage)) %>
    • <% end %> <% if @subprojects.any? %>
    • <%=l(:label_subproject_plural)%>: <%= @subprojects.collect{|p| link_to(h(p.name), :action => 'show', :id => p)}.join(", ") %>
    • <% end %> <% if @project.parent %>
    • <%=l(:field_parent)%>: <%= link_to h(@project.parent.name), :controller => 'projects', :action => 'show', :id => @project.parent %>
    • <% end %> - <% for custom_value in @custom_values %> + <% @project.custom_values.each do |custom_value| %> <% if !custom_value.value.empty? %>
    • <%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %>
    • <% end %> diff --git a/groups/app/views/queries/_filters.rhtml b/groups/app/views/queries/_filters.rhtml index ec9d4fef6..c9d612364 100644 --- a/groups/app/views/queries/_filters.rhtml +++ b/groups/app/views/queries/_filters.rhtml @@ -78,7 +78,7 @@ function toggle_multi_select(field) { - <%= link_to_function image_tag('expand.png'), "toggle_multi_select('#{field}');" %> + <%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('#{field}');", :style => "vertical-align: bottom;" %> <% when :date, :date_past %> <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %> <% when :string, :text %> diff --git a/groups/app/views/repositories/_dir_list_content.rhtml b/groups/app/views/repositories/_dir_list_content.rhtml index 3564e52ab..20473a264 100644 --- a/groups/app/views/repositories/_dir_list_content.rhtml +++ b/groups/app/views/repositories/_dir_list_content.rhtml @@ -1,32 +1,24 @@ <% @entries.each do |entry| %> <% tr_id = Digest::MD5.hexdigest(entry.path) depth = params[:depth].to_i %> - - -<%= if entry.is_dir? - link_to_remote h(entry.name), - {:url => {:action => 'browse', :id => @project, :path => entry.path, :rev => @rev, :depth => (depth + 1), :parent_id => tr_id}, + + +<% if entry.is_dir? %> + "scmEntryClick('#{tr_id}')" - }, - {:href => url_for({:action => 'browse', :id => @project, :path => entry.path, :rev => @rev}), - :class => ('icon icon-folder'), - :style => "margin-left: #{18 * depth}px;" - } -else - link_to h(entry.name), - {:action => (entry.is_dir? ? 'browse' : 'changes'), :id => @project, :path => entry.path, :rev => @rev}, - :class => 'icon icon-file', - :style => "margin-left: #{18 * depth}px;" -end %> + :condition => "scmEntryClick('#{tr_id}')"%>">  +<% end %> +<%= link_to h(entry.name), + {:action => (entry.is_dir? ? 'browse' : 'changes'), :id => @project, :path => to_path_param(entry.path), :rev => @rev}, + :class => (entry.is_dir? ? 'icon icon-folder' : 'icon icon-file')%> <%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %> <%= link_to(format_revision(entry.lastrev.name), :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %> <%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %> <%=h(entry.lastrev.author.to_s.split('<').first) if entry.lastrev %> -<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev %> +<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %> <%=h truncate(changeset.comments, 50) unless changeset.nil? %> <% end %> diff --git a/groups/app/views/repositories/_navigation.rhtml b/groups/app/views/repositories/_navigation.rhtml index b7ac989bc..25a15f496 100644 --- a/groups/app/views/repositories/_navigation.rhtml +++ b/groups/app/views/repositories/_navigation.rhtml @@ -10,10 +10,10 @@ dirs.each do |dir| link_path << '/' unless link_path.empty? link_path << "#{dir}" %> - / <%= link_to h(dir), :action => 'browse', :id => @project, :path => link_path, :rev => @rev %> + / <%= link_to h(dir), :action => 'browse', :id => @project, :path => to_path_param(link_path), :rev => @rev %> <% end %> <% if filename %> - / <%= link_to h(filename), :action => 'changes', :id => @project, :path => "#{link_path}/#{filename}", :rev => @rev %> + / <%= link_to h(filename), :action => 'changes', :id => @project, :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %> <% end %> <%= "@ #{revision}" if revision %> diff --git a/groups/app/views/repositories/_revisions.rhtml b/groups/app/views/repositories/_revisions.rhtml index 1bcf0208c..a938fecb8 100644 --- a/groups/app/views/repositories/_revisions.rhtml +++ b/groups/app/views/repositories/_revisions.rhtml @@ -1,4 +1,4 @@ -<% form_tag({:controller => 'repositories', :action => 'diff', :id => @project, :path => path}, :method => :get) do %> +<% form_tag({:controller => 'repositories', :action => 'diff', :id => @project, :path => to_path_param(path)}, :method => :get) do %> diff --git a/groups/app/views/repositories/browse.rhtml b/groups/app/views/repositories/browse.rhtml index 868388f11..4029a77d2 100644 --- a/groups/app/views/repositories/browse.rhtml +++ b/groups/app/views/repositories/browse.rhtml @@ -7,6 +7,7 @@

      <%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %>

      <%= render :partial => 'dir_list' %> +<%= render_properties(@properties) %> <% content_for :header_tags do %> <%= stylesheet_link_tag "scm" %> diff --git a/groups/app/views/repositories/changes.rhtml b/groups/app/views/repositories/changes.rhtml index 2d7462b29..ca5c58328 100644 --- a/groups/app/views/repositories/changes.rhtml +++ b/groups/app/views/repositories/changes.rhtml @@ -1,18 +1,19 @@

      <%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %>

      -

      <%=h @entry.name %>

      -

      <% if @repository.supports_cat? %> - <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> | + <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev } %> | <% end %> <% if @repository.supports_annotate? %> - <%= link_to l(:button_annotate), {:action => 'annotate', :id => @project, :path => @path, :rev => @rev } %> | + <%= link_to l(:button_annotate), {:action => 'annotate', :id => @project, :path => to_path_param(@path), :rev => @rev } %> | <% end %> -<%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %> +<%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %> <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>

      -<%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }%> +<%= render_properties(@properties) %> + +<%= render(:partial => 'revisions', + :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %> <% html_title(l(:label_change_plural)) -%> diff --git a/groups/app/views/repositories/diff.rhtml b/groups/app/views/repositories/diff.rhtml index eaef1abf5..52a5d6057 100644 --- a/groups/app/views/repositories/diff.rhtml +++ b/groups/app/views/repositories/diff.rhtml @@ -11,82 +11,14 @@ <%= select_tag 'type', options_for_select([[l(:label_diff_inline), "inline"], [l(:label_diff_side_by_side), "sbs"]], @diff_type), :onchange => "if (this.value != '') {this.form.submit()}" %>

      <% end %> -<% cache(@cache_key) do %> -<% @diff.each do |table_file| %> -
      -<% if @diff_type == 'sbs' %> -
      #
      - - - - - - - - - - - <% table_file.keys.sort.each do |key| %> - - - - - - - <% end %> - -
      - <%= table_file.file_name %> -
      @<%= format_revision @rev %>@<%= format_revision @rev_to %>
      - <%= table_file[key].nb_line_left %> - -
      <%=to_utf8 table_file[key].line_left %>
      -
      - <%= table_file[key].nb_line_right %> - -
      <%=to_utf8 table_file[key].line_right %>
      -
      +<% cache(@cache_key) do -%> +<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type} %> +<% end -%> -<% else %> - - - - - - - - - - - - - <% table_file.keys.sort.each do |key, line| %> - - - - <% if table_file[key].line_left.empty? %> - - <% else %> - - <% end %> - - <% end %> - -
      - <%= table_file.file_name %> -
      @<%= format_revision @rev %>@<%= format_revision @rev_to %>
      - <%= table_file[key].nb_line_left %> - - <%= table_file[key].nb_line_right %> - -
      <%=to_utf8 table_file[key].line_right %>
      -
      -
      <%=to_utf8 table_file[key].line_left %>
      -
      -<% end %> -
    -<% end %> -<% end %> +

    +<%= l(:label_export_to) %> +<%= link_to 'Unified diff', params.merge(:format => 'diff') %> +

    <% html_title(with_leading_slash(@path), 'Diff') -%> diff --git a/groups/app/views/repositories/entry.rhtml b/groups/app/views/repositories/entry.rhtml index 309da76fc..8e1e1992c 100644 --- a/groups/app/views/repositories/entry.rhtml +++ b/groups/app/views/repositories/entry.rhtml @@ -1,16 +1,6 @@

    <%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %>

    -
    - - -<% line_num = 1 %> -<% syntax_highlight(@path, to_utf8(@content)).each_line do |line| %> - -<% line_num += 1 %> -<% end %> - -
    <%= line_num %>
    <%= line %>
    -
    +<%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %> <% content_for :header_tags do %> <%= stylesheet_link_tag "scm" %> diff --git a/groups/app/views/repositories/revision.rhtml b/groups/app/views/repositories/revision.rhtml index f1e176669..80ac3bd1a 100644 --- a/groups/app/views/repositories/revision.rhtml +++ b/groups/app/views/repositories/revision.rhtml @@ -13,9 +13,9 @@ <% end -%> »  - <% form_tag do %> + <% form_tag({:controller => 'repositories', :action => 'revision', :id => @project, :rev => nil}, :method => :get) do %> <%= text_field_tag 'rev', @rev, :size => 5 %> - <%= submit_tag 'OK' %> + <%= submit_tag 'OK', :name => nil %> <% end %> @@ -46,10 +46,16 @@ <% @changes.each do |change| %> -
    <%= change.path %> <%= "(#{change.revision})" unless change.revision.blank? %> +
    +<% if change.action == "D" -%> + <%= change.path -%> +<% else -%> + <%= link_to change.path, :action => 'entry', :id => @project, :path => to_path_param(change.relative_path), :rev => @changeset.revision -%> +<% end -%> +<%= "(#{change.revision})" unless change.revision.blank? %> <% if change.action == "M" %> -<%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => without_leading_slash(change.path), :rev => @changeset.revision %> +<%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => to_path_param(change.relative_path), :rev => @changeset.revision %> <% end %> diff --git a/groups/app/views/repositories/show.rhtml b/groups/app/views/repositories/show.rhtml index 469ac063e..9a73183e8 100644 --- a/groups/app/views/repositories/show.rhtml +++ b/groups/app/views/repositories/show.rhtml @@ -1,11 +1,16 @@
    <%= link_to l(:label_statistics), {:action => 'stats', :id => @project}, :class => 'icon icon-stats' %> + +<% if !@entries.nil? && authorize_for('repositories', 'browse') -%> +<% form_tag(:action => 'browse', :id => @project) do -%> +| <%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %> +<% end -%> +<% end -%>

    <%= l(:label_repository) %> (<%= @repository.scm_name %>)

    <% if !@entries.nil? && authorize_for('repositories', 'browse') %> -

    <%= l(:label_browse) %>

    <%= render :partial => 'dir_list' %> <% end %> diff --git a/groups/app/views/repositories/stats.rhtml b/groups/app/views/repositories/stats.rhtml index 76ce892d5..1e617577e 100644 --- a/groups/app/views/repositories/stats.rhtml +++ b/groups/app/views/repositories/stats.rhtml @@ -1,13 +1,12 @@

    <%= l(:label_statistics) %>

    - - -
    -<%= tag("embed", :width => 500, :height => 300, :type => "image/svg+xml", :src => url_for(:controller => 'repositories', :action => 'graph', :id => @project, :graph => "commits_per_month")) %> - -<%= tag("embed", :width => 500, :height => 300, :type => "image/svg+xml", :src => url_for(:controller => 'repositories', :action => 'graph', :id => @project, :graph => "commits_per_author")) %> -
    -
    +

    +<%= tag("embed", :width => 800, :height => 300, :type => "image/svg+xml", :src => url_for(:controller => 'repositories', :action => 'graph', :id => @project, :graph => "commits_per_month")) %> +

    +

    +<%= tag("embed", :width => 800, :height => 400, :type => "image/svg+xml", :src => url_for(:controller => 'repositories', :action => 'graph', :id => @project, :graph => "commits_per_author")) %> +

    +

    <%= link_to l(:button_back), :action => 'show', :id => @project %>

    <% html_title(l(:label_repository), l(:label_statistics)) -%> diff --git a/groups/app/views/roles/_form.rhtml b/groups/app/views/roles/_form.rhtml index 58dc2af41..4aad45471 100644 --- a/groups/app/views/roles/_form.rhtml +++ b/groups/app/views/roles/_form.rhtml @@ -12,7 +12,7 @@ <% end %>

    <%= l(:label_permissions) %>

    -
    +
    <% perms_by_module = @permissions.group_by {|p| p.project_module.to_s} %> <% perms_by_module.keys.sort.each do |mod| %>
    <%= mod.blank? ? l(:label_project) : mod.humanize %> @@ -24,6 +24,6 @@ <% end %>
    <% end %> -
    <%= check_all_links 'role_form' %> +
    <%= check_all_links 'permissions' %> <%= hidden_field_tag 'role[permissions][]', '' %>
    diff --git a/groups/app/views/roles/report.rhtml b/groups/app/views/roles/report.rhtml index 98c3b651e..8e254379e 100644 --- a/groups/app/views/roles/report.rhtml +++ b/groups/app/views/roles/report.rhtml @@ -1,13 +1,17 @@

    <%=l(:label_permissions_report)%>

    <% form_tag({:action => 'report'}, :id => 'permissions_form') do %> -<%= hidden_field_tag 'permissions[0]', '' %> +<%= hidden_field_tag 'permissions[0]', '', :id => nil %> <% @roles.each do |role| %> - + <% end %> @@ -18,12 +22,16 @@ <%= content_tag('th', mod.humanize, :colspan => (@roles.size + 1), :align => 'left') %> <% end %> <% perms_by_module[mod].each do |permission| %> - - + + <% @roles.each do |role| %> <% end %> diff --git a/groups/app/views/search/index.rhtml b/groups/app/views/search/index.rhtml index 29c604a21..cb5b70a4c 100644 --- a/groups/app/views/search/index.rhtml +++ b/groups/app/views/search/index.rhtml @@ -4,27 +4,33 @@ <% form_tag({}, :method => :get) do %>

    <%= text_field_tag 'q', @question, :size => 60, :id => 'search-input' %> <%= javascript_tag "Field.focus('search-input')" %> - -<% @object_types.each do |t| %> - -<% end %> -
    +<%= project_select_tag %>

    -<%= submit_tag l(:button_submit), :name => 'submit' %> +

    +<% @object_types.each do |t| %> + +<% end %> +

    + +

    <%= submit_tag l(:button_submit), :name => 'submit' %>

    <% end %> <% if @results %> -

    <%= l(:label_result_plural) %>

    -
      +
      + <%= render_results_by_type(@results_by_type) unless @scope.size == 1 %> +
      + +

      <%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)

      +
      <% @results.each do |e| %> -
    • <%= link_to highlight_tokens(truncate(e.event_title, 255), @tokens), e.event_url %>
      - <%= highlight_tokens(e.event_description, @tokens) %>
      - <%= format_time(e.event_datetime) %>

    • +
      <%= content_tag('span', h(e.project), :class => 'project') unless @project == e.project %> <%= link_to highlight_tokens(truncate(e.event_title, 255), @tokens), e.event_url %>
      +
      <%= highlight_tokens(e.event_description, @tokens) %> + <%= format_time(e.event_datetime) %>
      <% end %> -
    + <% end %>

    diff --git a/groups/app/views/settings/_mail_handler.rhtml b/groups/app/views/settings/_mail_handler.rhtml new file mode 100644 index 000000000..830b1ba4a --- /dev/null +++ b/groups/app/views/settings/_mail_handler.rhtml @@ -0,0 +1,18 @@ +<% form_tag({:action => 'edit', :tab => 'mail_handler'}) do %> + +
    +

    +<%= check_box_tag 'settings[mail_handler_api_enabled]', 1, Setting.mail_handler_api_enabled?, + :onclick => "if (this.checked) { Form.Element.enable('settings_mail_handler_api_key'); } else { Form.Element.disable('settings_mail_handler_api_key'); }" %> +<%= hidden_field_tag 'settings[mail_handler_api_enabled]', 0 %>

    + +

    +<%= text_field_tag 'settings[mail_handler_api_key]', Setting.mail_handler_api_key, + :size => 30, + :id => 'settings_mail_handler_api_key', + :disabled => !Setting.mail_handler_api_enabled? %> +<%= link_to_function l(:label_generate_key), "if ($('settings_mail_handler_api_key').disabled == false) { $('settings_mail_handler_api_key').value = randomKey(20) }" %>

    +
    + +<%= submit_tag l(:button_save) %> +<% end %> diff --git a/groups/app/views/settings/_notifications.rhtml b/groups/app/views/settings/_notifications.rhtml index ac3213853..36701463a 100644 --- a/groups/app/views/settings/_notifications.rhtml +++ b/groups/app/views/settings/_notifications.rhtml @@ -1,3 +1,4 @@ +<% if @deliveries %> <% form_tag({:action => 'edit', :tab => 'notifications'}) do %>
    @@ -9,13 +10,13 @@ <%= hidden_field_tag 'settings[bcc_recipients]', 0 %>

    -
    <%=l(:text_select_mail_notifications)%> +
    <%=l(:text_select_mail_notifications)%> <% @notifiables.each do |notifiable| %>
    <% end %> <%= hidden_field_tag 'settings[notified_events][]', '' %> -

    <%= check_all_links('mail-options-form') %>

    +

    <%= check_all_links('notified_events') %>

    <%= l(:setting_emails_footer) %> @@ -28,3 +29,8 @@ <%= submit_tag l(:button_save) %> <% end %> +<% else %> +
    +<%= simple_format(l(:text_email_delivery_not_configured)) %> +
    +<% end %> diff --git a/groups/app/views/settings/_repositories.rhtml b/groups/app/views/settings/_repositories.rhtml index 59b3b51de..a8c924430 100644 --- a/groups/app/views/settings/_repositories.rhtml +++ b/groups/app/views/settings/_repositories.rhtml @@ -7,8 +7,18 @@

    <%= check_box_tag 'settings[sys_api_enabled]', 1, Setting.sys_api_enabled? %><%= hidden_field_tag 'settings[sys_api_enabled]', 0 %>

    +

    +<% REDMINE_SUPPORTED_SCM.each do |scm| -%> +<%= check_box_tag 'settings[enabled_scm][]', scm, Setting.enabled_scm.include?(scm) %> <%= scm %> +<% end -%> +<%= hidden_field_tag 'settings[enabled_scm][]', '' %> +

    +

    <%= text_field_tag 'settings[repositories_encodings]', Setting.repositories_encodings, :size => 60 %>
    <%= l(:text_comma_separated) %>

    + +

    +<%= select_tag 'settings[commit_logs_encoding]', options_for_select(Setting::ENCODINGS, Setting.commit_logs_encoding) %>

    <%= l(:text_issues_ref_in_commit_messages) %> diff --git a/groups/app/views/timelog/_list.rhtml b/groups/app/views/timelog/_list.rhtml index 189f4f5e8..8aebd75de 100644 --- a/groups/app/views/timelog/_list.rhtml +++ b/groups/app/views/timelog/_list.rhtml @@ -27,9 +27,9 @@
    <%= '' * level %> - + <%= '' * (criterias.length - level - 1) -%> <% total = 0 -%> <% @periods.each do |period| -%> diff --git a/groups/app/views/timelog/details.rhtml b/groups/app/views/timelog/details.rhtml index f02da9959..f111cbfc0 100644 --- a/groups/app/views/timelog/details.rhtml +++ b/groups/app/views/timelog/details.rhtml @@ -24,8 +24,13 @@

    <%= l(:label_export_to) %> +<%= link_to 'Atom', {:issue_id => @issue, :format => 'atom', :key => User.current.rss_key}, :class => 'feed' %> <%= link_to 'CSV', params.merge(:format => 'csv'), :class => 'csv' %>

    <% end %> <% html_title l(:label_spent_time), l(:label_details) %> + +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, {:issue_id => @issue, :format => 'atom', :key => User.current.rss_key}, :title => l(:label_spent_time)) %> +<% end %> diff --git a/groups/app/views/timelog/edit.rhtml b/groups/app/views/timelog/edit.rhtml index f9dae8a99..0dd3503ec 100644 --- a/groups/app/views/timelog/edit.rhtml +++ b/groups/app/views/timelog/edit.rhtml @@ -9,7 +9,10 @@

    <%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %>

    <%= f.text_field :hours, :size => 6, :required => true %>

    <%= f.text_field :comments, :size => 100 %>

    -

    <%= f.select :activity_id, (@activities.collect {|p| [p.name, p.id]}), :required => true %>

    +

    <%= f.select :activity_id, activity_collection_for_select_options, :required => true %>

    +<% @time_entry.custom_field_values.each do |value| %> +

    <%= custom_field_tag_with_label :time_entry, value %>

    +<% end %> <%= submit_tag l(:button_save) %> diff --git a/groups/app/views/users/_form.rhtml b/groups/app/views/users/_form.rhtml index f2b330828..e305581fe 100644 --- a/groups/app/views/users/_form.rhtml +++ b/groups/app/views/users/_form.rhtml @@ -2,7 +2,6 @@
    -

    <%=l(:label_information_plural)%>

    <%= f.text_field :login, :required => true, :size => 25 %>

    <%= f.text_field :firstname, :required => true %>

    <%= f.text_field :lastname, :required => true %>

    @@ -12,11 +11,11 @@ <% end -%>

    <%= f.select :language, lang_options_for_select %>

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

    <%= custom_field_tag_with_label @custom_value %>

    -<% end if @custom_values%> +<% @user.custom_field_values.each do |value| %> +

    <%= custom_field_tag_with_label :user, value %>

    +<% end %> -

    <%= f.check_box :admin %>

    +

    <%= f.check_box :admin, :disabled => (@user == User.current) %>

    diff --git a/groups/app/views/users/_general.rhtml b/groups/app/views/users/_general.rhtml new file mode 100644 index 000000000..80615ff6c --- /dev/null +++ b/groups/app/views/users/_general.rhtml @@ -0,0 +1,4 @@ +<% labelled_tabular_form_for :user, @user, :url => { :action => "edit" } do |f| %> +<%= render :partial => 'form', :locals => { :f => f } %> +<%= submit_tag l(:button_save) %> +<% end %> diff --git a/groups/app/views/users/_memberships.rhtml b/groups/app/views/users/_memberships.rhtml index 06d3f6029..94b49159e 100644 --- a/groups/app/views/users/_memberships.rhtml +++ b/groups/app/views/users/_memberships.rhtml @@ -1,33 +1,40 @@ -
    -

    <%= l(:label_project_plural) %>

    - -<% @user.memberships.select {|m| m.inherited_from.nil? }.each do |membership| %> -<% form_tag({ :action => 'edit_membership', :id => @user, :membership_id => membership }, :class => "tabular") do %> -

    - - - <%= submit_tag l(:button_change), :class => "button-small" %> - <%= link_to l(:button_delete), {:action => 'destroy_membership', :id => @user, :membership_id => membership }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %> -

    -<% end %> +<% if @memberships.any? %> +
    <%=l(:label_permissions)%><%= content_tag(role.builtin? ? 'em' : 'span', h(role.name)) %> + <%= content_tag(role.builtin? ? 'em' : 'span', h(role.name)) %> + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.role-#{role.id}')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> +
    <%= permission.name.to_s.humanize %>
    + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('.permission-#{permission.name} input')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> + <%= permission.name.to_s.humanize %> + <% if role.setable_permissions.include? permission %> - <%= check_box_tag "permissions[#{role.id}][]", permission.name, (role.permissions.include? permission.name) %> + <%= check_box_tag "permissions[#{role.id}][]", permission.name, (role.permissions.include? permission.name), :id => nil, :class => "role-#{role.id}" %> <% end %> <%= html_hours("%.2f" % entry.hours) %> <% if entry.editable_by?(User.current) -%> - <%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => entry}, + <%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => entry, :project_id => nil}, :title => l(:button_edit) %> - <%= link_to image_tag('delete.png'), {:controller => 'timelog', :action => 'destroy', :id => entry}, + <%= link_to image_tag('delete.png'), {:controller => 'timelog', :action => 'destroy', :id => entry, :project_id => nil}, :confirm => l(:text_are_you_sure), :method => :post, :title => l(:button_delete) %> diff --git a/groups/app/views/timelog/_report_criteria.rhtml b/groups/app/views/timelog/_report_criteria.rhtml index 94f3d20f9..c9a1cfb45 100644 --- a/groups/app/views/timelog/_report_criteria.rhtml +++ b/groups/app/views/timelog/_report_criteria.rhtml @@ -3,7 +3,7 @@ <% next if hours_for_value.empty? -%>
    <%= format_criteria_value(criterias[level], value) %><%= h(format_criteria_value(criterias[level], value)) %>
    + + + + + + + <% @memberships.each do |membership| %> + <% next if membership.new_record? %> + + + + + + +<% end; reset_cycle %> +
    <%= l(:label_project) %><%= l(:label_role) %>
    <%=h membership.project %> + <% form_tag({ :action => 'edit_membership', :id => @user, :membership_id => membership }) do %> + <%= select_tag 'membership[role_id]', options_from_collection_for_select(@roles, "id", "name", membership.role_id) %> + <%= submit_tag l(:button_change), :class => "small" %> + <% end %> + + <%= link_to l(:button_delete), {:action => 'destroy_membership', :id => @user, :membership_id => membership }, :method => :post, :class => 'icon icon-del' %> +
    +<% else %> +

    <%= l(:label_no_data) %>

    <% end %> -<% unless @projects.empty? || @roles.empty? %> -
    +<% if @projects.any? %>


    <% form_tag({ :action => 'edit_membership', :id => @user }) do %> - +<%= select_tag 'membership[project_id]', projects_options_for_select(@projects) %> <%= l(:label_role) %>: - +<%= select_tag 'membership[role_id]', options_from_collection_for_select(@roles, "id", "name") %> <%= submit_tag l(:button_add) %> <% end %>

    <% end %> -
    \ No newline at end of file diff --git a/groups/app/views/users/edit.rhtml b/groups/app/views/users/edit.rhtml index 0da99d0d2..4714bcecb 100644 --- a/groups/app/views/users/edit.rhtml +++ b/groups/app/views/users/edit.rhtml @@ -1,8 +1,27 @@ -

    <%=l(:label_user)%>

    +
    +<%= change_status_link(@user) %> +
    -<% labelled_tabular_form_for :user, @user, :url => { :action => "edit" } do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<%= submit_tag l(:button_save) %> -<% end %> +

    <%=l(:label_user)%>: <%=h @user.login %>

    -<%= render :partial => 'memberships' %> \ No newline at end of file +<% selected_tab = params[:tab] ? params[:tab].to_s : user_settings_tabs.first[:name] %> + +
    +
      +<% user_settings_tabs.each do |tab| -%> +
    • <%= link_to l(tab[:label]), { :tab => tab[:name] }, + :id => "tab-#{tab[:name]}", + :class => (tab[:name] != selected_tab ? nil : 'selected'), + :onclick => "showTab('#{tab[:name]}'); this.blur(); return false;" %>
    • +<% end -%> +
    +
    + +<% user_settings_tabs.each do |tab| -%> +<%= content_tag('div', render(:partial => tab[:partial]), + :id => "tab-content-#{tab[:name]}", + :style => (tab[:name] != selected_tab ? 'display:none' : nil), + :class => 'tab-content') %> +<% end -%> + +<% html_title(l(:label_user), @user.login, l(:label_administration)) -%> diff --git a/groups/app/views/users/list.rhtml b/groups/app/views/users/list.rhtml index 47a629469..6e6861ea9 100644 --- a/groups/app/views/users/list.rhtml +++ b/groups/app/views/users/list.rhtml @@ -7,7 +7,7 @@ <% form_tag({}, :method => :get) do %>
    <%= l(:label_filter_plural) %> -<%= select_tag 'status', status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> +<%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %>
    <% end %>   @@ -27,10 +27,10 @@ <% for user in @users -%> <%= %w(anon active registered locked)[user.status] %>"> - <%= link_to user.login, :action => 'edit', :id => user %> - <%=h user.firstname %> - <%=h user.lastname %> - <%=h user.mail %> + <%= link_to h(user.login), :action => 'edit', :id => user %> + <%= h(user.firstname) %> + <%= h(user.lastname) %> + <%= mail_to(h(user.mail)) %> <%=h user.group %> <%= image_tag('true.png') if user.admin? %> <%= format_time(user.created_on) %> diff --git a/groups/app/views/versions/show.rhtml b/groups/app/views/versions/show.rhtml index 7f81cf503..7f9518af8 100644 --- a/groups/app/views/versions/show.rhtml +++ b/groups/app/views/versions/show.rhtml @@ -38,7 +38,7 @@ diff --git a/groups/app/views/watchers/_watchers.rhtml b/groups/app/views/watchers/_watchers.rhtml new file mode 100644 index 000000000..14bb5fc6b --- /dev/null +++ b/groups/app/views/watchers/_watchers.rhtml @@ -0,0 +1,25 @@ +
    +<%= link_to_remote l(:button_add), + :url => {:controller => 'watchers', + :action => 'new', + :object_type => watched.class.name.underscore, + :object_id => watched} if User.current.allowed_to?(:add_issue_watchers, @project) %> +
    + +

    <%= l(:label_issue_watchers) %>

    +<%= watchers_list(watched) %> + +<% unless @watcher.nil? %> +<% remote_form_for(:watcher, @watcher, + :url => {:controller => 'watchers', + :action => 'new', + :object_type => watched.class.name.underscore, + :object_id => watched}, + :method => :post, + :html => {:id => 'new-watcher-form'}) do |f| %> +

    <%= f.select :user_id, (watched.addable_watcher_users.collect {|m| [m.name, m.id]}), :prompt => true %> + +<%= submit_tag l(:button_add) %> +<%= toggle_link l(:button_cancel), 'new-watcher-form'%>

    +<% end %> +<% end %> diff --git a/groups/app/views/welcome/index.rhtml b/groups/app/views/welcome/index.rhtml index 5da5a1ed3..855248c5e 100644 --- a/groups/app/views/welcome/index.rhtml +++ b/groups/app/views/welcome/index.rhtml @@ -12,17 +12,19 @@
    + <% if @projects.any? %>

    <%=l(:label_project_latest)%>

      <% for project in @projects %>
    • - <%= link_to project.name, :controller => 'projects', :action => 'show', :id => project %> (<%= format_time(project.created_on) %>) + <%= link_to h(project.name), :controller => 'projects', :action => 'show', :id => project %> (<%= format_time(project.created_on) %>) <%= textilizable project.short_description, :project => project %>
    • <% end %>
    -
    +
    + <% end %> <% content_for :header_tags do %> diff --git a/groups/app/views/wiki/export.rhtml b/groups/app/views/wiki/export.rhtml index 1ab5c13e4..94b4e6f0d 100644 --- a/groups/app/views/wiki/export.rhtml +++ b/groups/app/views/wiki/export.rhtml @@ -6,6 +6,10 @@ diff --git a/groups/app/views/wiki/history.rhtml b/groups/app/views/wiki/history.rhtml index 6462e9fdd..7ce78a0f2 100644 --- a/groups/app/views/wiki/history.rhtml +++ b/groups/app/views/wiki/history.rhtml @@ -18,7 +18,7 @@ <% line_num = 1 %> <% @versions.each do |ver| %> "> - <%= link_to ver.version, :action => 'index', :page => @page.title, :version => ver.version %> + <%= link_to ver.version, :action => 'index', :page => @page.title, :version => ver.version %> <%= radio_button_tag('version', ver.version, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < @versions.size) %> <%= radio_button_tag('version_from', ver.version, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true || $('version_from').value > #{ver.version}) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %> <%= format_time(ver.updated_on) %> @@ -30,6 +30,6 @@ <% end %> -<%= submit_tag l(:label_view_diff), :class => 'small' %> +<%= submit_tag l(:label_view_diff), :class => 'small' if show_diff %> <%= pagination_links_full @version_pages, @version_count, :page_param => :p %> <% end %> diff --git a/groups/app/views/wiki/rename.rhtml b/groups/app/views/wiki/rename.rhtml index 0c069f43d..260f9af8b 100644 --- a/groups/app/views/wiki/rename.rhtml +++ b/groups/app/views/wiki/rename.rhtml @@ -4,8 +4,9 @@ <% labelled_tabular_form_for :wiki_page, @page, :url => { :action => 'rename' } do |f| %>
    -

    <%= f.text_field :title, :required => true, :size => 255 %>

    +

    <%= f.text_field :title, :required => true, :size => 100 %>

    <%= f.check_box :redirect_existing_links %>

    +

    <%= f.text_field :parent_title, :size => 100 %>

    <%= submit_tag l(:button_rename) %> <% end %> diff --git a/groups/app/views/wiki/show.rhtml b/groups/app/views/wiki/show.rhtml index e4413d090..255b904f5 100644 --- a/groups/app/views/wiki/show.rhtml +++ b/groups/app/views/wiki/show.rhtml @@ -1,11 +1,17 @@
    +<% if @editable %> <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) if @content.version == @page.content.version %> +<%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :page => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %> +<%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :page => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %> <%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :page => @page.title}, :class => 'icon icon-move') if @content.version == @page.content.version %> <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :page => @page.title}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %> <%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :page => @page.title, :version => @content.version }, :class => 'icon icon-cancel') if @content.version < @page.content.version %> +<% end %> <%= link_to(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %>
    +<%= breadcrumb(@page.ancestors.reverse.collect {|parent| link_to h(parent.pretty_title), {:page => parent.title}}) %> + <% if @content.version != @page.content.version %>

    <%= link_to(('« ' + l(:label_previous)), :action => 'index', :page => @page.title, :version => (@content.version - 1)) + " - " if @content.version > 1 %> @@ -22,9 +28,9 @@ <%= render(:partial => "wiki/content", :locals => {:content => @content}) %> -<%= link_to_attachments @page.attachments, :delete_url => (authorize_for('wiki', 'destroy_attachment') ? {:controller => 'wiki', :action => 'destroy_attachment', :page => @page.title} : nil) %> +<%= link_to_attachments @page.attachments, :delete_url => ((@editable && authorize_for('wiki', 'destroy_attachment')) ? {:controller => 'wiki', :action => 'destroy_attachment', :page => @page.title} : nil) %> -<% if authorize_for('wiki', 'add_attachment') %> +<% if @editable && authorize_for('wiki', 'add_attachment') %>

    <%= link_to l(:label_attachment_new), {}, :onclick => "Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;", :id => 'attach_files_link' %>

    <% form_tag({ :controller => 'wiki', :action => 'add_attachment', :page => @page.title }, :multipart => true, :id => "add_attachment_form", :style => "display:none;") do %> diff --git a/groups/app/views/wiki/special_page_index.rhtml b/groups/app/views/wiki/special_page_index.rhtml index f21cc3423..72b395ef7 100644 --- a/groups/app/views/wiki/special_page_index.rhtml +++ b/groups/app/views/wiki/special_page_index.rhtml @@ -4,11 +4,7 @@

    <%= l(:label_no_data) %>

    <% end %> - +<%= render_page_hierarchy(@pages_by_parent_id) %> <% content_for :sidebar do %> <%= render :partial => 'sidebar' %> diff --git a/groups/config/boot.rb b/groups/config/boot.rb index 9fcd50fe3..cd21fb9ea 100644 --- a/groups/config/boot.rb +++ b/groups/config/boot.rb @@ -1,19 +1,109 @@ -# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb +# Don't change this file! +# Configure your app 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 +RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) + +module Rails + class << self + def boot! + unless booted? + preinitialize + pick_boot.run + end + end + + def booted? + defined? Rails::Initializer + end + + def pick_boot + (vendor_rails? ? VendorBoot : GemBoot).new + end + + def vendor_rails? + File.exist?("#{RAILS_ROOT}/vendor/rails") + end + + def preinitialize + load(preinitializer_path) if File.exist?(preinitializer_path) + end + + def preinitializer_path + "#{RAILS_ROOT}/config/preinitializer.rb" + end 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' + class Boot + def run + load_initializer + Rails::Initializer.run(:set_load_path) + end + end + + class VendorBoot < Boot + def load_initializer + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" + Rails::Initializer.run(:install_gem_spec_stubs) + end + end + + class GemBoot < Boot + def load_initializer + self.class.load_rubygems + load_rails_gem + require 'initializer' + end + + def load_rails_gem + if version = self.class.gem_version + gem 'rails', version + else + gem 'rails' + end + rescue Gem::LoadError => load_error + $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) + exit 1 + end + + class << self + def rubygems_version + Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion + end + + def gem_version + if defined? RAILS_GEM_VERSION + RAILS_GEM_VERSION + elsif ENV.include?('RAILS_GEM_VERSION') + ENV['RAILS_GEM_VERSION'] + else + parse_gem_version(read_environment_rb) + end + end + + def load_rubygems + require 'rubygems' + + unless rubygems_version >= '0.9.4' + $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.) + exit 1 + end + + rescue LoadError + $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org) + exit 1 + end + + def parse_gem_version(text) + $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ + end + + private + def read_environment_rb + File.read("#{RAILS_ROOT}/config/environment.rb") + end + end + end end -Rails::Initializer.run(:set_load_path) +# All that for this: +Rails.boot! diff --git a/groups/config/database.yml.example b/groups/config/database.yml.example index f72844a07..1dc678131 100644 --- a/groups/config/database.yml.example +++ b/groups/config/database.yml.example @@ -12,6 +12,7 @@ production: host: localhost username: root password: + encoding: utf8 development: adapter: mysql @@ -19,6 +20,7 @@ development: host: localhost username: root password: + encoding: utf8 test: adapter: mysql @@ -26,6 +28,7 @@ test: host: localhost username: root password: + encoding: utf8 test_pgsql: adapter: postgresql diff --git a/groups/config/email.yml.example b/groups/config/email.yml.example new file mode 100644 index 000000000..685096da4 --- /dev/null +++ b/groups/config/email.yml.example @@ -0,0 +1,21 @@ +# Outgoing email settings + +production: + delivery_method: :smtp + smtp_settings: + address: smtp.example.net + port: 25 + domain: example.net + authentication: :login + user_name: redmine@example.net + password: redmine + +development: + delivery_method: :smtp + smtp_settings: + address: 127.0.0.1 + port: 25 + domain: example.net + authentication: :login + user_name: redmine@example.net + password: redmine diff --git a/groups/config/environment.rb b/groups/config/environment.rb index 7878eca47..9a3bf4b1d 100644 --- a/groups/config/environment.rb +++ b/groups/config/environment.rb @@ -5,7 +5,7 @@ # ENV['RAILS_ENV'] ||= 'production' # Specifies gem version of Rails to use when vendor/rails is not present -RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION +RAILS_GEM_VERSION = '2.1.0' unless defined? RAILS_GEM_VERSION # Bootstrap the Rails environment, frameworks, and default configuration require File.join(File.dirname(__FILE__), 'boot') @@ -31,7 +31,7 @@ Rails::Initializer.run do |config| # config.log_level = :debug # Use the database for sessions instead of the file system - # (create the session table with 'rake create_sessions_table') + # (create the session table with 'rake db:sessions:create') # config.action_controller.session_store = :active_record_store config.action_controller.session_store = :PStore @@ -49,54 +49,9 @@ Rails::Initializer.run do |config| # 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.smtp_settings = { - :address => "127.0.0.1", - :port => 25, - :domain => "somenet.foo", - :authentication => :login, - :user_name => "redmine@somenet.foo", - :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 - + # Deliveries are disabled by default. Do NOT modify this section. + # Define your email configuration in email.yml instead. + # It will automatically turn deliveries on + config.action_mailer.perform_deliveries = false end - -ActiveRecord::Errors.default_error_messages = { - :inclusion => "activerecord_error_inclusion", - :exclusion => "activerecord_error_exclusion", - :invalid => "activerecord_error_invalid", - :confirmation => "activerecord_error_confirmation", - :accepted => "activerecord_error_accepted", - :empty => "activerecord_error_empty", - :blank => "activerecord_error_blank", - :too_long => "activerecord_error_too_long", - :too_short => "activerecord_error_too_short", - :wrong_length => "activerecord_error_wrong_length", - :taken => "activerecord_error_taken", - :not_a_number => "activerecord_error_not_a_number" -} - -ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" } - -Mime::SET << Mime::CSV unless Mime::SET.include?(Mime::CSV) -Mime::Type.register 'application/pdf', :pdf - -GLoc.set_config :default_language => :en -GLoc.clear_strings -GLoc.set_kcode -GLoc.load_localized_strings -GLoc.set_config(:raise_string_not_found_errors => false) - -require 'redmine' - diff --git a/groups/config/environments/test.rb b/groups/config/environments/test.rb index 9ba9ae0f8..7c821da07 100644 --- a/groups/config/environments/test.rb +++ b/groups/config/environments/test.rb @@ -13,4 +13,5 @@ config.whiny_nils = true config.action_controller.consider_all_requests_local = true config.action_controller.perform_caching = false +config.action_mailer.perform_deliveries = true config.action_mailer.delivery_method = :test diff --git a/groups/config/environments/test_pgsql.rb b/groups/config/environments/test_pgsql.rb index 35bb19bee..7c821da07 100644 --- a/groups/config/environments/test_pgsql.rb +++ b/groups/config/environments/test_pgsql.rb @@ -13,4 +13,5 @@ config.whiny_nils = true config.action_controller.consider_all_requests_local = true config.action_controller.perform_caching = false -config.action_mailer.delivery_method = :test \ No newline at end of file +config.action_mailer.perform_deliveries = true +config.action_mailer.delivery_method = :test diff --git a/groups/config/environments/test_sqlite3.rb b/groups/config/environments/test_sqlite3.rb index 35bb19bee..7c821da07 100644 --- a/groups/config/environments/test_sqlite3.rb +++ b/groups/config/environments/test_sqlite3.rb @@ -13,4 +13,5 @@ config.whiny_nils = true config.action_controller.consider_all_requests_local = true config.action_controller.perform_caching = false -config.action_mailer.delivery_method = :test \ No newline at end of file +config.action_mailer.perform_deliveries = true +config.action_mailer.delivery_method = :test diff --git a/groups/config/initializers/10-patches.rb b/groups/config/initializers/10-patches.rb new file mode 100644 index 000000000..fcc091997 --- /dev/null +++ b/groups/config/initializers/10-patches.rb @@ -0,0 +1,17 @@ + +ActiveRecord::Errors.default_error_messages = { + :inclusion => "activerecord_error_inclusion", + :exclusion => "activerecord_error_exclusion", + :invalid => "activerecord_error_invalid", + :confirmation => "activerecord_error_confirmation", + :accepted => "activerecord_error_accepted", + :empty => "activerecord_error_empty", + :blank => "activerecord_error_blank", + :too_long => "activerecord_error_too_long", + :too_short => "activerecord_error_too_short", + :wrong_length => "activerecord_error_wrong_length", + :taken => "activerecord_error_taken", + :not_a_number => "activerecord_error_not_a_number" +} + +ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" } diff --git a/groups/config/initializers/20-mime_types.rb b/groups/config/initializers/20-mime_types.rb new file mode 100644 index 000000000..269742b16 --- /dev/null +++ b/groups/config/initializers/20-mime_types.rb @@ -0,0 +1,4 @@ +# Add new mime types for use in respond_to blocks: + +Mime::SET << Mime::CSV unless Mime::SET.include?(Mime::CSV) +Mime::Type.register 'application/pdf', :pdf diff --git a/groups/config/initializers/30-redmine.rb b/groups/config/initializers/30-redmine.rb new file mode 100644 index 000000000..f2a9f6a30 --- /dev/null +++ b/groups/config/initializers/30-redmine.rb @@ -0,0 +1,7 @@ +GLoc.set_config :default_language => :en +GLoc.clear_strings +GLoc.set_kcode +GLoc.load_localized_strings +GLoc.set_config(:raise_string_not_found_errors => false) + +require 'redmine' diff --git a/groups/config/initializers/40-email.rb b/groups/config/initializers/40-email.rb new file mode 100644 index 000000000..5b388ec59 --- /dev/null +++ b/groups/config/initializers/40-email.rb @@ -0,0 +1,17 @@ +# Loads action_mailer settings from email.yml +# and turns deliveries on if configuration file is found + +filename = File.join(File.dirname(__FILE__), '..', 'email.yml') +if File.file?(filename) + mailconfig = YAML::load_file(filename) + + if mailconfig.is_a?(Hash) && mailconfig.has_key?(Rails.env) + # Enable deliveries + ActionMailer::Base.perform_deliveries = true + + mailconfig[Rails.env].each do |k, v| + v.symbolize_keys! if v.respond_to?(:symbolize_keys!) + ActionMailer::Base.send("#{k}=", v) + end + end +end diff --git a/groups/config/routes.rb b/groups/config/routes.rb index bc4247837..4213df915 100644 --- a/groups/config/routes.rb +++ b/groups/config/routes.rb @@ -31,8 +31,13 @@ ActionController::Routing::Routes.draw do |map| omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff' omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry' omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate' + omap.repositories_revision 'repositories/revision/:id/:rev', :action => 'revision' end + map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/ + map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/ + map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/ + # Allow downloading Web Service WSDL as a file with an extension # instead of a file named 'wsdl' map.connect ':controller/service.wsdl', :action => 'wsdl' diff --git a/groups/config/settings.yml b/groups/config/settings.yml index bb501823e..ac79edb8d 100644 --- a/groups/config/settings.yml +++ b/groups/config/settings.yml @@ -43,7 +43,7 @@ activity_days_default: per_page_options: default: '25,50,100' mail_from: - default: redmine@somenet.foo + default: redmine@example.net bcc_recipients: default: 1 text_formatting: @@ -59,6 +59,15 @@ protocol: feeds_limit: format: int default: 15 +enabled_scm: + serialized: true + default: + - Subversion + - Darcs + - Mercurial + - Cvs + - Bazaar + - Git autofetch_changesets: default: 1 sys_api_enabled: @@ -92,6 +101,10 @@ notified_events: default: - issue_added - issue_updated +mail_handler_api_enabled: + default: 0 +mail_handler_api_key: + default: issue_list_default_columns: serialized: true default: @@ -109,6 +122,9 @@ default_projects_public: # multiple values accepted, comma separated repositories_encodings: default: '' +# encoding used to convert commit logs to UTF-8 +commit_logs_encoding: + default: 'UTF-8' ui_theme: default: '' emails_footer: diff --git a/groups/db/migrate/001_setup.rb b/groups/db/migrate/001_setup.rb index 1160dd5ef..d49e0e444 100644 --- a/groups/db/migrate/001_setup.rb +++ b/groups/db/migrate/001_setup.rb @@ -16,7 +16,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class Setup < ActiveRecord::Migration - + + class User < ActiveRecord::Base; end # model removed class Permission < ActiveRecord::Base; end @@ -284,13 +285,15 @@ class Setup < ActiveRecord::Migration Permission.create :controller => "versions", :action => "destroy_file", :description => "button_delete", :sort => 1322 # create default administrator account - user = User.create :firstname => "Redmine", :lastname => "Admin", :mail => "admin@somenet.foo", :mail_notification => true, :language => "en" - user.login = "admin" - user.password = "admin" - user.admin = true - user.save - - + user = User.create :login => "admin", + :hashed_password => "d033e22ae348aeb5660fc2140aec35850c4da997", + :admin => true, + :firstname => "Redmine", + :lastname => "Admin", + :mail => "admin@example.net", + :mail_notification => true, + :language => "en", + :status => 1 end def self.down diff --git a/groups/db/migrate/072_add_enumerations_position.rb b/groups/db/migrate/072_add_enumerations_position.rb index e0beaf395..22558a6e9 100644 --- a/groups/db/migrate/072_add_enumerations_position.rb +++ b/groups/db/migrate/072_add_enumerations_position.rb @@ -1,7 +1,7 @@ class AddEnumerationsPosition < ActiveRecord::Migration def self.up add_column(:enumerations, :position, :integer, :default => 1) unless Enumeration.column_names.include?('position') - Enumeration.find(:all).group_by(&:opt).each_value do |enums| + Enumeration.find(:all).group_by(&:opt).each do |opt, enums| enums.each_with_index do |enum, i| # do not call model callbacks Enumeration.update_all "position = #{i+1}", {:id => enum.id} diff --git a/groups/db/migrate/078_add_custom_fields_position.rb b/groups/db/migrate/078_add_custom_fields_position.rb index 7ee8abb58..1c42ae732 100644 --- a/groups/db/migrate/078_add_custom_fields_position.rb +++ b/groups/db/migrate/078_add_custom_fields_position.rb @@ -1,7 +1,7 @@ class AddCustomFieldsPosition < ActiveRecord::Migration def self.up add_column(:custom_fields, :position, :integer, :default => 1) - CustomField.find(:all).group_by(&:type).each_value do |fields| + CustomField.find(:all).group_by(&:type).each do |t, fields| fields.each_with_index do |field, i| # do not call model callbacks CustomField.update_all "position = #{i+1}", {:id => field.id} diff --git a/groups/db/migrate/096_add_wiki_pages_protected.rb b/groups/db/migrate/096_add_wiki_pages_protected.rb new file mode 100644 index 000000000..49720fbb7 --- /dev/null +++ b/groups/db/migrate/096_add_wiki_pages_protected.rb @@ -0,0 +1,9 @@ +class AddWikiPagesProtected < ActiveRecord::Migration + def self.up + add_column :wiki_pages, :protected, :boolean, :default => false, :null => false + end + + def self.down + remove_column :wiki_pages, :protected + end +end diff --git a/groups/db/migrate/097_change_projects_homepage_limit.rb b/groups/db/migrate/097_change_projects_homepage_limit.rb new file mode 100644 index 000000000..98374aa4e --- /dev/null +++ b/groups/db/migrate/097_change_projects_homepage_limit.rb @@ -0,0 +1,9 @@ +class ChangeProjectsHomepageLimit < ActiveRecord::Migration + def self.up + change_column :projects, :homepage, :string, :limit => nil, :default => '' + end + + def self.down + change_column :projects, :homepage, :string, :limit => 60, :default => '' + end +end diff --git a/groups/db/migrate/098_add_wiki_pages_parent_id.rb b/groups/db/migrate/098_add_wiki_pages_parent_id.rb new file mode 100644 index 000000000..36b922ec1 --- /dev/null +++ b/groups/db/migrate/098_add_wiki_pages_parent_id.rb @@ -0,0 +1,9 @@ +class AddWikiPagesParentId < ActiveRecord::Migration + def self.up + add_column :wiki_pages, :parent_id, :integer, :default => nil + end + + def self.down + remove_column :wiki_pages, :parent_id + end +end diff --git a/groups/doc/CHANGELOG b/groups/doc/CHANGELOG index b39185151..ac8cb6673 100644 --- a/groups/doc/CHANGELOG +++ b/groups/doc/CHANGELOG @@ -5,6 +5,86 @@ Copyright (C) 2006-2008 Jean-Philippe Lang http://www.redmine.org/ +== 2008-07-06 v0.7.3 + +* Allow dot in firstnames and lastnames +* Add project name to cross-project Atom feeds +* Encoding set to utf8 in example database.yml +* HTML titles on forums related views +* Fixed: various XSS vulnerabilities +* Fixed: Entourage (and some old client) fails to correctly render notification styles +* Fixed: Fixed: timelog redirects inappropriately when :back_url is blank +* Fixed: wrong relative paths to images in wiki_syntax.html + + +== 2008-06-15 v0.7.2 + +* "New Project" link on Projects page +* Links to repository directories on the repo browser +* Move status to front in Activity View +* Remove edit step from Status context menu +* Fixed: No way to do textile horizontal rule +* Fixed: Repository: View differences doesn't work +* Fixed: attachement's name maybe invalid. +* Fixed: Error when creating a new issue +* Fixed: NoMethodError on @available_filters.has_key? +* Fixed: Check All / Uncheck All in Email Settings +* Fixed: "View differences" of one file at /repositories/revision/ fails +* Fixed: Column width in "my page" +* Fixed: private subprojects are listed on Issues view +* Fixed: Textile: bold, italics, underline, etc... not working after parentheses +* Fixed: Update issue form: comment field from log time end out of screen +* Fixed: Editing role: "issue can be assigned to this role" out of box +* Fixed: Unable use angular braces after include word +* Fixed: Using '*' as keyword for repository referencing keywords doesn't work +* Fixed: Subversion repository "View differences" on each file rise ERROR +* Fixed: View differences for individual file of a changeset fails if the repository URL doesn't point to the repository root +* Fixed: It is possible to lock out the last admin account +* Fixed: Wikis are viewable for anonymous users on public projects, despite not granting access +* Fixed: Issue number display clipped on 'my issues' +* Fixed: Roadmap version list links not carrying state +* Fixed: Log Time fieldset in IssueController#edit doesn't set default Activity as default +* Fixed: git's "get_rev" API should use repo's current branch instead of hardwiring "master" +* Fixed: browser's language subcodes ignored +* Fixed: Error on project selection with numeric (only) identifier. +* Fixed: Link to PDF doesn't work after creating new issue +* Fixed: "Replies" should not be shown on forum threads that are locked +* Fixed: SVN errors lead to svn username/password being displayed to end users (security issue) +* Fixed: http links containing hashes don't display correct +* Fixed: Allow ampersands in Enumeration names +* Fixed: Atom link on saved query does not include query_id +* Fixed: Logtime info lost when there's an error updating an issue +* Fixed: TOC does not parse colorization markups +* Fixed: CVS: add support for modules names with spaces +* Fixed: Bad rendering on projects/add +* Fixed: exception when viewing differences on cvs +* Fixed: export issue to pdf will messup when use Chinese language +* Fixed: Redmine::Scm::Adapters::GitAdapter#get_rev ignored GIT_BIN constant +* Fixed: Adding non-ASCII new issue type in the New Issue page have encoding error using IE +* Fixed: Importing from trac : some wiki links are messed +* Fixed: Incorrect weekend definition in Hebrew calendar locale +* Fixed: Atom feeds don't provide author section for repository revisions +* Fixed: In Activity views, changesets titles can be multiline while they should not +* Fixed: Ignore unreadable subversion directories (read disabled using authz) +* Fixed: lib/SVG/Graph/Graph.rb can't externalize stylesheets +* Fixed: Close statement handler in Redmine.pm + + +== 2008-05-04 v0.7.1 + +* Thai translation added (Gampol Thitinilnithi) +* Translations updates +* Escape HTML comment tags +* Prevent "can't convert nil into String" error when :sort_order param is not present +* Fixed: Updating tickets add a time log with zero hours +* Fixed: private subprojects names are revealed on the project overview +* Fixed: Search for target version of "none" fails with postgres 8.3 +* Fixed: Home, Logout, Login links shouldn't be absolute links +* Fixed: 'Latest projects' box on the welcome screen should be hidden if there are no projects +* Fixed: error when using upcase language name in coderay +* Fixed: error on Trac import when :due attribute is nil + + == 2008-04-28 v0.7.0 * Forces Redmine to use rails 2.0.2 gem when vendor/rails is not present diff --git a/groups/doc/INSTALL b/groups/doc/INSTALL index 7a00b9367..13502f8d0 100644 --- a/groups/doc/INSTALL +++ b/groups/doc/INSTALL @@ -1,25 +1,22 @@ == Redmine installation Redmine - project management software -Copyright (C) 2006-2007 Jean-Philippe Lang +Copyright (C) 2006-2008 Jean-Philippe Lang http://www.redmine.org/ == Requirements -* Ruby on Rails 2.0.2 -* A database (see compatibility below) +* Ruby on Rails 2.1 +* A database: + * MySQL (tested with MySQL 5) + * PostgreSQL (tested with PostgreSQL 8.1) + * SQLite (tested with SQLite 3) Optional: * SVN binaries >= 1.3 (needed for repository browsing, must be available in PATH) * RMagick (gantt export to png) -Supported databases: -* MySQL (tested with MySQL 5) -* PostgreSQL (tested with PostgreSQL 8.1) -* SQLite (tested with SQLite 3) - - == Installation 1. Uncompress the program archive @@ -33,24 +30,33 @@ Supported databases: rake db:migrate RAILS_ENV="production" It will create tables and an administrator account. -5. Test the installation by running WEBrick web server: +5. Setting up permissions + The user who runs Redmine must have write permission on the following + subdirectories: files, log, tmp (create the last one if not present). + + Assuming you run Redmine with a user named redmine: + mkdir tmp + sudo chown -R redmine:redmine files log tmp + sudo chmod -R 755 files log tmp + +6. 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: +7. Use default administrator account to log in: login: admin password: admin -7. Go to "Administration" to load the default configuration data (roles, + Go to "Administration" to load the default configuration data (roles, trackers, statuses, workflow) and adjust application settings -== SMTP server Configuration - -In config/environment.rb, you can set parameters for your SMTP server: -config.action_mailer.smtp_settings: SMTP server configuration -config.action_mailer.perform_deliveries: set to false to disable mail delivering +== Email delivery Configuration +Copy config/email.yml.example to config/email.yml and edit this file +to adjust your SMTP settings. Don't forget to restart the application after any change to this file. + +Please do not enter your SMTP settings in environment.rb. diff --git a/groups/doc/README_FOR_APP b/groups/doc/README_FOR_APP new file mode 100644 index 000000000..fb70acaac --- /dev/null +++ b/groups/doc/README_FOR_APP @@ -0,0 +1,5 @@ += Redmine + +Redmine is a flexible project management web application written using Ruby on Rails framework. + +More details can be found at http://www.redmine.org diff --git a/groups/doc/RUNNING_TESTS b/groups/doc/RUNNING_TESTS index 7a5e2b992..6ee977811 100644 --- a/groups/doc/RUNNING_TESTS +++ b/groups/doc/RUNNING_TESTS @@ -24,6 +24,14 @@ Git --- gunzip < test/fixtures/repositories/git_repository.tar.gz | tar -xv -C tmp/test +Darcs (2.0+ required) +--------------------- +gunzip < test/fixtures/repositories/darcs_repository.tar.gz | tar -xv -C tmp/test + +FileSystem +---------- +gunzip < test/fixtures/repositories/filesystem_repository.tar.gz | tar -xv -C tmp/test + Running Tests ============= diff --git a/groups/doc/UPGRADING b/groups/doc/UPGRADING index 2edb2952a..1dd901171 100644 --- a/groups/doc/UPGRADING +++ b/groups/doc/UPGRADING @@ -10,15 +10,13 @@ http://www.redmine.org/ 1. Uncompress the program archive in a new directory 3. Copy your database settings (RAILS_ROOT/config/database.yml) + and SMTP settings (RAILS_ROOT/config/email.yml) into the new config directory -4. Enter your SMTP settings in config/environment.rb - Do not replace this file with the old one - -5. Migrate your database (please make a backup before doing this): +4. Migrate your database (please make a backup before doing this): rake db:migrate RAILS_ENV="production" -6. Copy the RAILS_ROOT/files directory content into your new installation +5. Copy the RAILS_ROOT/files directory content into your new installation This directory contains all the attached files diff --git a/groups/extra/mail_handler/rdm-mailhandler.rb b/groups/extra/mail_handler/rdm-mailhandler.rb new file mode 100644 index 000000000..96e975187 --- /dev/null +++ b/groups/extra/mail_handler/rdm-mailhandler.rb @@ -0,0 +1,125 @@ +#!/usr/bin/ruby + +# rdm-mailhandler +# Reads an email from standard input and forward it to a Redmine server +# Can be used from a remote mail server + +require 'net/http' +require 'net/https' +require 'uri' +require 'getoptlong' + +module Net + class HTTPS < HTTP + def self.post_form(url, params) + request = Post.new(url.path) + request.form_data = params + request.basic_auth url.user, url.password if url.user + http = new(url.host, url.port) + http.use_ssl = (url.scheme == 'https') + http.start {|h| h.request(request) } + end + end +end + +class RedmineMailHandler + VERSION = '0.1' + + attr_accessor :verbose, :issue_attributes, :allow_override, :url, :key + + def initialize + self.issue_attributes = {} + + opts = GetoptLong.new( + [ '--help', '-h', GetoptLong::NO_ARGUMENT ], + [ '--version', '-V', GetoptLong::NO_ARGUMENT ], + [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], + [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ], + [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT], + [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ], + [ '--tracker', '-t', GetoptLong::REQUIRED_ARGUMENT], + [ '--category', GetoptLong::REQUIRED_ARGUMENT], + [ '--priority', GetoptLong::REQUIRED_ARGUMENT], + [ '--allow-override', '-o', GetoptLong::REQUIRED_ARGUMENT] + ) + + opts.each do |opt, arg| + case opt + when '--url' + self.url = arg.dup + when '--key' + self.key = arg.dup + when '--help' + usage + when '--verbose' + self.verbose = true + when '--version' + puts VERSION; exit + when '--project', '--tracker', '--category', '--priority' + self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup + when '--allow-override' + self.allow_override = arg.dup + end + end + + usage if url.nil? + end + + def submit(email) + uri = url.gsub(%r{/*$}, '') + '/mail_handler' + + data = { 'key' => key, 'email' => email, 'allow_override' => allow_override } + issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value } + + debug "Posting to #{uri}..." + response = Net::HTTPS.post_form(URI.parse(uri), data) + debug "Response received: #{response.code}" + response.code == 201 ? 0 : 1 + end + + private + + def usage + puts <<-USAGE +Usage: rdm-mailhandler [options] --url= --key= +Reads an email from standard input and forward it to a Redmine server + +Required: + -u, --url URL of the Redmine server + -k, --key Redmine API key + +General options: + -h, --help show this help + -v, --verbose show extra information + -V, --version show version information and exit + +Issue attributes control options: + -p, --project=PROJECT identifier of the target project + -t, --tracker=TRACKER name of the target tracker + --category=CATEGORY name of the target category + --priority=PRIORITY name of the target priority + -o, --allow-override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + rdm-mailhandler --url http://redmine.domain.foo --key secret + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: + rdm-mailhandler --url https://domain.foo/redmine --key secret \\ + --project foo \\ + --tracker bug \\ + --allow-override tracker,priority +USAGE + exit + end + + def debug(msg) + puts msg if verbose + end +end + +handler = RedmineMailHandler.new +handler.submit(STDIN.read) diff --git a/groups/extra/sample_plugin/app/models/meeting.rb b/groups/extra/sample_plugin/app/models/meeting.rb new file mode 100644 index 000000000..c1bb64a93 --- /dev/null +++ b/groups/extra/sample_plugin/app/models/meeting.rb @@ -0,0 +1,11 @@ +class Meeting < ActiveRecord::Base + belongs_to :project + + acts_as_event :title => Proc.new {|o| "#{o.scheduled_on} Meeting"}, + :datetime => :scheduled_on, + :author => nil, + :url => Proc.new {|o| {:controller => 'meetings', :action => 'show', :id => o.id}} + + acts_as_activity_provider :timestamp => 'scheduled_on', + :find_options => { :include => :project } +end diff --git a/groups/extra/sample_plugin/db/migrate/001_create_meetings.rb b/groups/extra/sample_plugin/db/migrate/001_create_meetings.rb new file mode 100644 index 000000000..fec9c8bd1 --- /dev/null +++ b/groups/extra/sample_plugin/db/migrate/001_create_meetings.rb @@ -0,0 +1,15 @@ +# Sample plugin migration +# Use rake db:migrate_plugins to migrate installed plugins +class CreateMeetings < ActiveRecord::Migration + def self.up + create_table :meetings do |t| + t.column :project_id, :integer, :null => false + t.column :description, :string + t.column :scheduled_on, :datetime + end + end + + def self.down + drop_table :meetings + end +end diff --git a/groups/extra/sample_plugin/db/migrate/001_create_some_models.rb b/groups/extra/sample_plugin/db/migrate/001_create_some_models.rb deleted file mode 100644 index 39d58a649..000000000 --- a/groups/extra/sample_plugin/db/migrate/001_create_some_models.rb +++ /dev/null @@ -1,13 +0,0 @@ -# Sample plugin migration -# Use rake db:migrate_plugins to migrate installed plugins -class CreateSomeModels < ActiveRecord::Migration - def self.up - create_table :example_plugin_model, :force => true do |t| - t.column "example_attribute", :integer - end - end - - def self.down - drop_table :example_plugin_model - end -end diff --git a/groups/extra/sample_plugin/init.rb b/groups/extra/sample_plugin/init.rb index 7389aaa6f..5c543338c 100644 --- a/groups/extra/sample_plugin/init.rb +++ b/groups/extra/sample_plugin/init.rb @@ -18,8 +18,13 @@ Redmine::Plugin.register :sample_plugin do # This permission has to be explicitly given # It will be listed on the permissions screen permission :example_say_goodbye, {:example => [:say_goodbye]} + # This permission can be given to project members only + permission :view_meetings, {:meetings => [:index, :show]}, :require => :member end # A new item is added to the project menu menu :project_menu, :sample_plugin, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample' + + # Meetings are added to the activity view + activity_provider :meetings end diff --git a/groups/extra/sample_plugin/lang/en.yml b/groups/extra/sample_plugin/lang/en.yml index bf62bc344..c4005a764 100644 --- a/groups/extra/sample_plugin/lang/en.yml +++ b/groups/extra/sample_plugin/lang/en.yml @@ -1,4 +1,5 @@ # Sample plugin label_plugin_example: Sample Plugin +label_meeting_plural: Meetings text_say_hello: Plugin say 'Hello' text_say_goodbye: Plugin say 'Good bye' diff --git a/groups/extra/sample_plugin/lang/fr.yml b/groups/extra/sample_plugin/lang/fr.yml index 2c0829c32..135050a5a 100644 --- a/groups/extra/sample_plugin/lang/fr.yml +++ b/groups/extra/sample_plugin/lang/fr.yml @@ -1,4 +1,5 @@ # Sample plugin label_plugin_example: Plugin exemple +label_meeting_plural: Meetings text_say_hello: Plugin dit 'Bonjour' text_say_goodbye: Plugin dit 'Au revoir' diff --git a/groups/extra/svn/Redmine.pm b/groups/extra/svn/Redmine.pm index 6f3ba4385..09a85fb09 100644 --- a/groups/extra/svn/Redmine.pm +++ b/groups/extra/svn/Redmine.pm @@ -36,10 +36,9 @@ Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used): =head1 CONFIGURATION - ## if the module isn't in your perl path - PerlRequire /usr/local/apache/Redmine.pm - ## else - # PerlModule Apache::Authn::Redmine + ## This module has to be in your perl path + ## eg: /usr/lib/perl5/Apache/Authn/Redmine.pm + PerlLoadModule Apache::Authn::Redmine DAV svn SVNParentPath "/var/svn" @@ -52,12 +51,17 @@ Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used): PerlAuthenHandler Apache::Authn::Redmine::authen_handler ## for mysql - PerlSetVar dsn DBI:mysql:database=databasename;host=my.db.server + RedmineDSN "DBI:mysql:database=databasename;host=my.db.server" ## for postgres - # PerlSetVar dsn DBI:Pg:dbname=databasename;host=my.db.server - - PerlSetVar db_user redmine - PerlSetVar db_pass password + # RedmineDSN "DBI:Pg:dbname=databasename;host=my.db.server" + + RedmineDbUser "redmine" + RedmineDbPass "password" + ## Optional where clause (fulltext search would be slow and + ## database dependant). + # RedmineDbWhereClause "and members.role_id IN (1,2)" + ## Optional credentials cache size + # RedmineCacheCredsMax 50 To be able to browse repository inside redmine, you must add something @@ -92,6 +96,7 @@ And you need to upgrade at least reposman.rb (after r860). =cut use strict; +use warnings FATAL => 'all', NONFATAL => 'redefine'; use DBI; use Digest::SHA1; @@ -103,9 +108,87 @@ use Apache2::Access; use Apache2::ServerRec qw(); use Apache2::RequestRec qw(); use Apache2::RequestUtil qw(); -use Apache2::Const qw(:common); +use Apache2::Const qw(:common :override :cmd_how); +use APR::Pool (); +use APR::Table (); + # use Apache2::Directive qw(); +my @directives = ( + { + name => 'RedmineDSN', + req_override => OR_AUTHCFG, + args_how => TAKE1, + errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"', + }, + { + name => 'RedmineDbUser', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'RedmineDbPass', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'RedmineDbWhereClause', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'RedmineCacheCredsMax', + req_override => OR_AUTHCFG, + args_how => TAKE1, + errmsg => 'RedmineCacheCredsMax must be decimal number', + }, +); + +sub RedmineDSN { + my ($self, $parms, $arg) = @_; + $self->{RedmineDSN} = $arg; + my $query = "SELECT + hashed_password, auth_source_id + FROM members, projects, users + WHERE + projects.id=members.project_id + AND users.id=members.user_id + AND users.status=1 + AND login=? + AND identifier=? "; + $self->{RedmineQuery} = trim($query); +} +sub RedmineDbUser { set_val('RedmineDbUser', @_); } +sub RedmineDbPass { set_val('RedmineDbPass', @_); } +sub RedmineDbWhereClause { + my ($self, $parms, $arg) = @_; + $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." "); +} + +sub RedmineCacheCredsMax { + my ($self, $parms, $arg) = @_; + if ($arg) { + $self->{RedmineCachePool} = APR::Pool->new; + $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg); + $self->{RedmineCacheCredsCount} = 0; + $self->{RedmineCacheCredsMax} = $arg; + } +} + +sub trim { + my $string = shift; + $string =~ s/\s{2,}/ /g; + return $string; +} + +sub set_val { + my ($key, $self, $parms, $arg) = @_; + $self->{$key} = $arg; +} + +Apache2::Module::add(__PACKAGE__, \@directives); + + my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/; sub access_handler { @@ -117,7 +200,7 @@ sub access_handler { } my $method = $r->method; - return OK unless 1 == $read_only_methods{$method}; + return OK if defined $read_only_methods{$method}; my $project_id = get_project_identifier($r); @@ -152,6 +235,7 @@ sub is_public_project { $sth->execute($project_id); my $ret = $sth->fetchrow_array ? 1 : 0; + $sth->finish(); $dbh->disconnect(); $ret; @@ -182,9 +266,14 @@ sub is_member { my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass); - my $sth = $dbh->prepare( - "SELECT hashed_password, auth_source_id FROM members, projects, users WHERE projects.id=members.project_id AND users.id=members.user_id AND users.status=1 AND login=? AND identifier=?;" - ); + my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); + my $usrprojpass; + if ($cfg->{RedmineCacheCredsMax}) { + $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id); + return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest)); + } + my $query = $cfg->{RedmineQuery}; + my $sth = $dbh->prepare($query); $sth->execute($redmine_user, $project_id); my $ret; @@ -216,6 +305,20 @@ sub is_member { $sth->finish(); $dbh->disconnect(); + if ($cfg->{RedmineCacheCredsMax} and $ret) { + if (defined $usrprojpass) { + $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest); + } else { + if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) { + $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest); + $cfg->{RedmineCacheCredsCount}++; + } else { + $cfg->{RedmineCacheCreds}->clear(); + $cfg->{RedmineCacheCredsCount} = 0; + } + } + } + $ret; } @@ -229,9 +332,9 @@ sub get_project_identifier { sub connect_database { my $r = shift; - - my ($dsn, $db_user, $db_pass) = map { $r->dir_config($_) } qw/dsn db_user db_pass/; - return DBI->connect($dsn, $db_user, $db_pass); + + my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); + return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass}); } 1; diff --git a/groups/lang/bg.yml b/groups/lang/bg.yml index b341d989f..1f174e29f 100644 --- a/groups/lang/bg.yml +++ b/groups/lang/bg.yml @@ -48,6 +48,7 @@ general_text_no: 'не' general_text_yes: 'да' general_lang_name: 'Bulgarian' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: UTF-8 general_pdf_encoding: UTF-8 general_day_names: Понеделник,Вторник,СрÑда,Четвъртък,Петък,Събота,ÐÐµÐ´ÐµÐ»Ñ @@ -618,3 +619,20 @@ setting_default_projects_public: Ðовите проекти Ñа публичн error_scm_annotate: "Обектът не ÑъщеÑтвува или не може да бъде анотиран." label_planning: Планиране text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/cs.yml b/groups/lang/cs.yml index 250c602c2..609e95478 100644 --- a/groups/lang/cs.yml +++ b/groups/lang/cs.yml @@ -51,6 +51,7 @@ general_text_no: 'ne' general_text_yes: 'ano' general_lang_name: 'ÄŒeÅ¡tina' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: UTF-8 general_pdf_encoding: UTF-8 general_day_names: PondÄ›lí,Úterý,StÅ™eda,ÄŒtvrtek,Pátek,Sobota,NedÄ›le @@ -623,3 +624,20 @@ enumeration_activities: Aktivity (sledování Äasu) error_scm_annotate: "Položka neexistuje nebo nemůže být komentována." label_planning: Plánování text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/da.yml b/groups/lang/da.yml index ff2ed982d..a76e7ea5c 100644 --- a/groups/lang/da.yml +++ b/groups/lang/da.yml @@ -48,6 +48,7 @@ general_text_no: 'nej' general_text_yes: 'ja' general_lang_name: 'Danish (Dansk)' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_day_names: Mandag,Tirsdag,Onsdag,Torsdag,Fredag,Lørdag,Søndag @@ -620,3 +621,20 @@ setting_default_projects_public: Nye projekter er offentlige som default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planlægning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/de.yml b/groups/lang/de.yml index 77184cf88..e309dfb57 100644 --- a/groups/lang/de.yml +++ b/groups/lang/de.yml @@ -48,6 +48,7 @@ general_text_no: 'nein' general_text_yes: 'ja' general_lang_name: 'Deutsch' general_csv_separator: ';' +general_csv_decimal_separator: ',' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_day_names: Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag,Sonntag @@ -619,3 +620,20 @@ enumeration_issue_priorities: Ticket-Prioritäten enumeration_doc_categories: Dokumentenkategorien enumeration_activities: Aktivitäten (Zeiterfassung) text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/en.yml b/groups/lang/en.yml index 320501c2b..7763b44b5 100644 --- a/groups/lang/en.yml +++ b/groups/lang/en.yml @@ -48,6 +48,7 @@ general_text_no: 'no' general_text_yes: 'yes' general_lang_name: 'English' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_day_names: Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday @@ -91,6 +92,8 @@ mail_body_account_information_external: You can use your "%s" account to log in. mail_body_account_information: Your account information mail_subject_account_activation_request: %s account activation request mail_body_account_activation_request: 'A new user (%s) has registered. His account is pending your approval:' +mail_subject_reminder: "%d issue(s) due in the next days" +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" gui_validation_error: 1 error gui_validation_error_plural: %d errors @@ -180,6 +183,7 @@ field_searchable: Searchable field_default_value: Default value field_comments_sorting: Display comments field_group: Group +field_parent_title: Parent page setting_app_title: Application title setting_app_subtitle: Application subtitle @@ -206,12 +210,16 @@ setting_time_format: Time format setting_cross_project_issue_relations: Allow cross-project issue relations setting_issue_list_default_columns: Default columns displayed on the issue list setting_repositories_encodings: Repositories encodings +setting_commit_logs_encoding: Commit messages encoding setting_emails_footer: Emails footer setting_protocol: Protocol setting_per_page_options: Objects per page options setting_user_format: Users display format setting_activity_days_default: Days displayed on project activity setting_display_subprojects_issues: Display subprojects issues on main projects by default +setting_enabled_scm: Enabled SCM +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key project_module_issue_tracking: Issue tracking project_module_time_tracking: Time tracking @@ -292,6 +300,7 @@ label_auth_source: Authentication mode label_auth_source_new: New authentication mode label_auth_source_plural: Authentication modes label_subproject_plural: Subprojects +label_and_its_subprojects: %s and its subprojects label_min_max_length: Min - Max length label_list: List label_date: Date @@ -446,6 +455,7 @@ label_relation_new: New relation label_relation_delete: Delete relation label_relates_to: related to label_duplicates: duplicates +label_duplicated_by: duplicated by label_blocks: blocks label_blocked_by: blocked by label_precedes: precedes @@ -514,6 +524,9 @@ label_planning: Planning label_group: Group label_group_plural: Groups label_group_new: New group +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +label_issue_watchers: Watchers button_login: Login button_submit: Submit @@ -552,6 +565,7 @@ button_copy: Copy button_annotate: Annotate button_update: Update button_configure: Configure +button_quote: Quote status_active: active status_registered: registered @@ -570,7 +584,7 @@ text_journal_deleted: deleted text_tip_task_begin_day: task beginning this day text_tip_task_end_day: task ending this day text_tip_task_begin_end_day: task beginning and ending this day -text_project_identifier_info: 'Lower case letters (a-z), numbers and dashes allowed.
    Once saved, the identifier can not be changed.' +text_project_identifier_info: 'Only lower case letters (a-z), numbers and dashes are allowed.
    Once saved, the identifier can not be changed.' text_caracters_maximum: %d characters maximum. text_caracters_minimum: Must be at least %d characters long. text_length_between: Length between %d and %d characters. @@ -597,6 +611,10 @@ text_destroy_time_entries_question: %.02f hours were reported on the issues you text_destroy_time_entries: Delete reported hours text_assign_time_entries_to_project: Assign reported hours to the project text_reassign_time_entries: 'Reassign reported hours to this issue:' +text_user_wrote: '%s wrote:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." default_role_manager: Manager default_role_developper: Developer diff --git a/groups/lang/es.yml b/groups/lang/es.yml index c6eef021a..fc9540a02 100644 --- a/groups/lang/es.yml +++ b/groups/lang/es.yml @@ -48,6 +48,7 @@ general_text_no: 'no' general_text_yes: 'sí' general_lang_name: 'Español' general_csv_separator: ';' +general_csv_decimal_separator: ',' general_csv_encoding: ISO-8859-15 general_pdf_encoding: ISO-8859-15 general_day_names: Lunes,Martes,Miércoles,Jueves,Viernes,Sábado,Domingo @@ -621,3 +622,20 @@ setting_default_projects_public: Los proyectos nuevos son públicos por defecto error_scm_annotate: "No existe la entrada o no ha podido ser anotada" label_planning: Planificación text_subprojects_destroy_warning: 'Sus subprojectos: %s también se eliminarán' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/fi.yml b/groups/lang/fi.yml index 68b6c20d7..6eb16bfac 100644 --- a/groups/lang/fi.yml +++ b/groups/lang/fi.yml @@ -16,7 +16,7 @@ actionview_datehelper_time_in_words_minute_less_than: vähemmän kuin minuuttia actionview_datehelper_time_in_words_minute_plural: %d minuuttia actionview_datehelper_time_in_words_minute_single: 1 minuutti actionview_datehelper_time_in_words_second_less_than: vähemmän kuin sekuntin -actionview_datehelper_time_in_words_second_less_than_plural: vähemmän kuin %d sekunttia +actionview_datehelper_time_in_words_second_less_than_plural: vähemmän kuin %d sekuntia actionview_instancetag_blank_option: Valitse, ole hyvä activerecord_error_inclusion: ei ole listalla @@ -34,7 +34,7 @@ activerecord_error_not_a_number: ei ole numero activerecord_error_not_a_date: ei ole oikea päivä activerecord_error_greater_than_start_date: tulee olla aloituspäivän jälkeinen activerecord_error_not_same_project: ei kuulu samaan projektiin -activerecord_error_circular_dependency: Tämä suhde loisi kiertävän suhteen. +activerecord_error_circular_dependency: Tämä suhde loisi kehän. general_fmt_age: %d v. general_fmt_age_plural: %d vuotta @@ -48,19 +48,20 @@ general_text_no: 'ei' general_text_yes: 'kyllä' general_lang_name: 'Finnish (Suomi)' general_csv_separator: ',' -general_csv_encoding: ISO-8859-1 -general_pdf_encoding: ISO-8859-1 +general_csv_decimal_separator: '.' +general_csv_encoding: ISO-8859-15 +general_pdf_encoding: ISO-8859-15 general_day_names: Maanantai,Tiistai,Keskiviikko,Torstai,Perjantai,Lauantai,Sunnuntai general_first_day_of_week: '1' notice_account_updated: Tilin päivitys onnistui. -notice_account_invalid_creditentials: Väärä käyttäjä tai salasana +notice_account_invalid_creditentials: Virheellinen käyttäjätunnus tai salasana notice_account_password_updated: Salasanan päivitys onnistui. notice_account_wrong_password: Väärä salasana notice_account_register_done: Tilin luonti onnistui. Aktivoidaksesi tilin seuraa linkkiä joka välitettiin sähköpostiisi. notice_account_unknown_email: Tuntematon käyttäjä. -notice_can_t_change_password: Tämä tili käyttää ulkoista autentikointi järjestelmää. Mahdotonta muuttaa salasanaa. -notice_account_lost_email_sent: Sinulle on lähetetty sähköposti jossa on ohje miten vaihdat salasanasi. +notice_can_t_change_password: Tämä tili käyttää ulkoista tunnistautumisjärjestelmää. Salasanaa ei voi muuttaa. +notice_account_lost_email_sent: Sinulle on lähetetty sähköposti jossa on ohje kuinka vaihdat salasanasi. notice_account_activated: Tilisi on nyt aktivoitu, voit kirjautua sisälle. notice_successful_create: Luonti onnistui. notice_successful_update: Päivitys onnistui. @@ -71,20 +72,20 @@ notice_locking_conflict: Toinen käyttäjä on päivittänyt tiedot. notice_not_authorized: Sinulla ei ole oikeutta näyttää tätä sivua. notice_email_sent: Sähköposti on lähetty osoitteeseen %s notice_email_error: Sähköpostilähetyksessä tapahtui virhe (%s) -notice_feeds_access_key_reseted: RSS pääsy avaimesi on nollaantunut. +notice_feeds_access_key_reseted: RSS salasana on nollaantunut. notice_failed_to_save_issues: "%d Tapahtum(an/ien) tallennus epäonnistui %d valitut: %s." notice_no_issue_selected: "Tapahtumia ei ole valittu! Valitse tapahtumat joita haluat muokata." notice_account_pending: "Tilisi on luotu ja odottaa ylläpitäjän hyväksyntää." -notice_default_data_loaded: Vakio asetusten palautus onnistui. +notice_default_data_loaded: Vakioasetusten palautus onnistui. -error_can_t_load_default_data: "Vakio asetuksia ei voitu ladata: %s" -error_scm_not_found: "Syötettä ja/tai versiota ei löydy säiliöstä." -error_scm_command_failed: "Säiliöön pääsyssä tapahtui virhe: %s" +error_can_t_load_default_data: "Vakioasetuksia ei voitu ladata: %s" +error_scm_not_found: "Syötettä ja/tai versiota ei löydy tietovarastosta." +error_scm_command_failed: "Tietovarastoon pääsyssä tapahtui virhe: %s" mail_subject_lost_password: Sinun %s salasanasi -mail_body_lost_password: 'Vaihtaaksesi salasanasi, paina seuraavaa linkkiä:' +mail_body_lost_password: 'Vaihtaaksesi salasanasi, napsauta seuraavaa linkkiä:' mail_subject_register: %s tilin aktivointi -mail_body_register: 'Aktivoidaksesi tilisi, paina seuraavaa linkkiä:' +mail_body_register: 'Aktivoidaksesi tilisi, napsauta seuraavaa linkkiä:' mail_body_account_information_external: Voit nyt käyttää "%s" tiliäsi kirjautuaksesi järjestelmään. mail_body_account_information: Sinun tilin tiedot mail_subject_account_activation_request: %s tilin aktivointi pyyntö @@ -97,8 +98,8 @@ field_name: Nimi field_description: Kuvaus field_summary: Yhteenveto field_is_required: Vaaditaan -field_firstname: Etu nimi -field_lastname: Suku nimi +field_firstname: Etunimi +field_lastname: Sukunimi field_mail: Sähköposti field_filename: Tiedosto field_filesize: Koko @@ -109,9 +110,9 @@ field_updated_on: Päivitetty field_field_format: Muoto field_is_for_all: Kaikille projekteille field_possible_values: Mahdolliset arvot -field_regexp: Säännönmukainen ilmentymä (reg exp) -field_min_length: Minimi pituus -field_max_length: Maksimi pituus +field_regexp: Säännöllinen lauseke (reg exp) +field_min_length: Minimipituus +field_max_length: Maksimipituus field_value: Arvo field_category: Luokka field_title: Otsikko @@ -120,18 +121,18 @@ field_issue: Tapahtuma field_status: Tila field_notes: Muistiinpanot field_is_closed: Tapahtuma suljettu -field_is_default: Vakio arvo +field_is_default: Vakioarvo field_tracker: Tapahtuma field_subject: Aihe field_due_date: Määräaika field_assigned_to: Nimetty field_priority: Prioriteetti -field_fixed_version: Kohde versio +field_fixed_version: Kohdeversio field_user: Käyttäjä field_role: Rooli field_homepage: Kotisivu field_is_public: Julkinen -field_parent: Alaprojekti +field_parent: Aliprojekti field_is_in_chlog: Tapahtumat näytetään muutoslokissa field_is_in_roadmap: Tapahtumat näytetään roadmap näkymässä field_login: Kirjautuminen @@ -145,23 +146,23 @@ field_new_password: Uusi salasana field_password_confirmation: Vahvistus field_version: Versio field_type: Tyyppi -field_host: Isäntä +field_host: Verkko-osoite field_port: Portti field_account: Tili field_base_dn: Base DN -field_attr_login: Kirjautumis määre -field_attr_firstname: Etuminen määre -field_attr_lastname: Sukunimen määre -field_attr_mail: Sähköpostin määre +field_attr_login: Kirjautumismääre +field_attr_firstname: Etuminenmääre +field_attr_lastname: Sukunimenmääre +field_attr_mail: Sähköpostinmääre field_onthefly: Automaattinen käyttäjien luonti field_start_date: Alku field_done_ratio: %% Tehty -field_auth_source: Autentikointi muoto +field_auth_source: Varmennusmuoto field_hide_mail: Piiloita sähköpostiosoitteeni field_comments: Kommentti field_url: URL -field_start_page: Aloitus sivu -field_subproject: Alaprojekti +field_start_page: Aloitussivu +field_subproject: Aliprojekti field_hours: Tuntia field_activity: Historia field_spent_on: Päivä @@ -175,32 +176,32 @@ field_estimated_hours: Arvioitu aika field_column_names: Saraketta field_time_zone: Aikavyöhyke field_searchable: Haettava -field_default_value: Vakio arvo +field_default_value: Vakioarvo setting_app_title: Ohjelman otsikko setting_app_subtitle: Ohjelman alaotsikko -setting_welcome_text: Tervetulo teksti -setting_default_language: Vakio kieli -setting_login_required: Pakollinen autentikointi -setting_self_registration: Tee-Se-Itse rekisteröinti -setting_attachment_max_size: Liitteen maksimi koko -setting_issues_export_limit: Tapahtumien vienti rajoite +setting_welcome_text: Tervehdysteksti +setting_default_language: Vakiokieli +setting_login_required: Pakollinen kirjautuminen +setting_self_registration: Itserekisteröinti +setting_attachment_max_size: Liitteen maksimikoko +setting_issues_export_limit: Tapahtumien vientirajoite setting_mail_from: Lähettäjän sähköpostiosoite -setting_bcc_recipients: Blind carbon copy vastaanottajat (bcc) -setting_host_name: Isännän nimi +setting_bcc_recipients: Vastaanottajat piilokopiona (bcc) +setting_host_name: Verkko-osoite setting_text_formatting: Tekstin muotoilu setting_wiki_compression: Wiki historian pakkaus setting_feeds_limit: Syötteen sisällön raja -setting_autofetch_changesets: Automaatisen haun souritukset -setting_sys_api_enabled: Salli WS säiliön hallintaan +setting_autofetch_changesets: Automaattisten muutosjoukkojen haku +setting_sys_api_enabled: Salli WS tietovaraston hallintaan setting_commit_ref_keywords: Viittaavat hakusanat setting_commit_fix_keywords: Korjaavat hakusanat setting_autologin: Automaatinen kirjautuminen setting_date_format: Päivän muoto setting_time_format: Ajan muoto setting_cross_project_issue_relations: Salli projektien väliset tapahtuminen suhteet -setting_issue_list_default_columns: Vakio sarakkeiden näyttö tapahtuma listauksessa -setting_repositories_encodings: Säiliön koodaus +setting_issue_list_default_columns: Vakiosarakkeiden näyttö tapahtumalistauksessa +setting_repositories_encodings: Tietovaraston koodaus setting_emails_footer: Sähköpostin alatunniste setting_protocol: Protokolla setting_per_page_options: Sivun objektien määrän asetukset @@ -235,8 +236,8 @@ label_workflow: Työnkulku label_issue_status: Tapahtuman tila label_issue_status_plural: Tapahtumien tilat label_issue_status_new: Uusi tila -label_issue_category: Tapahtuma luokka -label_issue_category_plural: Tapahtuma luokat +label_issue_category: Tapahtumaluokka +label_issue_category_plural: Tapahtumaluokat label_issue_category_new: Uusi luokka label_custom_field: Räätälöity kenttä label_custom_field_plural: Räätälöidyt kentät @@ -249,9 +250,9 @@ label_please_login: Kirjaudu ole hyvä label_register: Rekisteröidy label_password_lost: Hukattu salasana label_home: Koti -label_my_page: Minun sivu -label_my_account: Minun tili -label_my_projects: Minun projektit +label_my_page: Omasivu +label_my_account: Oma tili +label_my_projects: Omat projektit label_administration: Ylläpito label_login: Kirjaudu sisään label_logout: Kirjaudu ulos @@ -266,11 +267,11 @@ label_activity: Historia label_new: Uusi label_logged_as: Kirjauduttu nimellä label_environment: Ympäristö -label_authentication: Autentikointi -label_auth_source: Autentikointi tapa -label_auth_source_new: Uusi autentikointi tapa -label_auth_source_plural: Autentikointi tavat -label_subproject_plural: Alaprojektit +label_authentication: Varmennus +label_auth_source: Varmennustapa +label_auth_source_new: Uusi varmennustapa +label_auth_source_plural: Varmennustavat +label_subproject_plural: Aliprojektit label_min_max_length: Min - Max pituudet label_list: Lista label_date: Päivä @@ -307,8 +308,8 @@ label_confirmation: Vahvistus label_export_to: Vie label_read: Lukee... label_public_projects: Julkiset projektit -label_open_issues: avoin -label_open_issues_plural: avointa +label_open_issues: avoin, yhteensä +label_open_issues_plural: avointa, yhteensä label_closed_issues: suljettu label_closed_issues_plural: suljettua label_total: Yhteensä @@ -341,8 +342,8 @@ label_query_plural: Räätälöidyt haut label_query_new: Uusi haku label_filter_add: Lisää suodatin label_filter_plural: Suodattimet -label_equals: yhtä kuin -label_not_equals: epäsuuri kuin +label_equals: sama kuin +label_not_equals: eri kuin label_in_less_than: pienempi kuin label_in_more_than: suurempi kuin label_today: tänään @@ -353,8 +354,8 @@ label_ago: päiviä sitten label_contains: sisältää label_not_contains: ei sisällä label_day_plural: päivää -label_repository: Säiliö -label_repository_plural: Säiliöt +label_repository: Tietovarasto +label_repository_plural: Tietovarastot label_browse: Selaus label_modification: %d muutos label_modification_plural: %d muutettu @@ -366,7 +367,7 @@ label_deleted: poistettu label_latest_revision: Viimeisin versio label_latest_revision_plural: Viimeisimmät versiot label_view_revisions: Näytä versiot -label_max_size: Maksimi koko +label_max_size: Suurin koko label_sort_highest: Siirrä ylimmäiseksi label_sort_higher: Siirrä ylös label_sort_lower: Siirrä alas @@ -411,15 +412,15 @@ label_loading: Lataa... label_relation_new: Uusi suhde label_relation_delete: Poista suhde label_relates_to: liittyy -label_duplicates: kaksoiskappale +label_duplicates: kopio label_blocks: estää label_blocked_by: estetty label_precedes: edeltää label_follows: seuraa -label_end_to_start: loppu alkuun -label_end_to_end: loppu loppuun -label_start_to_start: alku alkuun -label_start_to_end: alku loppuun +label_end_to_start: lopusta alkuun +label_end_to_end: lopusta loppuun +label_start_to_start: alusta alkuun +label_start_to_end: alusta loppuun label_stay_logged_in: Pysy kirjautuneena label_disabled: poistettu käytöstä label_show_completed_versions: Näytä valmiit versiot @@ -439,14 +440,14 @@ label_week: Viikko label_language_based: Pohjautuen käyttäjän kieleen label_sort_by: Lajittele %s label_send_test_email: Lähetä testi sähköposti -label_feeds_access_key_created_on: RSS pääsy avain luotiin %s sitten +label_feeds_access_key_created_on: RSS salasana luotiin %s sitten label_module_plural: Moduulit label_added_time_by: Lisännyt %s %s sitten label_updated_time: Päivitetty %s sitten label_jump_to_a_project: Siirry projektiin... label_file_plural: Tiedostot label_changeset_plural: Muutosryhmät -label_default_columns: Vakio sarakkeet +label_default_columns: Vakiosarakkeet label_no_change_option: (Ei muutosta) label_bulk_edit_selected_issues: Perusmuotoile valitut tapahtumat label_theme: Teema @@ -457,8 +458,8 @@ label_user_mail_option_selected: "Kaikista tapahtumista vain valitsemistani proj label_user_mail_option_none: "Vain tapahtumista joita valvon tai olen mukana" label_user_mail_no_self_notified: "En halua muistutusta muutoksista joita itse teen" label_registration_activation_by_email: tilin aktivointi sähköpostitse -label_registration_manual_activation: manuaalinen tilin aktivointi -label_registration_automatic_activation: automaattinen tilin aktivointi +label_registration_manual_activation: tilin aktivointi käsin +label_registration_automatic_activation: tilin aktivointi automaattisesti label_display_per_page: 'Per sivu: %s' label_age: Ikä label_change_properties: Vaihda asetuksia @@ -507,7 +508,7 @@ status_locked: lukittu text_select_mail_notifications: Valitse tapahtumat joista tulisi lähettää sähköpostimuistutus. text_regexp_info: esim. ^[A-Z0-9]+$ -text_min_max_length_info: 0 tarkoitta, ei rajoitusta +text_min_max_length_info: 0 tarkoittaa, ei rajoitusta text_project_destroy_confirmation: Oletko varma että haluat poistaa tämän projektin ja kaikki siihen kuuluvat tiedot? text_workflow_edit: Valitse rooli ja tapahtuma muokataksesi työnkulkua text_are_you_sure: Oletko varma? @@ -521,7 +522,7 @@ text_project_identifier_info: 'Pienet kirjaimet (a-z), numerot ja viivat ovat sa text_caracters_maximum: %d merkkiä enintään. text_caracters_minimum: Täytyy olla vähintään %d merkkiä pitkä. text_length_between: Pituus välillä %d ja %d merkkiä. -text_tracker_no_workflow: Ei työnkulkua määritelty tälle tapahtumalle +text_tracker_no_workflow: Työnkulkua ei määritelty tälle tapahtumalle text_unallowed_characters: Kiellettyjä merkkejä text_comma_separated: Useat arvot sallittu (pilkku eroteltuna). text_issues_ref_in_commit_messages: Liitän ja korjaan ongelmia syötetyssä viestissä @@ -531,8 +532,8 @@ text_wiki_destroy_confirmation: Oletko varma että haluat poistaa tämän wiki:n text_issue_category_destroy_question: Jotkut tapahtumat (%d) ovat nimetty tälle luokalle. Mitä haluat tehdä? text_issue_category_destroy_assignments: Poista luokan tehtävät text_issue_category_reassign_to: Vaihda tapahtuma tähän luokkaan -text_user_mail_option: "Valitesemattomille projekteille, saat vain muistutuksen asioista joita seuraat tai olet mukana (esim. tapahtumat joissa olet tekijä tai nimettynä)." -text_no_configuration_data: "Rooleja, tikettejä, tapahtumien tiloja ja työnkulkua ei vielä olla määritelty.\nOn erittäin suotavaa ladata vakioasetukset. Voit muuttaa sitä latauksen jälkeen." +text_user_mail_option: "Valitsemattomille projekteille, saat vain muistutuksen asioista joita seuraat tai olet mukana (esim. tapahtumat joissa olet tekijä tai nimettynä)." +text_no_configuration_data: "Rooleja, tapahtumien tiloja ja työnkulkua ei vielä olla määritelty.\nOn erittäin suotavaa ladata vakioasetukset. Voit muuttaa sitä latauksen jälkeen." text_load_default_configuration: Lataa vakioasetukset default_role_manager: Päälikkö @@ -557,7 +558,7 @@ default_priority_immediate: Valitön default_activity_design: Suunnittelu default_activity_development: Kehitys -enumeration_issue_priorities: Tapahtuman prioriteetit +enumeration_issue_priorities: Tapahtuman tärkeysjärjestys enumeration_doc_categories: Dokumentin luokat enumeration_activities: Historia (ajan seuranta) label_associated_revisions: Liittyvät versiot @@ -578,15 +579,15 @@ project_module_issue_tracking: Tapahtuman seuranta project_module_wiki: Wiki project_module_files: Tiedostot project_module_documents: Dokumentit -project_module_repository: Säiliö +project_module_repository: Tietovarasto project_module_news: Uutiset project_module_time_tracking: Ajan seuranta -text_file_repository_writable: Kirjoitettava tiedosto säiliö +text_file_repository_writable: Kirjoitettava tiedostovarasto text_default_administrator_account_changed: Vakio hallinoijan tunnus muutettu text_rmagick_available: RMagick saatavilla (valinnainen) button_configure: Asetukset label_plugins: Lisäosat -label_ldap_authentication: LDAP autentikointi +label_ldap_authentication: LDAP tunnistautuminen label_downloads_abbr: D/L label_add_another_file: Lisää uusi tiedosto label_this_month: tässä kuussa @@ -609,7 +610,7 @@ label_date_to: '' setting_activity_days_default: Päivien esittäminen projektien historiassa label_date_from: '' label_in: '' -setting_display_subprojects_issues: Näytä alaprojektien tapahtumat pääprojektissa oletusarvoisesti +setting_display_subprojects_issues: Näytä aliprojektien tapahtumat pääprojektissa oletusarvoisesti field_comments_sorting: Näytä kommentit label_reverse_chronological_order: Käänteisessä aikajärjestyksessä label_preferences: Asetukset @@ -617,4 +618,21 @@ setting_default_projects_public: Uudet projektit ovat oletuksena julkisia label_overall_activity: Kokonaishistoria error_scm_annotate: "Merkintää ei ole tai siihen ei voi lisätä selityksiä." label_planning: Suunnittelu -text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +text_subprojects_destroy_warning: 'Tämän aliprojekti(t): %s tullaan myös poistamaan.' +label_and_its_subprojects: %s ja aliprojektit +mail_body_reminder: "%d sinulle nimettyä tapahtuma(a) erääntyy %d päivä sisään:" +mail_subject_reminder: "%d tapahtuma(a) erääntyy lähipäivinä" +text_user_wrote: '%s kirjoitti:' +label_duplicated_by: kopioinut +setting_enabled_scm: Versionhallinta käytettävissä +text_enumeration_category_reassign_to: 'Siirrä täksi arvoksi:' +text_enumeration_destroy_question: '%d kohdetta on sijoitettu tälle arvolle.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/fr.yml b/groups/lang/fr.yml index cbdda4f3d..81e44949f 100644 --- a/groups/lang/fr.yml +++ b/groups/lang/fr.yml @@ -48,6 +48,7 @@ general_text_no: 'non' general_text_yes: 'oui' general_lang_name: 'Français' general_csv_separator: ';' +general_csv_decimal_separator: ',' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_day_names: Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche @@ -91,6 +92,8 @@ mail_body_account_information_external: Vous pouvez utiliser votre compte "%s" p mail_body_account_information: Paramètres de connexion de votre compte mail_subject_account_activation_request: "Demande d'activation d'un compte %s" mail_body_account_activation_request: "Un nouvel utilisateur (%s) s'est inscrit. Son compte nécessite votre approbation:" +mail_subject_reminder: "%d demande(s) arrivent à échéance" +mail_body_reminder: "%d demande(s) qui vous sont assignées arrivent à échéance dans les %d prochains jours:" gui_validation_error: 1 erreur gui_validation_error_plural: %d erreurs @@ -180,6 +183,7 @@ field_time_zone: Fuseau horaire field_searchable: Utilisé pour les recherches field_default_value: Valeur par défaut field_comments_sorting: Afficher les commentaires +field_parent_title: Page parent setting_app_title: Titre de l'application setting_app_subtitle: Sous-titre de l'application @@ -206,12 +210,16 @@ setting_time_format: Format d'heure setting_cross_project_issue_relations: Autoriser les relations entre demandes de différents projets setting_issue_list_default_columns: Colonnes affichées par défaut sur la liste des demandes setting_repositories_encodings: Encodages des dépôts +setting_commit_logs_encoding: Encodage des messages de commit setting_emails_footer: Pied-de-page des emails setting_protocol: Protocole setting_per_page_options: Options d'objets affichés par page setting_user_format: Format d'affichage des utilisateurs setting_activity_days_default: Nombre de jours affichés sur l'activité des projets setting_display_subprojects_issues: Afficher par défaut les demandes des sous-projets sur les projets principaux +setting_enabled_scm: SCM activés +setting_mail_handler_api_enabled: "Activer le WS pour la réception d'emails" +setting_mail_handler_api_key: Clé de protection de l'API project_module_issue_tracking: Suivi des demandes project_module_time_tracking: Suivi du temps passé @@ -291,6 +299,7 @@ label_auth_source: Mode d'authentification label_auth_source_new: Nouveau mode d'authentification label_auth_source_plural: Modes d'authentification label_subproject_plural: Sous-projets +label_and_its_subprojects: %s et ses sous-projets label_min_max_length: Longueurs mini - maxi label_list: Liste label_date: Date @@ -444,7 +453,8 @@ label_loading: Chargement... label_relation_new: Nouvelle relation label_relation_delete: Supprimer la relation label_relates_to: lié à -label_duplicates: doublon de +label_duplicates: duplique +label_duplicated_by: dupliqué par label_blocks: bloque label_blocked_by: bloqué par label_precedes: précède @@ -510,6 +520,9 @@ label_preferences: Préférences label_chronological_order: Dans l'ordre chronologique label_reverse_chronological_order: Dans l'ordre chronologique inverse label_planning: Planning +label_incoming_emails: Emails entrants +label_generate_key: Générer une clé +label_issue_watchers: Utilisateurs surveillant cette demande button_login: Connexion button_submit: Soumettre @@ -548,6 +561,7 @@ button_copy: Copier button_annotate: Annoter button_update: Mettre à jour button_configure: Configurer +button_quote: Citer status_active: actif status_registered: enregistré @@ -566,7 +580,7 @@ text_journal_deleted: supprimé text_tip_task_begin_day: tâche commençant ce jour text_tip_task_end_day: tâche finissant ce jour text_tip_task_begin_end_day: tâche commençant et finissant ce jour -text_project_identifier_info: 'Lettres minuscules (a-z), chiffres et tirets autorisés.
    Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' +text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres et tirets sont autorisés.
    Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' text_caracters_maximum: %d caractères maximum. text_caracters_minimum: %d caractères minimum. text_length_between: Longueur comprise entre %d et %d caractères. @@ -593,6 +607,10 @@ text_destroy_time_entries_question: %.02f heures ont été enregistrées sur les text_destroy_time_entries: Supprimer les heures text_assign_time_entries_to_project: Reporter les heures sur le projet text_reassign_time_entries: 'Reporter les heures sur cette demande:' +text_user_wrote: '%s a écrit:' +text_enumeration_destroy_question: 'Cette valeur est affectée à %d objets.' +text_enumeration_category_reassign_to: 'Réaffecter les objets à cette valeur:' +text_email_delivery_not_configured: "L'envoi de mail n'est pas configuré, les notifications sont désactivées.\nConfigurez votre serveur SMTP dans config/email.yml et redémarrez l'application pour les activer." default_role_manager: Manager default_role_developper: Développeur diff --git a/groups/lang/he.yml b/groups/lang/he.yml index a611c8c39..77fe32e53 100644 --- a/groups/lang/he.yml +++ b/groups/lang/he.yml @@ -48,6 +48,7 @@ general_text_no: 'ל×' general_text_yes: 'כן' general_lang_name: 'Hebrew (עברית)' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-8-I general_pdf_encoding: ISO-8859-8-I general_day_names: שני,שלישי,רביעי,חמישי,שישי,שבת,ר×שון @@ -618,3 +619,20 @@ setting_default_projects_public: ×¤×¨×•×™×§×˜×™× ×—×“×©×™× ×”×™× × ×¤×•×ž×‘×™ error_scm_annotate: "הכניסה ×œ× ×§×™×™×ž×ª ×ו ×©×œ× × ×™×ª×Ÿ לת×ר ×ותה." label_planning: תכנון text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/hu.yml b/groups/lang/hu.yml new file mode 100644 index 000000000..208b6fe1e --- /dev/null +++ b/groups/lang/hu.yml @@ -0,0 +1,639 @@ +_gloc_rule_default: '|n| n==1 ? "" : "_plural" ' + +actionview_datehelper_select_day_prefix: +actionview_datehelper_select_month_names: Január,Február,Március,Ãprilis,Május,Június,Július,Augusztus,Szeptember,Október,November,December +actionview_datehelper_select_month_names_abbr: Jan,Feb,Már,Ãpr,Máj,Jún,Júl,Aug,Szept,Okt,Nov,Dec +actionview_datehelper_select_month_prefix: +actionview_datehelper_select_year_prefix: +actionview_datehelper_time_in_words_day: 1 nap +actionview_datehelper_time_in_words_day_plural: %d nap +actionview_datehelper_time_in_words_hour_about: kb. 1 óra +actionview_datehelper_time_in_words_hour_about_plural: kb. %d óra +actionview_datehelper_time_in_words_hour_about_single: kb. 1 óra +actionview_datehelper_time_in_words_minute: 1 perc +actionview_datehelper_time_in_words_minute_half: fél perc +actionview_datehelper_time_in_words_minute_less_than: kevesebb, mint 1 perc +actionview_datehelper_time_in_words_minute_plural: %d perc +actionview_datehelper_time_in_words_minute_single: 1 perc +actionview_datehelper_time_in_words_second_less_than: kevesebb, mint 1 másodperc +actionview_datehelper_time_in_words_second_less_than_plural: kevesebb, mint %d másodperc +actionview_instancetag_blank_option: Kérem válasszon + +activerecord_error_inclusion: nem található a listában +activerecord_error_exclusion: foglalt +activerecord_error_invalid: érvénytelen +activerecord_error_confirmation: jóváhagyás szükséges +activerecord_error_accepted: ell kell fogadni +activerecord_error_empty: nem lehet üres +activerecord_error_blank: nem lehet üres +activerecord_error_too_long: túl hosszú +activerecord_error_too_short: túl rövid +activerecord_error_wrong_length: hibás a hossza +activerecord_error_taken: már foglalt +activerecord_error_not_a_number: nem egy szám +activerecord_error_not_a_date: nem érvényes dátum +activerecord_error_greater_than_start_date: nagyobbnak kell lennie, mint az indítás dátuma +activerecord_error_not_same_project: nem azonos projekthez tartozik +activerecord_error_circular_dependency: Ez a kapcsolat egy körkörös függÅ‘séget eredményez + +general_fmt_age: %d év +general_fmt_age_plural: %d év +general_fmt_date: %%Y.%%m.%%d +general_fmt_datetime: %%Y.%%m.%%d %%H:%%M:%%S +general_fmt_datetime_short: %%b %%d, %%H:%%M:%%S +general_fmt_time: %%H:%%M:%%S +general_text_No: 'Nem' +general_text_Yes: 'Igen' +general_text_no: 'nem' +general_text_yes: 'igen' +general_lang_name: 'Magyar' +general_csv_separator: ',' +general_csv_decimal_separator: '.' +general_csv_encoding: ISO-8859-2 +general_pdf_encoding: ISO-8859-2 +general_day_names: HétfÅ‘,Kedd,Szerda,Csütörtök,Péntek,Szombat,Vasárnap +general_first_day_of_week: '1' + +notice_account_updated: A fiók adatai sikeresen frissítve. +notice_account_invalid_creditentials: Hibás felhasználói név, vagy jelszó +notice_account_password_updated: A jelszó módosítása megtörtént. +notice_account_wrong_password: Hibás jelszó +notice_account_register_done: A fiók sikeresen létrehozva. Aktiválásához kattints az e-mailben kapott linkre +notice_account_unknown_email: Ismeretlen felhasználó. +notice_can_t_change_password: A fiók külsÅ‘ azonosítási forrást használ. A jelszó megváltoztatása nem lehetséges. +notice_account_lost_email_sent: Egy e-mail üzenetben postáztunk Önnek egy leírást az új jelszó beállításáról. +notice_account_activated: Fiókját aktiváltuk. Most már be tud jelentkezni a rendszerbe. +notice_successful_create: Sikeres létrehozás. +notice_successful_update: Sikeres módosítás. +notice_successful_delete: Sikeres törlés. +notice_successful_connection: Sikeres bejelentkezés. +notice_file_not_found: Az oldal, amit meg szeretne nézni nem található, vagy átkerült egy másik helyre. +notice_locking_conflict: Az adatot egy másik felhasználó idÅ‘ közben módosította. +notice_not_authorized: Nincs hozzáférési engedélye ehhez az oldalhoz. +notice_email_sent: Egy e-mail üzenetet küldtünk a következÅ‘ címre %s +notice_email_error: Hiba történt a levél küldése közben (%s) +notice_feeds_access_key_reseted: Az RSS hozzáférési kulcsát újra generáltuk. +notice_failed_to_save_issues: "Nem sikerült a %d feladat(ok) mentése a %d -ban kiválasztva: %s." +notice_no_issue_selected: "Nincs feladat kiválasztva! Kérem jelölje meg melyik feladatot szeretné szerkeszteni!" +notice_account_pending: "A fiókja létrejött, és adminisztrátori jóváhagyásra vár." +notice_default_data_loaded: Az alapértelmezett konfiguráció betöltése sikeresen megtörtént. + +error_can_t_load_default_data: "Az alapértelmezett konfiguráció betöltése nem lehetséges: %s" +error_scm_not_found: "A bejegyzés, vagy revízió nem található a tárolóban." +error_scm_command_failed: "A tároló elérése közben hiba lépett fel: %s" +error_scm_annotate: "A bejegyzés nem létezik, vagy nics jegyzetekkel ellátva." +error_issue_not_found_in_project: 'A feladat nem található, vagy nem ehhez a projekthez tartozik' + +mail_subject_lost_password: Az Ön Redmine jelszava +mail_body_lost_password: 'A Redmine jelszó megváltoztatásához, kattintson a következÅ‘ linkre:' +mail_subject_register: Redmine azonosító aktiválása +mail_body_register: 'A Redmine azonosítója aktiválásához, kattintson a következÅ‘ linkre:' +mail_body_account_information_external: A "%s" azonosító használatával bejelentkezhet a Redmineba. +mail_body_account_information: Az Ön Redmine azonosítójának információi +mail_subject_account_activation_request: Redmine azonosító aktiválási kérelem +mail_body_account_activation_request: 'Egy új felhasználó (%s) regisztrált, azonosítója jóváhasgyásra várakozik:' + +gui_validation_error: 1 hiba +gui_validation_error_plural: %d hiba + +field_name: Név +field_description: Leírás +field_summary: Összegzés +field_is_required: KötelezÅ‘ +field_firstname: Keresztnév +field_lastname: Vezetéknév +field_mail: E-mail +field_filename: Fájl +field_filesize: Méret +field_downloads: Letöltések +field_author: SzerzÅ‘ +field_created_on: Létrehozva +field_updated_on: Módosítva +field_field_format: Formátum +field_is_for_all: Minden projekthez +field_possible_values: Lehetséges értékek +field_regexp: Reguláris kifejezés +field_min_length: Minimum hossz +field_max_length: Maximum hossz +field_value: Érték +field_category: Kategória +field_title: Cím +field_project: Projekt +field_issue: Feladat +field_status: Státusz +field_notes: Feljegyzések +field_is_closed: Feladat lezárva +field_is_default: Alapértelmezett érték +field_tracker: Típus +field_subject: Tárgy +field_due_date: Befejezés dátuma +field_assigned_to: FelelÅ‘s +field_priority: Prioritás +field_fixed_version: Cél verzió +field_user: Felhasználó +field_role: Szerepkör +field_homepage: Weboldal +field_is_public: Nyilvános +field_parent: SzülÅ‘ projekt +field_is_in_chlog: Feladatok látszanak a változás naplóban +field_is_in_roadmap: Feladatok látszanak az életútban +field_login: Azonosító +field_mail_notification: E-mail értesítések +field_admin: Adminisztrátor +field_last_login_on: Utolsó bejelentkezés +field_language: Nyelv +field_effective_date: Dátum +field_password: Jelszó +field_new_password: Új jelszó +field_password_confirmation: MegerÅ‘sítés +field_version: Verzió +field_type: Típus +field_host: Kiszolgáló +field_port: Port +field_account: Felhasználói fiók +field_base_dn: Base DN +field_attr_login: Bejelentkezési tulajdonság +field_attr_firstname: Családnév +field_attr_lastname: Utónév +field_attr_mail: E-mail +field_onthefly: On-the-fly felhasználó létrehozás +field_start_date: Kezdés dátuma +field_done_ratio: Elkészült (%%) +field_auth_source: Azonosítási mód +field_hide_mail: Rejtse el az e-mail címem +field_comments: Megjegyzés +field_url: URL +field_start_page: KezdÅ‘lap +field_subproject: Alprojekt +field_hours: Óra +field_activity: Aktivitás +field_spent_on: Dátum +field_identifier: Azonosító +field_is_filter: SzűrÅ‘ként használható +field_issue_to_id: Kapcsolódó feladat +field_delay: Késés +field_assignable: Feladat rendelhetÅ‘ ehhez a szerepkörhöz +field_redirect_existing_links: LétezÅ‘ linkek átirányítása +field_estimated_hours: Becsült idÅ‘ +field_column_names: Oszlopok +field_time_zone: IdÅ‘zóna +field_searchable: KereshetÅ‘ +field_default_value: Alapértelmezett érték +field_comments_sorting: Feljegyzések megjelenítése + +setting_app_title: Alkalmazás címe +setting_app_subtitle: Alkalmazás alcíme +setting_welcome_text: ÜdvözlÅ‘ üzenet +setting_default_language: Alapértelmezett nyelv +setting_login_required: Azonosítás szükséges +setting_self_registration: Regisztráció +setting_attachment_max_size: Melléklet max. mérete +setting_issues_export_limit: Feladatok exportálásának korlátja +setting_mail_from: Kibocsátó e-mail címe +setting_bcc_recipients: Titkos másolat címzet (bcc) +setting_host_name: Kiszolgáló neve +setting_text_formatting: Szöveg formázás +setting_wiki_compression: Wiki történet tömörítés +setting_feeds_limit: RSS tartalom korlát +setting_default_projects_public: Az új projektek alapértelmezés szerint nyilvánosak +setting_autofetch_changesets: Commitok automatikus lehúzása +setting_sys_api_enabled: WS engedélyezése a tárolók kezeléséhez +setting_commit_ref_keywords: Hivatkozó kulcsszavak +setting_commit_fix_keywords: Javítások kulcsszavai +setting_autologin: Automatikus bejelentkezés +setting_date_format: Dátum formátum +setting_time_format: IdÅ‘ formátum +setting_cross_project_issue_relations: Kereszt-projekt feladat hivatkozások engedélyezése +setting_issue_list_default_columns: Az alapértelmezésként megjelenített oszlopok a feladat listában +setting_repositories_encodings: Tárolók kódolása +setting_emails_footer: E-mail lábléc +setting_protocol: Protokol +setting_per_page_options: Objektum / oldal opciók +setting_user_format: Felhasználók megjelenítésének formája +setting_activity_days_default: Napok megjelenítése a project aktivitásnál +setting_display_subprojects_issues: Alapértelmezettként mutassa az alprojektek feladatait is a projekteken + +project_module_issue_tracking: Feladat követés +project_module_time_tracking: IdÅ‘ rögzítés +project_module_news: Hírek +project_module_documents: Dokumentumok +project_module_files: Fájlok +project_module_wiki: Wiki +project_module_repository: Tároló +project_module_boards: Fórumok + +label_user: Felhasználó +label_user_plural: Felhasználók +label_user_new: Új felhasználó +label_project: Projekt +label_project_new: Új projekt +label_project_plural: Projektek +label_project_all: Az összes projekt +label_project_latest: Legutóbbi projektek +label_issue: Feladat +label_issue_new: Új feladat +label_issue_plural: Feladatok +label_issue_view_all: Minden feladat megtekintése +label_issues_by: %s feladatai +label_issue_added: Feladat hozzáadva +label_issue_updated: Feladat frissítve +label_document: Dokumentum +label_document_new: Új dokumentum +label_document_plural: Dokumentumok +label_document_added: Dokumentum hozzáadva +label_role: Szerepkör +label_role_plural: Szerepkörök +label_role_new: Új szerepkör +label_role_and_permissions: Szerepkörök, és jogosultságok +label_member: RésztvevÅ‘ +label_member_new: Új résztvevÅ‘ +label_member_plural: RésztvevÅ‘k +label_tracker: Feladat típus +label_tracker_plural: Feladat típusok +label_tracker_new: Új feladat típus +label_workflow: Workflow +label_issue_status: Feladat státusz +label_issue_status_plural: Feladat státuszok +label_issue_status_new: Új státusz +label_issue_category: Feladat kategória +label_issue_category_plural: Feladat kategóriák +label_issue_category_new: Új kategória +label_custom_field: Egyéni mezÅ‘ +label_custom_field_plural: Egyéni mezÅ‘k +label_custom_field_new: Új egyéni mezÅ‘ +label_enumerations: Felsorolások +label_enumeration_new: Új érték +label_information: Információ +label_information_plural: Információk +label_please_login: Jelentkezzen be +label_register: Regisztráljon +label_password_lost: Elfelejtett jelszó +label_home: KezdÅ‘lap +label_my_page: Saját kezdÅ‘lapom +label_my_account: Fiókom adatai +label_my_projects: Saját projektem +label_administration: Adminisztráció +label_login: Bejelentkezés +label_logout: Kijelentkezés +label_help: Súgó +label_reported_issues: Bejelentett feladatok +label_assigned_to_me_issues: A nekem kiosztott feladatok +label_last_login: Utolsó bejelentkezés +label_last_updates: Utoljára frissítve +label_last_updates_plural: Utoljára módosítva %d +label_registered_on: Regisztrált +label_activity: Tevékenységek +label_overall_activity: Teljes aktivitás +label_new: Új +label_logged_as: Bejelentkezve, mint +label_environment: Környezet +label_authentication: Azonosítás +label_auth_source: Azonosítás módja +label_auth_source_new: Új azonosítási mód +label_auth_source_plural: Azonosítási módok +label_subproject_plural: Alprojektek +label_and_its_subprojects: %s és alprojektjei +label_min_max_length: Min - Max hossz +label_list: Lista +label_date: Dátum +label_integer: Egész +label_float: LebegÅ‘pontos +label_boolean: Logikai +label_string: Szöveg +label_text: Hosszú szöveg +label_attribute: Tulajdonság +label_attribute_plural: Tulajdonságok +label_download: %d Letöltés +label_download_plural: %d Letöltések +label_no_data: Nincs megjeleníthetÅ‘ adat +label_change_status: Státusz módosítása +label_history: Történet +label_attachment: Fájl +label_attachment_new: Új fájl +label_attachment_delete: Fájl törlése +label_attachment_plural: Fájlok +label_file_added: Fájl hozzáadva +label_report: Jelentés +label_report_plural: Jelentések +label_news: Hírek +label_news_new: Hír hozzáadása +label_news_plural: Hírek +label_news_latest: Legutóbbi hírek +label_news_view_all: Minden hír megtekintése +label_news_added: Hír hozzáadva +label_change_log: Változás napló +label_settings: Beállítások +label_overview: Ãttekintés +label_version: Verzió +label_version_new: Új verzió +label_version_plural: Verziók +label_confirmation: Jóváhagyás +label_export_to: Exportálás +label_read: Olvas... +label_public_projects: Nyilvános projektek +label_open_issues: nyitott +label_open_issues_plural: nyitott +label_closed_issues: lezárt +label_closed_issues_plural: lezárt +label_total: Összesen +label_permissions: Jogosultságok +label_current_status: Jelenlegi státusz +label_new_statuses_allowed: Státusz változtatások engedélyei +label_all: mind +label_none: nincs +label_nobody: senki +label_next: KövetkezÅ‘ +label_previous: ElÅ‘zÅ‘ +label_used_by: Használja +label_details: Részletek +label_add_note: Jegyzet hozzáadása +label_per_page: Oldalanként +label_calendar: Naptár +label_months_from: hónap, kezdve +label_gantt: Gantt +label_internal: BelsÅ‘ +label_last_changes: utolsó %d változás +label_change_view_all: Minden változás megtekintése +label_personalize_page: Az oldal testreszabása +label_comment: Megjegyzés +label_comment_plural: Megjegyzés +label_comment_add: Megjegyzés hozzáadása +label_comment_added: Megjegyzés hozzáadva +label_comment_delete: Megjegyzések törlése +label_query: Egyéni lekérdezés +label_query_plural: Egyéni lekérdezések +label_query_new: Új lekérdezés +label_filter_add: SzűrÅ‘ hozzáadása +label_filter_plural: SzűrÅ‘k +label_equals: egyenlÅ‘ +label_not_equals: nem egyenlÅ‘ +label_in_less_than: kevesebb, mint +label_in_more_than: több, mint +label_in: in +label_today: ma +label_all_time: mindenkor +label_yesterday: tegnap +label_this_week: aktuális hét +label_last_week: múlt hét +label_last_n_days: az elmúlt %d nap +label_this_month: aktuális hónap +label_last_month: múlt hónap +label_this_year: aktuális év +label_date_range: Dátum intervallum +label_less_than_ago: kevesebb, mint nappal ezelÅ‘tt +label_more_than_ago: több, mint nappal ezelÅ‘tt +label_ago: nappal ezelÅ‘tt +label_contains: tartalmazza +label_not_contains: nem tartalmazza +label_day_plural: nap +label_repository: Tároló +label_repository_plural: Tárolók +label_browse: Tallóz +label_modification: %d változás +label_modification_plural: %d változások +label_revision: Revízió +label_revision_plural: Revíziók +label_associated_revisions: Kapcsolt revíziók +label_added: hozzáadva +label_modified: módosítva +label_deleted: törölve +label_latest_revision: Legutolsó revízió +label_latest_revision_plural: Legutolsó revíziók +label_view_revisions: Revíziók megtekintése +label_max_size: Maximális méret +label_on: 'összesen' +label_sort_highest: Az elejére +label_sort_higher: Eggyel feljebb +label_sort_lower: Eggyel lejjebb +label_sort_lowest: Az aljára +label_roadmap: Életút +label_roadmap_due_in: Elkészültéig várhatóan még +label_roadmap_overdue: %s késésben +label_roadmap_no_issues: Nincsenek feladatok ehhez a verzióhoz +label_search: Keresés +label_result_plural: Találatok +label_all_words: Minden szó +label_wiki: Wiki +label_wiki_edit: Wiki szerkesztés +label_wiki_edit_plural: Wiki szerkesztések +label_wiki_page: Wiki oldal +label_wiki_page_plural: Wiki oldalak +label_index_by_title: Cím szerint indexelve +label_index_by_date: Dátum szerint indexelve +label_current_version: Jelenlegi verzió +label_preview: ElÅ‘nézet +label_feed_plural: Visszajelzések +label_changes_details: Változások részletei +label_issue_tracking: Feladat követés +label_spent_time: Ráfordított idÅ‘ +label_f_hour: %.2f óra +label_f_hour_plural: %.2f óra +label_time_tracking: IdÅ‘ követés +label_change_plural: Változások +label_statistics: Statisztikák +label_commits_per_month: Commits havonta +label_commits_per_author: Commits szerzÅ‘nként +label_view_diff: Különbségek megtekintése +label_diff_inline: inline +label_diff_side_by_side: side by side +label_options: Opciók +label_copy_workflow_from: Workflow másolása innen +label_permissions_report: Jogosultsági riport +label_watched_issues: Megfigyelt feladatok +label_related_issues: Kapcsolódó feladatok +label_applied_status: Alkalmazandó státusz +label_loading: Betöltés... +label_relation_new: Új kapcsolat +label_relation_delete: Kapcsolat törlése +label_relates_to: kapcsolódik +label_duplicates: duplikálja +label_blocks: zárolja +label_blocked_by: zárolta +label_precedes: megelÅ‘zi +label_follows: követi +label_end_to_start: végétÅ‘l indulásig +label_end_to_end: végétÅ‘l végéig +label_start_to_start: indulástól indulásig +label_start_to_end: indulástól végéig +label_stay_logged_in: Emlékezzen rám +label_disabled: kikapcsolva +label_show_completed_versions: A kész verziók mutatása +label_me: én +label_board: Fórum +label_board_new: Új fórum +label_board_plural: Fórumok +label_topic_plural: Témák +label_message_plural: Üzenetek +label_message_last: Utolsó üzenet +label_message_new: Új üzenet +label_message_posted: Üzenet hozzáadva +label_reply_plural: Válaszok +label_send_information: Fiók infomációk küldése a felhasználónak +label_year: Év +label_month: Hónap +label_week: Hét +label_date_from: 'Kezdet:' +label_date_to: 'Vége:' +label_language_based: A felhasználó nyelve alapján +label_sort_by: %s szerint rendezve +label_send_test_email: Teszt e-mail küldése +label_feeds_access_key_created_on: 'RSS hozzáférési kulcs létrehozva ennyivel ezelÅ‘tt: %s' +label_module_plural: Modulok +label_added_time_by: '%s adta hozzá ennyivel ezelÅ‘tt: %s' +label_updated_time: 'Utolsó módosítás ennyivel ezelÅ‘tt: %s' +label_jump_to_a_project: Ugrás projekthez... +label_file_plural: Fájlok +label_changeset_plural: Changesets +label_default_columns: Alapértelmezett oszlopok +label_no_change_option: (Nincs változás) +label_bulk_edit_selected_issues: A kiválasztott feladatok kötegelt szerkesztése +label_theme: Téma +label_default: Alapértelmezett +label_search_titles_only: Keresés csak a címekben +label_user_mail_option_all: "Minden eseményrÅ‘l minden saját projektemben" +label_user_mail_option_selected: "Minden eseményrÅ‘l a kiválasztott projektekben..." +label_user_mail_option_none: "Csak a megfigyelt dolgokról, vagy, amiben részt veszek" +label_user_mail_no_self_notified: "Nem kérek értesítést az általam végzett módosításokról" +label_registration_activation_by_email: Fiók aktiválása e-mailben +label_registration_manual_activation: Manuális fiók aktiválás +label_registration_automatic_activation: Automatikus fiók aktiválás +label_display_per_page: 'Oldalanként: %s' +label_age: Kor +label_change_properties: Tulajdonságok változtatása +label_general: Ãltalános +label_more: továbbiak +label_scm: SCM +label_plugins: Pluginek +label_ldap_authentication: LDAP azonosítás +label_downloads_abbr: D/L +label_optional_description: Opcionális leírás +label_add_another_file: Újabb fájl hozzáadása +label_preferences: Tulajdonságok +label_chronological_order: IdÅ‘rendben +label_reverse_chronological_order: Fordított idÅ‘rendben +label_planning: Tervezés + +button_login: Bejelentkezés +button_submit: Elfogad +button_save: Mentés +button_check_all: Mindent kijelöl +button_uncheck_all: Kijelölés törlése +button_delete: Töröl +button_create: Létrehoz +button_test: Teszt +button_edit: Szerkeszt +button_add: Hozzáad +button_change: Változtat +button_apply: Alkalmaz +button_clear: Töröl +button_lock: Zárol +button_unlock: Felold +button_download: Letöltés +button_list: Lista +button_view: Megnéz +button_move: Mozgat +button_back: Vissza +button_cancel: Mégse +button_activate: Aktivál +button_sort: Rendezés +button_log_time: IdÅ‘ rögzítés +button_rollback: Visszaáll erre a verzióra +button_watch: Megfigyel +button_unwatch: Megfigyelés törlése +button_reply: Válasz +button_archive: Archivál +button_unarchive: Dearchivál +button_reset: Reset +button_rename: Ãtnevez +button_change_password: Jelszó megváltoztatása +button_copy: Másol +button_annotate: Jegyzetel +button_update: Módosít +button_configure: Konfigurál + +status_active: aktív +status_registered: regisztrált +status_locked: zárolt + +text_select_mail_notifications: Válasszon eseményeket, amelyekrÅ‘l e-mail értesítést kell küldeni. +text_regexp_info: eg. ^[A-Z0-9]+$ +text_min_max_length_info: 0 = nincs korlátozás +text_project_destroy_confirmation: Biztosan törölni szeretné a projektet és vele együtt minden kapcsolódó adatot ? +text_subprojects_destroy_warning: 'Az alprojekt(ek): %s szintén törlésre kerülnek.' +text_workflow_edit: Válasszon egy szerepkört, és egy trackert a workflow szerkesztéséhez +text_are_you_sure: Biztos benne ? +text_journal_changed: "változás: %s volt, %s lett" +text_journal_set_to: "beállítva: %s" +text_journal_deleted: törölve +text_tip_task_begin_day: a feladat ezen a napon kezdÅ‘dik +text_tip_task_end_day: a feladat ezen a napon ér véget +text_tip_task_begin_end_day: a feladat ezen a napon kezdÅ‘dik és ér véget +text_project_identifier_info: 'Kis betűk (a-z), számok és kötÅ‘jel megengedett.
    Mentés után az azonosítót megváltoztatni nem lehet.' +text_caracters_maximum: maximum %d karakter. +text_caracters_minimum: Legkevesebb %d karakter hosszúnek kell lennie. +text_length_between: Legalább %d és legfeljebb %d hosszú karakter. +text_tracker_no_workflow: Nincs workflow definiálva ehhez a tracker-hez +text_unallowed_characters: Tiltott karakterek +text_comma_separated: Több érték megengedett (vesszÅ‘vel elválasztva) +text_issues_ref_in_commit_messages: Hivatkozás feladatokra, feladatok javítása a commit üzenetekben +text_issue_added: %s feladat bejelentve. +text_issue_updated: %s feladat frissítve. +text_wiki_destroy_confirmation: Biztosan törölni szeretné ezt a wiki-t minden tartalmával együtt ? +text_issue_category_destroy_question: Néhány feladat (%d) hozzá van rendelve ehhez a kategóriához. Mit szeretne tenni ? +text_issue_category_destroy_assignments: Kategória hozzárendelés megszűntetése +text_issue_category_reassign_to: Feladatok újra hozzárendelése a kategóriához +text_user_mail_option: "A nem kiválasztott projektekrÅ‘l csak akkor kap értesítést, ha figyelést kér rá, vagy részt vesz benne (pl. Ön a létrehozó, vagy a hozzárendelÅ‘)" +text_no_configuration_data: "Szerepkörök, trackerek, feladat státuszok, és workflow adatok még nincsenek konfigurálva.\nErÅ‘sen ajánlott, az alapértelmezett konfiguráció betöltése, és utána módosíthatja azt." +text_load_default_configuration: Alapértelmezett konfiguráció betöltése +text_status_changed_by_changeset: Applied in changeset %s. +text_issues_destroy_confirmation: 'Biztos benne, hogy törölni szeretné a kijelölt feladato(ka)t ?' +text_select_project_modules: 'Válassza ki az engedélyezett modulokat ehhez a projekthez:' +text_default_administrator_account_changed: Alapértelmezett adminisztrátor fiók megváltoztatva +text_file_repository_writable: Fájl tároló írható +text_rmagick_available: RMagick elérhetÅ‘ (opcionális) +text_destroy_time_entries_question: %.02f órányi munka van rögzítve a feladatokon, amiket törölni szeretne. Mit szeretne tenni ? +text_destroy_time_entries: A rögzített órák törlése +text_assign_time_entries_to_project: A rögzített órák hozzárendelése a projekthez +text_reassign_time_entries: 'A rögzített órák újra hozzárendelése ehhez a feladathoz:' + +default_role_manager: VezetÅ‘ +default_role_developper: FejlesztÅ‘ +default_role_reporter: BejelentÅ‘ +default_tracker_bug: Hiba +default_tracker_feature: Fejlesztés +default_tracker_support: Support +default_issue_status_new: Új +default_issue_status_assigned: Kiosztva +default_issue_status_resolved: Megoldva +default_issue_status_feedback: Visszajelzés +default_issue_status_closed: Lezárt +default_issue_status_rejected: Elutasított +default_doc_category_user: Felhasználói dokumentáció +default_doc_category_tech: Technikai dokumentáció +default_priority_low: Alacsony +default_priority_normal: Normál +default_priority_high: Magas +default_priority_urgent: SürgÅ‘s +default_priority_immediate: Azonnal +default_activity_design: Tervezés +default_activity_development: Fejlesztés + +enumeration_issue_priorities: Feladat prioritások +enumeration_doc_categories: Dokumentum kategóriák +enumeration_activities: Tevékenységek (idÅ‘ rögzítés) +mail_body_reminder: "%d neked kiosztott feladat határidÅ‘s az elkövetkezÅ‘ %d napban:" +mail_subject_reminder: "%d feladat határidÅ‘s az elkövetkezÅ‘ napokban" +text_user_wrote: '%s írta:' +label_duplicated_by: duplikálta +setting_enabled_scm: ForráskódkezelÅ‘ (SCM) engedélyezése +text_enumeration_category_reassign_to: 'Újra hozzárendelés ehhez:' +text_enumeration_destroy_question: '%d objektum van hozzárendelve ehhez az értékhez.' +label_incoming_emails: Beérkezett levelek +label_generate_key: Kulcs generálása +setting_mail_handler_api_enabled: Web Service engedélyezése a beérkezett levelekhez +setting_mail_handler_api_key: API kulcs +text_email_delivery_not_configured: "Az E-mail küldés nincs konfigurálva, és az értesítések ki vannak kapcsolva.\nÃllítsd be az SMTP szervert a config/email.yml fájlban és indítsd újra az alkalmazást, hogy érvénybe lépjen." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/it.yml b/groups/lang/it.yml index 3d1dea09e..d123e913a 100644 --- a/groups/lang/it.yml +++ b/groups/lang/it.yml @@ -20,24 +20,24 @@ actionview_datehelper_time_in_words_second_less_than_plural: meno di %d secondi actionview_instancetag_blank_option: Scegli activerecord_error_inclusion: non è incluso nella lista -activerecord_error_exclusion: e' riservato -activerecord_error_invalid: non e' valido +activerecord_error_exclusion: è riservato +activerecord_error_invalid: non è valido activerecord_error_confirmation: non coincide con la conferma activerecord_error_accepted: deve essere accettato activerecord_error_empty: non puo' essere vuoto activerecord_error_blank: non puo' essere blank -activerecord_error_too_long: e' troppo lungo/a -activerecord_error_too_short: e' troppo corto/a -activerecord_error_wrong_length: e' della lunghezza sbagliata -activerecord_error_taken: e' gia' stato/a preso/a -activerecord_error_not_a_number: non e' un numero -activerecord_error_not_a_date: non e' una data valida +activerecord_error_too_long: è troppo lungo/a +activerecord_error_too_short: è troppo corto/a +activerecord_error_wrong_length: è della lunghezza sbagliata +activerecord_error_taken: è già stato/a preso/a +activerecord_error_not_a_number: non è un numero +activerecord_error_not_a_date: non è una data valida activerecord_error_greater_than_start_date: deve essere maggiore della data di partenza -activerecord_error_not_same_project: doesn't belong to the same project -activerecord_error_circular_dependency: This relation would create a circular dependency +activerecord_error_not_same_project: non appartiene allo stesso progetto +activerecord_error_circular_dependency: Questa relazione creerebbe una dipendenza circolare -general_fmt_age: %d yr -general_fmt_age_plural: %d yrs +general_fmt_age: %d anno +general_fmt_age_plural: %d anni general_fmt_date: %%d/%%m/%%Y general_fmt_datetime: %%d/%%m/%%Y %%I:%%M %%p general_fmt_datetime_short: %%b %%d, %%I:%%M %%p @@ -48,6 +48,7 @@ general_text_no: 'no' general_text_yes: 'si' general_lang_name: 'Italiano' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_day_names: Lunedì,Martedì,Mercoledì,Giovedì,Venerdì,Sabato,Domenica @@ -68,13 +69,13 @@ notice_successful_delete: Eliminazione effettuata. notice_successful_connection: Connessione effettuata. notice_file_not_found: La pagina desiderata non esiste o è stata rimossa. notice_locking_conflict: Le informazioni sono state modificate da un altro utente. -notice_not_authorized: You are not authorized to access this page. -notice_email_sent: An email was sent to %s -notice_email_error: An error occurred while sending mail (%s) -notice_feeds_access_key_reseted: Your RSS access key was reseted. +notice_not_authorized: Non sei autorizzato ad accedere a questa pagina. +notice_email_sent: Una e-mail è stata spedita a %s +notice_email_error: Si è verificato un errore durante l'invio di una e-mail (%s) +notice_feeds_access_key_reseted: La tua chiave di accesso RSS è stata reimpostata. error_scm_not_found: "La risorsa e/o la versione non esistono nel repository." -error_scm_command_failed: "An error occurred when trying to access the repository: %s" +error_scm_command_failed: "Si è verificato un errore durante l'accesso al repository: %s" mail_subject_lost_password: Password %s mail_body_lost_password: 'Per cambiare la password, usate il seguente collegamento:' @@ -110,21 +111,21 @@ field_project: Progetto field_issue: Issue field_status: Stato field_notes: Note -field_is_closed: Chiude il contesto +field_is_closed: Chiude la segnalazione field_is_default: Stato predefinito field_tracker: Tracker field_subject: Oggetto field_due_date: Data ultima field_assigned_to: Assegnato a field_priority: Priorita' -field_fixed_version: Target version +field_fixed_version: Versione prevista field_user: Utente field_role: Ruolo field_homepage: Homepage field_is_public: Pubblico field_parent: Sottoprogetto di -field_is_in_chlog: Contesti mostrati nel changelog -field_is_in_roadmap: Contesti mostrati nel roadmap +field_is_in_chlog: Segnalazioni mostrate nel changelog +field_is_in_roadmap: Segnalazioni mostrate nel roadmap field_login: Login field_mail_notification: Notifiche via e-mail field_admin: Amministratore @@ -153,16 +154,16 @@ field_comments: Commento field_url: URL field_start_page: Pagina principale field_subproject: Sottoprogetto -field_hours: Hours -field_activity: Activity +field_hours: Ore +field_activity: Attività field_spent_on: Data -field_identifier: Identifier -field_is_filter: Used as a filter -field_issue_to_id: Related issue -field_delay: Delay -field_assignable: Issues can be assigned to this role -field_redirect_existing_links: Redirect existing links -field_estimated_hours: Estimated time +field_identifier: Identificativo +field_is_filter: Usato come filtro +field_issue_to_id: Segnalazioni correlate +field_delay: Ritardo +field_assignable: E' possibile assegnare segnalazioni a questo ruolo +field_redirect_existing_links: Redirige i collegamenti esistenti +field_estimated_hours: Tempo stimato field_default_value: Stato predefinito setting_app_title: Titolo applicazione @@ -172,7 +173,7 @@ setting_default_language: Lingua di default setting_login_required: Autenticazione richiesta setting_self_registration: Auto-registrazione abilitata setting_attachment_max_size: Massima dimensione allegati -setting_issues_export_limit: Limite esportazione contesti +setting_issues_export_limit: Limite esportazione segnalazioni setting_mail_from: Indirizzo sorgente e-mail setting_host_name: Nome host setting_text_formatting: Formattazione testo @@ -182,9 +183,9 @@ setting_autofetch_changesets: Acquisisci automaticamente le commit setting_sys_api_enabled: Abilita WS per la gestione del repository setting_commit_ref_keywords: Referencing keywords setting_commit_fix_keywords: Fixing keywords -setting_autologin: Autologin -setting_date_format: Date format -setting_cross_project_issue_relations: Allow cross-project issue relations +setting_autologin: Login automatico +setting_date_format: Formato data +setting_cross_project_issue_relations: Consenti la creazione di relazioni tra segnalazioni in progetti differenti label_user: Utente label_user_plural: Utenti @@ -192,12 +193,12 @@ label_user_new: Nuovo utente label_project: Progetto label_project_new: Nuovo progetto label_project_plural: Progetti -label_project_all: All Projects +label_project_all: Tutti i progetti label_project_latest: Ultimi progetti registrati -label_issue: Contesto -label_issue_new: Nuovo contesto -label_issue_plural: Contesti -label_issue_view_all: Mostra tutti i contesti +label_issue: Segnalazione +label_issue_new: Nuova segnalazione +label_issue_plural: Segnalazioni +label_issue_view_all: Mostra tutte le segnalazioni label_document: Documento label_document_new: Nuovo documento label_document_plural: Documenti @@ -212,11 +213,11 @@ label_tracker: Tracker label_tracker_plural: Tracker label_tracker_new: Nuovo tracker label_workflow: Workflow -label_issue_status: Stato contesti -label_issue_status_plural: Stati contesto +label_issue_status: Stato segnalazioni +label_issue_status_plural: Stati segnalazione label_issue_status_new: Nuovo stato -label_issue_category: Categorie contesti -label_issue_category_plural: Categorie contesto +label_issue_category: Categorie segnalazioni +label_issue_category_plural: Categorie segnalazioni label_issue_category_new: Nuova categoria label_custom_field: Campo personalizzato label_custom_field_plural: Campi personalizzati @@ -236,8 +237,8 @@ label_administration: Amministrazione label_login: Login label_logout: Logout label_help: Aiuto -label_reported_issues: Contesti segnalati -label_assigned_to_me_issues: I miei contesti +label_reported_issues: Segnalazioni +label_assigned_to_me_issues: Le mie segnalazioni label_last_login: Ultimo collegamento label_last_updates: Ultimo aggiornamento label_last_updates_plural: %d ultimo aggiornamento @@ -276,7 +277,7 @@ label_news_new: Aggiungi notizia label_news_plural: Notizie label_news_latest: Utime notizie label_news_view_all: Tutte le notizie -label_change_log: Change log +label_change_log: Elenco modifiche label_settings: Impostazioni label_overview: Panoramica label_version: Versione @@ -314,7 +315,7 @@ label_comment_plural: Commenti label_comment_add: Aggiungi un commento label_comment_added: Commento aggiunto label_comment_delete: Elimina commenti -label_query: Custom query +label_query: Query personalizzata label_query_plural: Query personalizzate label_query_new: Nuova query label_filter_add: Aggiungi filtro @@ -325,7 +326,7 @@ label_in_less_than: è minore di label_in_more_than: è maggiore di label_in: in label_today: oggi -label_this_week: this week +label_this_week: questa settimana label_less_than_ago: meno di giorni fa label_more_than_ago: più di giorni fa label_ago: giorni fa @@ -333,7 +334,7 @@ label_contains: contiene label_not_contains: non contiene label_day_plural: giorni label_repository: Repository -label_browse: Browse +label_browse: Sfoglia label_modification: %d modifica label_modification_plural: %d modifiche label_revision: Versione @@ -353,7 +354,7 @@ label_sort_lowest: Sposta in fondo label_roadmap: Roadmap label_roadmap_due_in: Da ultimare in label_roadmap_overdue: %s late -label_roadmap_no_issues: Nessun contesto per questa versione +label_roadmap_no_issues: Nessuna segnalazione per questa versione label_search: Ricerca label_result_plural: Risultati label_all_words: Tutte le parole @@ -368,7 +369,7 @@ label_current_version: Versione corrente label_preview: Anteprima label_feed_plural: Feed label_changes_details: Particolari di tutti i cambiamenti -label_issue_tracking: tracking dei contesti +label_issue_tracking: tracking delle segnalazioni label_spent_time: Tempo impiegato label_f_hour: %.2f ora label_f_hour_plural: %.2f ore @@ -378,53 +379,53 @@ label_statistics: Statistiche label_commits_per_month: Commit per mese label_commits_per_author: Commit per autore label_view_diff: mostra differenze -label_diff_inline: inline -label_diff_side_by_side: side by side +label_diff_inline: in linea +label_diff_side_by_side: fianco a fianco label_options: Opzioni label_copy_workflow_from: Copia workflow da label_permissions_report: Report permessi -label_watched_issues: Watched issues -label_related_issues: Related issues -label_applied_status: Applied status -label_loading: Loading... -label_relation_new: New relation -label_relation_delete: Delete relation -label_relates_to: related to -label_duplicates: duplicates -label_blocks: blocks -label_blocked_by: blocked by -label_precedes: precedes -label_follows: follows +label_watched_issues: Segnalazioni osservate +label_related_issues: Segnalazioni correlate +label_applied_status: Stato applicato +label_loading: Caricamento... +label_relation_new: Nuova relazione +label_relation_delete: Elimina relazione +label_relates_to: correlato a +label_duplicates: duplicati +label_blocks: blocchi +label_blocked_by: bloccato da +label_precedes: precede +label_follows: segue label_end_to_start: end to start label_end_to_end: end to end label_start_to_start: start to start label_start_to_end: start to end -label_stay_logged_in: Stay logged in -label_disabled: disabled -label_show_completed_versions: Show completed versions -label_me: me +label_stay_logged_in: Rimani collegato +label_disabled: disabilitato +label_show_completed_versions: Mostra versioni completate +label_me: io label_board: Forum -label_board_new: New forum -label_board_plural: Forums -label_topic_plural: Topics -label_message_plural: Messages -label_message_last: Last message -label_message_new: New message -label_reply_plural: Replies -label_send_information: Send account information to the user -label_year: Year -label_month: Month -label_week: Week -label_date_from: From -label_date_to: To -label_language_based: Language based -label_sort_by: Sort by %s -label_send_test_email: Send a test email -label_feeds_access_key_created_on: RSS access key created %s ago -label_module_plural: Modules -label_added_time_by: Added by %s %s ago -label_updated_time: Updated %s ago -label_jump_to_a_project: Jump to a project... +label_board_new: Nuovo forum +label_board_plural: Forum +label_topic_plural: Argomenti +label_message_plural: Messaggi +label_message_last: Ultimo messaggio +label_message_new: Nuovo messaggio +label_reply_plural: Risposte +label_send_information: Invia all'utente le informazioni relative all'account +label_year: Anno +label_month: Mese +label_week: Settimana +label_date_from: Da +label_date_to: A +label_language_based: Basato sul linguaggio +label_sort_by: Ordina per %s +label_send_test_email: Invia una e-mail di test +label_feeds_access_key_created_on: chiave di accesso RSS creata %s fa +label_module_plural: Moduli +label_added_time_by: Aggiunto da %s %s fa +label_updated_time: Aggiornato %s fa +label_jump_to_a_project: Vai al progetto... button_login: Login button_submit: Invia @@ -451,13 +452,13 @@ button_activate: Attiva button_sort: Ordina button_log_time: Registra tempo button_rollback: Ripristina questa versione -button_watch: Watch -button_unwatch: Unwatch -button_reply: Reply -button_archive: Archive -button_unarchive: Unarchive +button_watch: Osserva +button_unwatch: Dimentica +button_reply: Rispondi +button_archive: Archivia +button_unarchive: Ripristina button_reset: Reset -button_rename: Rename +button_rename: Rinomina status_active: attivo status_registered: registrato @@ -475,32 +476,32 @@ text_journal_deleted: cancellato text_tip_task_begin_day: attività che iniziano in questa giornata text_tip_task_end_day: attività che terminano in questa giornata text_tip_task_begin_end_day: attività che iniziano e terminano in questa giornata -text_project_identifier_info: 'Lower case letters (a-z), numbers and dashes allowed.
    Once saved, the identifier can not be changed.' +text_project_identifier_info: "Lettere minuscole (a-z), numeri e trattini permessi.
    Una volta salvato, l'identificativo non può essere modificato." text_caracters_maximum: massimo %d caratteri. text_length_between: Lunghezza compresa tra %d e %d caratteri. text_tracker_no_workflow: Nessun workflow definito per questo tracker -text_unallowed_characters: Unallowed characters -text_comma_separated: Multiple values allowed (comma separated). +text_unallowed_characters: Caratteri non permessi +text_comma_separated: Valori multipli permessi (separati da virgola). text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages text_issue_added: "E' stata segnalata l'anomalia %s da %s." text_issue_updated: "L'anomalia %s e' stata aggiornata da %s." text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ? -text_issue_category_destroy_question: Some issues (%d) are assigned to this category. What do you want to do ? -text_issue_category_destroy_assignments: Remove category assignments -text_issue_category_reassign_to: Reassing issues to this category +text_issue_category_destroy_question: Alcune segnalazioni (%d) risultano assegnate a questa categoria. Cosa vuoi fare ? +text_issue_category_destroy_assignments: Rimuovi gli assegnamenti a questa categoria +text_issue_category_reassign_to: Riassegna segnalazioni a questa categoria default_role_manager: Manager default_role_developper: Sviluppatore default_role_reporter: Reporter -default_tracker_bug: Contesto +default_tracker_bug: Segnalazione default_tracker_feature: Funzione default_tracker_support: Supporto -default_issue_status_new: Nuovo/a -default_issue_status_assigned: Assegnato/a -default_issue_status_resolved: Risolto/a +default_issue_status_new: Nuovo +default_issue_status_assigned: Assegnato +default_issue_status_resolved: Risolto default_issue_status_feedback: Feedback -default_issue_status_closed: Chiuso/a -default_issue_status_rejected: Rifiutato/a +default_issue_status_closed: Chiuso +default_issue_status_rejected: Rifiutato default_doc_category_user: Documentazione utente default_doc_category_tech: Documentazione tecnica default_priority_low: Bassa @@ -508,113 +509,130 @@ default_priority_normal: Normale default_priority_high: Alta default_priority_urgent: Urgente default_priority_immediate: Immediata -default_activity_design: Design -default_activity_development: Development +default_activity_design: Progettazione +default_activity_development: Sviluppo -enumeration_issue_priorities: Priorità contesti +enumeration_issue_priorities: Priorità segnalazioni enumeration_doc_categories: Categorie di documenti enumeration_activities: Attività (time tracking) -label_file_plural: Files -label_changeset_plural: Changesets -field_column_names: Columns -label_default_columns: Default columns -setting_issue_list_default_columns: Default columns displayed on the issue list -setting_repositories_encodings: Repositories encodings -notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." -label_bulk_edit_selected_issues: Bulk edit selected issues -label_no_change_option: (No change) -notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." -label_theme: Theme -label_default: Default -label_search_titles_only: Search titles only -label_nobody: nobody -button_change_password: Change password +label_file_plural: File +label_changeset_plural: Changeset +field_column_names: Colonne +label_default_columns: Colonne predefinite +setting_issue_list_default_columns: Colonne predefinite mostrate nell'elenco segnalazioni +setting_repositories_encodings: Codifiche dei repository +notice_no_issue_selected: "Nessuna segnalazione selezionata! Seleziona le segnalazioni che intendi modificare." +label_bulk_edit_selected_issues: Modifica massiva delle segnalazioni selezionate +label_no_change_option: (Nessuna modifica) +notice_failed_to_save_issues: "Impossibile salvare %d segnalazioni su %d selezionate: %s." +label_theme: Tema +label_default: Predefinito +label_search_titles_only: Cerca solo nei titoli +label_nobody: nessuno +button_change_password: Modifica password text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." -label_user_mail_option_selected: "For any event on the selected projects only..." -label_user_mail_option_all: "For any event on all my projects" -label_user_mail_option_none: "Only for things I watch or I'm involved in" -setting_emails_footer: Emails footer +label_user_mail_option_selected: "Solo per gli eventi relativi ai progetti selezionati..." +label_user_mail_option_all: "Per ogni evento relativo ad uno dei miei progetti" +label_user_mail_option_none: "Solo per argomenti che osservo o che mi riguardano" +setting_emails_footer: Piè di pagina e-mail label_float: Float -button_copy: Copy -mail_body_account_information_external: You can use your "%s" account to log in. -mail_body_account_information: Your account information -setting_protocol: Protocol -label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" -setting_time_format: Time format -label_registration_activation_by_email: account activation by email -mail_subject_account_activation_request: %s account activation request -mail_body_account_activation_request: 'A new user (%s) has registered. His account his pending your approval:' -label_registration_automatic_activation: automatic account activation -label_registration_manual_activation: manual account activation -notice_account_pending: "Your account was created and is now pending administrator approval." +button_copy: Copia +mail_body_account_information_external: Puoi utilizzare il tuo account "%s" per accedere al sistema. +mail_body_account_information: Le informazioni riguardanti il tuo account +setting_protocol: Protocollo +label_user_mail_no_self_notified: "Non voglio notifiche riguardanti modifiche da me apportate" +setting_time_format: Formato ora +label_registration_activation_by_email: attivazione account via e-mail +mail_subject_account_activation_request: %s richiesta attivazione account +mail_body_account_activation_request: 'Un nuovo utente (%s) ha effettuato la registrazione. Il suo account è in attesa di abilitazione da parte tua:' +label_registration_automatic_activation: attivazione account automatica +label_registration_manual_activation: attivazione account manuale +notice_account_pending: "Il tuo account è stato creato ed è in attesa di attivazione da parte dell'amministratore." field_time_zone: Time zone -text_caracters_minimum: Must be at least %d characters long. -setting_bcc_recipients: Blind carbon copy recipients (bcc) -button_annotate: Annotate -label_issues_by: Issues by %s -field_searchable: Searchable -label_display_per_page: 'Per page: %s' -setting_per_page_options: Objects per page options -label_age: Age -notice_default_data_loaded: Default configuration successfully loaded. -text_load_default_configuration: Load the default configuration -text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." -error_can_t_load_default_data: "Default configuration could not be loaded: %s" -button_update: Update -label_change_properties: Change properties -label_general: General -label_repository_plural: Repositories -label_associated_revisions: Associated revisions -setting_user_format: Users display format -text_status_changed_by_changeset: Applied in changeset %s. -label_more: More -text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?' +text_caracters_minimum: Deve essere lungo almeno %d caratteri. +setting_bcc_recipients: Destinatari in copia nascosta (bcc) +button_annotate: Annota +label_issues_by: Segnalazioni di %s +field_searchable: Ricercabile +label_display_per_page: 'Per pagina: %s' +setting_per_page_options: Opzioni oggetti per pagina +label_age: Età +notice_default_data_loaded: Configurazione di default caricata con successo. +text_load_default_configuration: Carica la configurazione di default +text_no_configuration_data: "Ruoli, tracker, stati delle segnalazioni e workflow non sono stati ancora configurati.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." +error_can_t_load_default_data: "Non è stato possibile caricare la configurazione di default : %s" +button_update: Aggiorna +label_change_properties: Modifica le proprietà +label_general: Generale +label_repository_plural: Repository +label_associated_revisions: Revisioni associate +setting_user_format: Formato visualizzazione utenti +text_status_changed_by_changeset: Applicata nel changeset %s. +label_more: Altro +text_issues_destroy_confirmation: 'Sei sicuro di voler eliminare le segnalazioni selezionate?' label_scm: SCM -text_select_project_modules: 'Select modules to enable for this project:' -label_issue_added: Issue added -label_issue_updated: Issue updated -label_document_added: Document added -label_message_posted: Message added -label_file_added: File added -label_news_added: News added +text_select_project_modules: 'Seleziona i moduli abilitati per questo progetto:' +label_issue_added: Segnalazioni aggiunte +label_issue_updated: Segnalazioni aggiornate +label_document_added: Documenti aggiunti +label_message_posted: Messaggi aggiunti +label_file_added: File aggiunti +label_news_added: Notizie aggiunte project_module_boards: Boards -project_module_issue_tracking: Issue tracking +project_module_issue_tracking: Tracking delle segnalazioni project_module_wiki: Wiki -project_module_files: Files -project_module_documents: Documents +project_module_files: File +project_module_documents: Documenti project_module_repository: Repository -project_module_news: News +project_module_news: Notizie project_module_time_tracking: Time tracking -text_file_repository_writable: File repository writable -text_default_administrator_account_changed: Default administrator account changed -text_rmagick_available: RMagick available (optional) -button_configure: Configure -label_plugins: Plugins -label_ldap_authentication: LDAP authentication +text_file_repository_writable: Repository dei file scrivibile +text_default_administrator_account_changed: L'account amministrativo di default è stato modificato +text_rmagick_available: RMagick disponibile (opzionale) +button_configure: Configura +label_plugins: Plugin +label_ldap_authentication: Autenticazione LDAP label_downloads_abbr: D/L -label_this_month: this month -label_last_n_days: last %d days -label_all_time: all time -label_this_year: this year -label_date_range: Date range -label_last_week: last week -label_yesterday: yesterday -label_last_month: last month -label_add_another_file: Add another file -label_optional_description: Optional description -text_destroy_time_entries_question: %.02f hours were reported on the issues you are about to delete. What do you want to do ? -error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' -text_assign_time_entries_to_project: Assign reported hours to the project -text_destroy_time_entries: Delete reported hours -text_reassign_time_entries: 'Reassign reported hours to this issue:' -setting_activity_days_default: Days displayed on project activity -label_chronological_order: In chronological order -field_comments_sorting: Display comments -label_reverse_chronological_order: In reverse chronological order -label_preferences: Preferences -setting_display_subprojects_issues: Display subprojects issues on main projects by default -label_overall_activity: Overall activity -setting_default_projects_public: New projects are public by default -error_scm_annotate: "The entry does not exist or can not be annotated." -label_planning: Planning -text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_this_month: questo mese +label_last_n_days: ultimi %d giorni +label_all_time: sempre +label_this_year: quest'anno +label_date_range: Intervallo di date +label_last_week: ultima settimana +label_yesterday: ieri +label_last_month: ultimo mese +label_add_another_file: Aggiungi un altro file +label_optional_description: Descrizione opzionale +text_destroy_time_entries_question: %.02f ore risultano spese sulle segnalazioni che stai per cancellare. Cosa vuoi fare ? +error_issue_not_found_in_project: 'La segnalazione non è stata trovata o non appartiene al progetto' +text_assign_time_entries_to_project: Assegna le ore segnalate al progetto +text_destroy_time_entries: Elimina le ore segnalate +text_reassign_time_entries: 'Riassegna le ore a questa segnalazione:' +setting_activity_days_default: Giorni mostrati sulle attività di progetto +label_chronological_order: In ordine cronologico +field_comments_sorting: Mostra commenti +label_reverse_chronological_order: In ordine cronologico inverso +label_preferences: Preferenze +setting_display_subprojects_issues: Mostra le segnalazioni dei sottoprogetti nel progetto principale per default +label_overall_activity: Attività generale +setting_default_projects_public: I nuovi progetti sono pubblici per default +error_scm_annotate: "L'oggetto non esiste o non può essere annotato." +label_planning: Pianificazione +text_subprojects_destroy_warning: 'Anche i suoi sottoprogetti: %s verranno eliminati.' +label_and_its_subprojects: %s ed i suoi sottoprogetti +mail_body_reminder: "%d segnalazioni che ti sono state assegnate scadranno nei prossimi %d giorni:" +mail_subject_reminder: "%d segnalazioni in scadenza nei prossimi giorni" +text_user_wrote: '%s ha scritto:' +label_duplicated_by: duplicato da +setting_enabled_scm: SCM abilitato +text_enumeration_category_reassign_to: 'Riassegnale a questo valore:' +text_enumeration_destroy_question: '%d oggetti hanno un assegnamento su questo valore.' +label_incoming_emails: E-mail in arrivo +label_generate_key: Genera una chiave +setting_mail_handler_api_enabled: Abilita WS per le e-mail in arrivo +setting_mail_handler_api_key: chiave API +text_email_delivery_not_configured: "La consegna via e-mail non è configurata e le notifiche sono disabilitate.\nConfigura il tuo server SMTP in config/email.yml e riavvia l'applicazione per abilitarle." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/ja.yml b/groups/lang/ja.yml index 680d29836..5a728fb02 100644 --- a/groups/lang/ja.yml +++ b/groups/lang/ja.yml @@ -49,6 +49,7 @@ general_text_no: 'ã„ã„ãˆ' general_text_yes: 'ã¯ã„' general_lang_name: 'Japanese (日本語)' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: SJIS general_pdf_encoding: UTF-8 general_day_names: 月曜日,ç«æ›œæ—¥,水曜日,木曜日,金曜日,土曜日,日曜日 @@ -619,3 +620,20 @@ setting_default_projects_public: ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§æ–°ã—ã„プロジェクト error_scm_annotate: "エントリãŒå­˜åœ¨ã—ãªã„ã€ã‚‚ã—ãã¯ã‚¢ãƒŽãƒ†ãƒ¼ãƒˆã§ãã¾ã›ã‚“。" label_planning: 計画 text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/ko.yml b/groups/lang/ko.yml index 4281f3881..16bd65364 100644 --- a/groups/lang/ko.yml +++ b/groups/lang/ko.yml @@ -48,6 +48,7 @@ general_text_no: '아니오' general_text_yes: '예' general_lang_name: 'Korean (한국어)' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: CP949 general_pdf_encoding: CP949 general_day_names: 월요ì¼,화요ì¼,수요ì¼,목요ì¼,금요ì¼,토요ì¼,ì¼ìš”ì¼ @@ -618,3 +619,20 @@ setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/lt.yml b/groups/lang/lt.yml index df7cd960b..2a75a95ea 100644 --- a/groups/lang/lt.yml +++ b/groups/lang/lt.yml @@ -48,6 +48,7 @@ general_text_no: 'ne' general_text_yes: 'taip' general_lang_name: 'Lithuanian (lietuvių)' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: UTF-8 general_pdf_encoding: UTF-8 general_day_names: pirmadienis,antradienis,treÄiadienis,ketvirtadienis,penktadienis,Å¡eÅ¡tadienis,sekmadienis @@ -554,7 +555,7 @@ enumeration_issue_priorities: Darbo prioritetai enumeration_doc_categories: Dokumento kategorijos enumeration_activities: Veiklos (laiko sekimas) label_display_per_page: '%s įrašų puslapyje' -setting_per_page_options: Objects per page options +setting_per_page_options: Ä®rašų puslapyje nustatimas notice_default_data_loaded: Numatytoji konfiguracija sÄ—kmingai užkrauta. label_age: Amžius label_general: Bendri @@ -578,44 +579,63 @@ label_document_added: Dokumentas pridÄ—tas label_message_posted: PraneÅ¡imas pridÄ—tas label_file_added: Byla pridÄ—ta label_news_added: Naujiena pridÄ—ta -project_module_boards: Boards -project_module_issue_tracking: Issue tracking +project_module_boards: Forumai +project_module_issue_tracking: Darbu pÄ—dsekys project_module_wiki: Wiki -project_module_files: Files -project_module_documents: Documents -project_module_repository: Repository -project_module_news: News -project_module_time_tracking: Time tracking -text_file_repository_writable: File repository writable -text_default_administrator_account_changed: Default administrator account changed -text_rmagick_available: RMagick available (optional) -button_configure: Configure +project_module_files: Rinkmenos +project_module_documents: Dokumentai +project_module_repository: Saugykla +project_module_news: Žinios +project_module_time_tracking: Laiko pÄ—dsekys +text_file_repository_writable: Ä® rinkmenu saugyklÄ… galima saugoti (RW) +text_default_administrator_account_changed: Administratoriaus numatyta paskyra pakeista +text_rmagick_available: RMagick pasiekiamas (pasirinktinai) +button_configure: Konfiguruoti label_plugins: Plugins -label_ldap_authentication: LDAP authentication -label_downloads_abbr: D/L -label_this_month: this month -label_last_n_days: last %d days -label_all_time: all time -label_this_year: this year -label_date_range: Date range -label_last_week: last week -label_yesterday: yesterday -label_last_month: last month -label_add_another_file: Add another file -label_optional_description: Optional description -text_destroy_time_entries_question: %.02f hours were reported on the issues you are about to delete. What do you want to do ? -error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' -text_assign_time_entries_to_project: Assign reported hours to the project -text_destroy_time_entries: Delete reported hours -text_reassign_time_entries: 'Reassign reported hours to this issue:' -setting_activity_days_default: Days displayed on project activity -label_chronological_order: In chronological order -field_comments_sorting: Display comments -label_reverse_chronological_order: In reverse chronological order -label_preferences: Preferences -setting_display_subprojects_issues: Display subprojects issues on main projects by default -label_overall_activity: Overall activity -setting_default_projects_public: New projects are public by default -error_scm_annotate: "The entry does not exist or can not be annotated." -label_planning: Planning -text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_ldap_authentication: LDAP autentifikacija +label_downloads_abbr: siunt. +label_this_month: Å¡is menuo +label_last_n_days: paskutinių %d dienų +label_all_time: visas laikas +label_this_year: Å¡iemet +label_date_range: Dienų diapazonas +label_last_week: paskutinÄ— savaitÄ— +label_yesterday: vakar +label_last_month: paskutinis menuo +label_add_another_file: PridÄ—ti kitÄ… bylÄ… +label_optional_description: ApibÅ«dinimas (laisvai pasirenkamas) +text_destroy_time_entries_question: Naikinamam darbui paskelbta %.02f valandų. KÄ… jÅ«s noryte su jomis daryti? +error_issue_not_found_in_project: 'Darbas nerastas arba nesuriÅ¡tas su Å¡iuo projektu' +text_assign_time_entries_to_project: Priskirti valandas prie projekto +text_destroy_time_entries: IÅ¡trinti paskelbtas valandas +text_reassign_time_entries: 'Priskirti paskelbtas valandas Å¡iam darbui:' +setting_activity_days_default: Atvaizduojamos dienos projekto veikloje +label_chronological_order: Chronologine tvarka +field_comments_sorting: rodyti komentarus +label_reverse_chronological_order: Atbuline chronologine tvarka +label_preferences: SavybÄ—s +setting_display_subprojects_issues: Pagal nutylÄ—jimÄ… rodyti subprojektų darbus pagrindiniame projekte +label_overall_activity: Visa veikla +setting_default_projects_public: Naujas projektas vieÅ¡as pagal nutylÄ—jimÄ… +error_scm_annotate: "Ä®raÅ¡as neegzituoja arba negalima jo atvaizduoti." +label_planning: Planavimas +text_subprojects_destroy_warning: 'Å is(ie) subprojektas(ai): %s taip pat bus iÅ¡trintas(i).' +label_and_its_subprojects: %s projektas ir jo subprojektai + +mail_body_reminder: "%d darbas(ai), kurie yra jums priskirti, baigiasi po %d dienų(os):" +mail_subject_reminder: "%d darbas(ai) po kelių dienų" +text_user_wrote: '%s parašė:' +label_duplicated_by: susiejo +setting_enabled_scm: Ä®galintas SCM +text_enumeration_category_reassign_to: 'Priskirti juos Å¡iai reikÅ¡mei:' +text_enumeration_destroy_question: '%d objektai priskirti Å¡iai reikÅ¡mei.' +label_incoming_emails: Ä®einantys laiÅ¡kai +label_generate_key: Generuoti raktÄ… +setting_mail_handler_api_enabled: Ä®galinti WS įeinantiems laiÅ¡kams +setting_mail_handler_api_key: API raktas + +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/nl.yml b/groups/lang/nl.yml index e487a7a6d..f79e78994 100644 --- a/groups/lang/nl.yml +++ b/groups/lang/nl.yml @@ -48,6 +48,7 @@ general_text_no: 'nee' general_text_yes: 'ja' general_lang_name: 'Nederlands' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_day_names: Maandag, Dinsdag, Woensdag, Donderdag, Vrijdag, Zaterdag, Zondag @@ -619,3 +620,20 @@ setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/no.yml b/groups/lang/no.yml index 22b8c10af..6643f9c86 100644 --- a/groups/lang/no.yml +++ b/groups/lang/no.yml @@ -48,6 +48,7 @@ general_text_no: 'nei' general_text_yes: 'ja' general_lang_name: 'Norwegian (Norsk bokmÃ¥l)' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_day_names: Mandag,Tirsdag,Onsdag,Torsdag,Fredag,Lørdag,Søndag @@ -91,6 +92,8 @@ mail_body_account_information_external: Du kan bruke din "%s"-konto for Ã¥ logge mail_body_account_information: Informasjon om din konto mail_subject_account_activation_request: %s kontoaktivering mail_body_account_activation_request: 'En ny bruker (%s) er registrert, og avventer din godkjenning:' +mail_subject_reminder: "%d sak(er) har frist de kommende dagene" +mail_body_reminder: "%d sak(er) som er tildelt deg har frist de kommende %d dager:" gui_validation_error: 1 feil gui_validation_error_plural: %d feil @@ -211,6 +214,7 @@ setting_per_page_options: Alternativer, objekter pr. side setting_user_format: Visningsformat, brukere setting_activity_days_default: Dager vist pÃ¥ prosjektaktivitet setting_display_subprojects_issues: Vis saker fra underprosjekter pÃ¥ hovedprosjekt som standard +setting_enabled_scm: Aktiviserte SCM project_module_issue_tracking: Sakssporing project_module_time_tracking: Tidssporing @@ -291,6 +295,7 @@ label_auth_source: Autentifikasjonsmodus label_auth_source_new: Ny autentifikasjonmodus label_auth_source_plural: Autentifikasjonsmoduser label_subproject_plural: Underprosjekter +label_and_its_subprojects: %s og dets underprosjekter label_min_max_length: Min.-maks. lengde label_list: Liste label_date: Dato @@ -444,7 +449,8 @@ label_loading: Laster... label_relation_new: Ny relasjon label_relation_delete: Slett relasjon label_relates_to: relatert til -label_duplicates: duplikater +label_duplicates: dupliserer +label_duplicated_by: duplisert av label_blocks: blokkerer label_blocked_by: blokkert av label_precedes: kommer før @@ -557,7 +563,7 @@ text_select_mail_notifications: Velg hendelser som skal varsles med e-post. text_regexp_info: eg. ^[A-Z0-9]+$ text_min_max_length_info: 0 betyr ingen begrensning text_project_destroy_confirmation: Er du sikker pÃ¥ at du vil slette dette prosjekter og alle relatert data ? -text_subprojects_destroy_warning: 'Underprojekt(ene): %s vil ogsÃ¥ bli slettet.' +text_subprojects_destroy_warning: 'Underprojekt(ene): %s vil ogsÃ¥ bli slettet.' text_workflow_edit: Velg en rolle og en sakstype for Ã¥ endre arbeidsflyten text_are_you_sure: Er du sikker ? text_journal_changed: endret fra %s til %s @@ -593,6 +599,7 @@ text_destroy_time_entries_question: %.02f timer er ført pÃ¥ sakene du er i ferd text_destroy_time_entries: Slett førte timer text_assign_time_entries_to_project: Overfør førte timer til prosjektet text_reassign_time_entries: 'Overfør førte timer til denne saken:' +text_user_wrote: '%s skrev:' default_role_manager: Leder default_role_developper: Utvikler @@ -619,3 +626,14 @@ default_activity_development: Utvikling enumeration_issue_priorities: Sakssprioriteringer enumeration_doc_categories: Dokument-kategorier enumeration_activities: Aktiviteter (tidssporing) +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/pl.yml b/groups/lang/pl.yml index 81f03a62f..2df921b71 100644 --- a/groups/lang/pl.yml +++ b/groups/lang/pl.yml @@ -48,6 +48,7 @@ general_text_no: 'nie' general_text_yes: 'tak' general_lang_name: 'Polski' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-2 general_pdf_encoding: ISO-8859-2 general_day_names: PoniedziaÅ‚ek,Wtorek,Åšroda,Czwartek,PiÄ…tek,Sobota,Niedziela @@ -70,7 +71,7 @@ notice_file_not_found: Strona do której próbujesz siÄ™ dostać nie istnieje lu notice_locking_conflict: Dane poprawione przez innego użytkownika. notice_not_authorized: Nie jesteÅ› autoryzowany by zobaczyć stronÄ™. -error_scm_not_found: "WejÅ›cie i/lub zmiana nie istnieje w repozytorium." +error_scm_not_found: "Obiekt lub wersja nie zostaÅ‚y znalezione w repozytorium." error_scm_command_failed: "An error occurred when trying to access the repository: %s" mail_subject_lost_password: Twoje hasÅ‚o do %s @@ -114,12 +115,12 @@ field_subject: Temat field_due_date: Data oddania field_assigned_to: Przydzielony do field_priority: Priorytet -field_fixed_version: Target version +field_fixed_version: Wersja docelowa field_user: Użytkownik field_role: Rola field_homepage: Strona www field_is_public: Publiczny -field_parent: Podprojekt +field_parent: Nadprojekt field_is_in_chlog: Zagadnienie pokazywane w zapisie zmian field_is_in_roadmap: Zagadnienie pokazywane na mapie field_login: Login @@ -172,10 +173,10 @@ setting_host_name: Nazwa hosta setting_text_formatting: Formatowanie tekstu setting_wiki_compression: Kompresja historii Wiki setting_feeds_limit: Limit danych RSS -setting_autofetch_changesets: Auto-odÅ›wieżanie CVS +setting_autofetch_changesets: Automatyczne pobieranie zmian setting_sys_api_enabled: Włączenie WS do zarzÄ…dzania repozytorium -setting_commit_ref_keywords: Terminy odnoszÄ…ce (CVS) -setting_commit_fix_keywords: Terminy ustalajÄ…ce (CVS) +setting_commit_ref_keywords: SÅ‚owa tworzÄ…ce powiÄ…zania +setting_commit_fix_keywords: SÅ‚owa zmieniajÄ…ce status setting_autologin: Auto logowanie setting_date_format: Format daty @@ -328,14 +329,14 @@ label_repository: Repozytorium label_browse: PrzeglÄ…d label_modification: %d modyfikacja label_modification_plural: %d modyfikacja -label_revision: Zmiana -label_revision_plural: Zmiany +label_revision: Rewizja +label_revision_plural: Rewizje label_added: dodane -label_modified: zmodufikowane +label_modified: zmodyfikowane label_deleted: usuniÄ™te -label_latest_revision: Ostatnia zmiana -label_latest_revision_plural: Ostatnie zmiany -label_view_revisions: Pokaż zmiany +label_latest_revision: Najnowsza rewizja +label_latest_revision_plural: Najnowsze rewizje +label_view_revisions: Pokaż rewizje label_max_size: Maksymalny rozmiar label_on: 'z' label_sort_highest: PrzesuÅ„ na górÄ™ @@ -366,8 +367,8 @@ label_f_hour_plural: %.2f godzin label_time_tracking: Åšledzenie czasu label_change_plural: Zmiany label_statistics: Statystyki -label_commits_per_month: Wrzutek CVS w miesiÄ…cu -label_commits_per_author: Wrzutek CVS przez autora +label_commits_per_month: Zatwierdzenia wedÅ‚ug miesiÄ™cy +label_commits_per_author: Zatwierdzenia wedÅ‚ug autorów label_view_diff: Pokaż różnice label_diff_inline: w linii label_diff_side_by_side: obok siebie @@ -463,13 +464,13 @@ text_length_between: DÅ‚ugość pomiÄ™dzy %d i %d znaków. text_tracker_no_workflow: Brak przepÅ‚ywu zefiniowanego dla tego typu zagadnienia text_unallowed_characters: Niedozwolone znaki text_comma_separated: Wielokrotne wartoÅ›ci dozwolone (rozdzielone przecinkami). -text_issues_ref_in_commit_messages: Zagadnienia odnoszÄ…ce i ustalajÄ…ce we wrzutkach CVS +text_issues_ref_in_commit_messages: OdwoÅ‚ania do zagadnieÅ„ w komentarzach zatwierdzeÅ„ default_role_manager: Kierownik default_role_developper: Programista default_role_reporter: Wprowadzajacy default_tracker_bug: Błąd -default_tracker_feature: Cecha +default_tracker_feature: Zadanie default_tracker_support: Wsparcie default_issue_status_new: Nowy default_issue_status_assigned: Przypisany @@ -483,7 +484,7 @@ default_priority_low: Niski default_priority_normal: Normalny default_priority_high: Wysoki default_priority_urgent: Pilny -default_priority_immediate: Natyczmiastowy +default_priority_immediate: Natychmiastowy default_activity_design: Projektowanie default_activity_development: Rozwój @@ -618,3 +619,20 @@ setting_default_projects_public: Nowe projekty sÄ… domyÅ›lnie publiczne error_scm_annotate: "Wpis nie istnieje lub nie można do niego dodawać adnotacji." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/pt-br.yml b/groups/lang/pt-br.yml index 9facd8d19..8cd171b72 100644 --- a/groups/lang/pt-br.yml +++ b/groups/lang/pt-br.yml @@ -1,140 +1,141 @@ _gloc_rule_default: '|n| n==1 ? "" : "_plural" ' actionview_datehelper_select_day_prefix: -actionview_datehelper_select_month_names: Janeiro,Fevereiro,Marco,Abrill,Maio,Junho,Julho,Agosto,Setembro,Outubro,Novembro,Dezembro +actionview_datehelper_select_month_names: Janeiro,Fevereiro,Março,Abrill,Maio,Junho,Julho,Agosto,Setembro,Outubro,Novembro,Dezembro actionview_datehelper_select_month_names_abbr: Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez actionview_datehelper_select_month_prefix: actionview_datehelper_select_year_prefix: actionview_datehelper_time_in_words_day: 1 dia actionview_datehelper_time_in_words_day_plural: %d dias -actionview_datehelper_time_in_words_hour_about: sobre uma hora -actionview_datehelper_time_in_words_hour_about_plural: sobra %d horas -actionview_datehelper_time_in_words_hour_about_single: sobre uma hora +actionview_datehelper_time_in_words_hour_about: aproximadamente uma hora +actionview_datehelper_time_in_words_hour_about_plural: aproximadamente %d horas +actionview_datehelper_time_in_words_hour_about_single: aproximadamente uma hora actionview_datehelper_time_in_words_minute: 1 minuto actionview_datehelper_time_in_words_minute_half: meio minuto -actionview_datehelper_time_in_words_minute_less_than: menos que um minuto +actionview_datehelper_time_in_words_minute_less_than: menos de um minuto actionview_datehelper_time_in_words_minute_plural: %d minutos actionview_datehelper_time_in_words_minute_single: 1 minuto -actionview_datehelper_time_in_words_second_less_than: menos que um segundo -actionview_datehelper_time_in_words_second_less_than_plural: menos que %d segundos +actionview_datehelper_time_in_words_second_less_than: menos de um segundo +actionview_datehelper_time_in_words_second_less_than_plural: menos de %d segundos actionview_instancetag_blank_option: Selecione -activerecord_error_inclusion: nao esta incluido na lista -activerecord_error_exclusion: esta reservado -activerecord_error_invalid: e invalido -activerecord_error_confirmation: confirmacao nao confere +activerecord_error_inclusion: não está incluso na lista +activerecord_error_exclusion: está reservado +activerecord_error_invalid: é inválido +activerecord_error_confirmation: confirmação não confere activerecord_error_accepted: deve ser aceito -activerecord_error_empty: nao pode ser vazio -activerecord_error_blank: nao pode estar em branco -activerecord_error_too_long: e muito longo -activerecord_error_too_short: e muito comprido -activerecord_error_wrong_length: esta com o comprimento errado -activerecord_error_taken: ja esta examinado -activerecord_error_not_a_number: nao e um numero -activerecord_error_not_a_date: nao e uma data valida +activerecord_error_empty: não pode ser vazio +activerecord_error_blank: não pode estar em branco +activerecord_error_too_long: é muito longo +activerecord_error_too_short: é muito curto +activerecord_error_wrong_length: esta com o tamanho errado +activerecord_error_taken: já foi obtido +activerecord_error_not_a_number: não é um numero +activerecord_error_not_a_date: não é uma data valida activerecord_error_greater_than_start_date: deve ser maior que a data inicial -activerecord_error_not_same_project: doesn't belong to the same project -activerecord_error_circular_dependency: This relation would create a circular dependency +activerecord_error_not_same_project: não pode pertencer ao mesmo projeto +activerecord_error_circular_dependency: Esta relação geraria uma dependência circular -general_fmt_age: %d yr -general_fmt_age_plural: %d yrs -general_fmt_date: %%m/%%d/%%Y -general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p +general_fmt_age: %d ano +general_fmt_age_plural: %d anos +general_fmt_date: %%d/%%m/%%Y +general_fmt_datetime: %%d/%%m/%%Y %%I:%%M %%p general_fmt_datetime_short: %%b %%d, %%I:%%M %%p general_fmt_time: %%I:%%M %%p -general_text_No: 'Nao' +general_text_No: 'Não' general_text_Yes: 'Sim' -general_text_no: 'nao' +general_text_no: 'não' general_text_yes: 'sim' -general_lang_name: 'Portugues Brasileiro' +general_lang_name: 'Português(Brasil)' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 -general_day_names: Segunda,Terca,Quarta,Quinta,Sexta,Sabado,Domingo +general_day_names: Segunda,Terça,Quarta,Quinta,Sexta,Sabado,Domingo general_first_day_of_week: '1' notice_account_updated: Conta foi alterada com sucesso. -notice_account_invalid_creditentials: Usuario ou senha invalido. -notice_account_password_updated: Senha foi alterada com sucesso. -notice_account_wrong_password: Senha errada. -notice_account_register_done: Conta foi criada com sucesso. -notice_account_unknown_email: Usuario desconhecido. -notice_can_t_change_password: Esta conta usa autenticacao externa. E impossivel trocar a senha. -notice_account_lost_email_sent: Um email com instrucoes para escolher uma nova senha foi enviado para voce. -notice_account_activated: Sua conta foi ativada. Voce pode logar agora +notice_account_invalid_creditentials: Usuário ou senha inválido. +notice_account_password_updated: Senha alterada com sucesso. +notice_account_wrong_password: Senha inválida. +notice_account_register_done: Conta criada com sucesso. +notice_account_unknown_email: Usuário desconhecido. +notice_can_t_change_password: Esta conta usa autenticação externa. E impossível alterar a senha. +notice_account_lost_email_sent: Um email com instruções para escolher uma nova senha foi enviado para você. +notice_account_activated: Sua conta foi ativada. Você pode acessá-la agora. notice_successful_create: Criado com sucesso. notice_successful_update: Alterado com sucesso. -notice_successful_delete: Apagado com sucesso. +notice_successful_delete: Excluído com sucesso. notice_successful_connection: Conectado com sucesso. -notice_file_not_found: A pagina que voce esta tentando acessar nao existe ou foi excluida. -notice_locking_conflict: Os dados foram atualizados por um outro usuario. -notice_not_authorized: You are not authorized to access this page. -notice_email_sent: An email was sent to %s -notice_email_error: An error occurred while sending mail (%s) -notice_feeds_access_key_reseted: Your RSS access key was reseted. +notice_file_not_found: A página que você está tentando acessar não existe ou foi excluída. +notice_locking_conflict: Os dados foram atualizados por outro usuário. +notice_not_authorized: Você não está autorizado a acessar esta página. +notice_email_sent: Um email foi enviado para %s +notice_email_error: Um erro ocorreu ao enviar o email (%s) +notice_feeds_access_key_reseted: Sua chave RSS foi reconfigurada. -error_scm_not_found: "A entrada e/ou a revisao nao existem no repositorio." -error_scm_command_failed: "An error occurred when trying to access the repository: %s" +error_scm_not_found: "A entrada e/ou a revisão não existe no repositório." +error_scm_command_failed: "Ocorreu um erro ao tentar acessar o repositório: %s" mail_subject_lost_password: Sua senha do %s. mail_body_lost_password: 'Para mudar sua senha, clique no link abaixo:' -mail_subject_register: Ativacao de conta do %s. +mail_subject_register: Ativação de conta do %s. mail_body_register: 'Para ativar sua conta, clique no link abaixo:' gui_validation_error: 1 erro gui_validation_error_plural: %d erros field_name: Nome -field_description: Descricao -field_summary: Sumario -field_is_required: Obrigatorio +field_description: Descrição +field_summary: Resumo +field_is_required: Obrigatório field_firstname: Primeiro nome -field_lastname: Ultimo nome +field_lastname: Último nome field_mail: Email field_filename: Arquivo field_filesize: Tamanho field_downloads: Downloads field_author: Autor -field_created_on: Criado -field_updated_on: Alterado +field_created_on: Criado em +field_updated_on: Alterado em field_field_format: Formato field_is_for_all: Para todos os projetos -field_possible_values: Possiveis valores -field_regexp: Expressao regular -field_min_length: Tamanho minimo -field_max_length: Tamanho maximo +field_possible_values: Possíveis valores +field_regexp: Expressão regular +field_min_length: Tamanho mínimo +field_max_length: Tamanho máximo field_value: Valor field_category: Categoria -field_title: Titulo +field_title: Título field_project: Projeto -field_issue: Tarefa +field_issue: Ticket field_status: Status field_notes: Notas -field_is_closed: Tarefa fechada -field_is_default: Status padrao +field_is_closed: Ticket fechado +field_is_default: Status padrão field_tracker: Tipo -field_subject: Titulo -field_due_date: Data devida -field_assigned_to: Atribuido para +field_subject: Título +field_due_date: Data prevista +field_assigned_to: Atribuído para field_priority: Prioridade -field_fixed_version: Target version -field_user: Usuario -field_role: Regra -field_homepage: Pagina inicial -field_is_public: Publico +field_fixed_version: Versão +field_user: Usuário +field_role: Papel +field_homepage: Página inicial +field_is_public: Público field_parent: Sub-projeto de -field_is_in_chlog: Tarefas mostradas no changelog -field_is_in_roadmap: Tarefas mostradas no roadmap +field_is_in_chlog: Tarefas exibidas no registro de alterações +field_is_in_roadmap: Tarefas exibidas no planejamento field_login: Login -field_mail_notification: Notificacoes por email +field_mail_notification: Notificações por email field_admin: Administrador -field_last_login_on: Ultima conexao -field_language: Lingua +field_last_login_on: Última conexão +field_language: Idioma field_effective_date: Data field_password: Senha field_new_password: Nova senha -field_password_confirmation: Confirmacao -field_version: Versao +field_password_confirmation: Confirmação +field_version: Versão field_type: Tipo field_host: Servidor field_port: Porta @@ -142,116 +143,116 @@ field_account: Conta field_base_dn: Base DN field_attr_login: Atributo login field_attr_firstname: Atributo primeiro nome -field_attr_lastname: Atributo ultimo nome +field_attr_lastname: Atributo último nome field_attr_mail: Atributo email -field_onthefly: Criacao de usuario on-the-fly -field_start_date: Inicio +field_onthefly: Criação automática de usuário +field_start_date: Início field_done_ratio: %% Terminado -field_auth_source: Modo de autenticacao -field_hide_mail: Esconder meu email -field_comments: Comentario +field_auth_source: Modo de autenticação +field_hide_mail: Ocultar meu email +field_comments: Comentário field_url: URL -field_start_page: Pagina inicial +field_start_page: Página inicial field_subproject: Sub-projeto field_hours: Horas field_activity: Atividade field_spent_on: Data field_identifier: Identificador -field_is_filter: Used as a filter -field_issue_to_id: Related issue -field_delay: Delay -field_assignable: Issues can be assigned to this role -field_redirect_existing_links: Redirect existing links -field_estimated_hours: Estimated time -field_default_value: Padrao +field_is_filter: É um filtro +field_issue_to_id: Ticket relacionado +field_delay: Espera +field_assignable: Tickets podem ser atribuídos para este papel +field_redirect_existing_links: Redirecionar links existentes +field_estimated_hours: Tempo estimado +field_default_value: Padrão -setting_app_title: Titulo da aplicacao -setting_app_subtitle: Sub-titulo da aplicacao -setting_welcome_text: Texto de boa-vinda -setting_default_language: Lingua padrao -setting_login_required: Autenticacao obrigatoria -setting_self_registration: Registro de si mesmo permitido -setting_attachment_max_size: Tamanho maximo do anexo -setting_issues_export_limit: Limite de exportacao das tarefas +setting_app_title: Título da aplicação +setting_app_subtitle: Sub-título da aplicação +setting_welcome_text: Texto de boas-vindas +setting_default_language: Idioma padrão +setting_login_required: Autenticação obrigatória +setting_self_registration: Permitido Auto-registro +setting_attachment_max_size: Tamanho máximo do anexo +setting_issues_export_limit: Limite de exportação das tarefas setting_mail_from: Email enviado de setting_host_name: Servidor setting_text_formatting: Formato do texto -setting_wiki_compression: Compactacao do historio do Wiki +setting_wiki_compression: Compactação de histórico do Wiki setting_feeds_limit: Limite do Feed -setting_autofetch_changesets: Autofetch commits -setting_sys_api_enabled: Ativa WS para gerenciamento do repositorio -setting_commit_ref_keywords: Referencing keywords -setting_commit_fix_keywords: Fixing keywords -setting_autologin: Autologin -setting_date_format: Date format -setting_cross_project_issue_relations: Allow cross-project issue relations +setting_autofetch_changesets: Auto-obter commits +setting_sys_api_enabled: Ativa WS para gerenciamento do repositório +setting_commit_ref_keywords: Palavras de referência +setting_commit_fix_keywords: Palavras de fechamento +setting_autologin: Auto-login +setting_date_format: Formato da data +setting_cross_project_issue_relations: Permitir relacionar tickets entre projetos -label_user: Usuario -label_user_plural: Usuarios -label_user_new: Novo usuario +label_user: Usuário +label_user_plural: Usuários +label_user_new: Novo usuário label_project: Projeto label_project_new: Novo projeto label_project_plural: Projetos -label_project_all: All Projects -label_project_latest: Ultimos projetos -label_issue: Tarefa -label_issue_new: Nova tarefa -label_issue_plural: Tarefas -label_issue_view_all: Ver todas as tarefas +label_project_all: Todos os projetos +label_project_latest: Últimos projetos +label_issue: Ticket +label_issue_new: Novo ticket +label_issue_plural: Tickets +label_issue_view_all: Ver todos os tickets label_document: Documento label_document_new: Novo documento label_document_plural: Documentos -label_role: Regra -label_role_plural: Regras -label_role_new: Nova regra -label_role_and_permissions: Regras e permissoes +label_role: Papel +label_role_plural: Papéis +label_role_new: Novo papel +label_role_and_permissions: Papéis e permissões label_member: Membro label_member_new: Novo membro label_member_plural: Membros -label_tracker: Tipo -label_tracker_plural: Tipos +label_tracker: Tipo de ticket +label_tracker_plural: Tipos de ticket label_tracker_new: Novo tipo label_workflow: Workflow -label_issue_status: Status da tarefa -label_issue_status_plural: Status das tarefas +label_issue_status: Status do ticket +label_issue_status_plural: Status dos tickets label_issue_status_new: Novo status -label_issue_category: Categoria de tarefa -label_issue_category_plural: Categorias de tarefa +label_issue_category: Categoria de ticket +label_issue_category_plural: Categorias de tickets label_issue_category_new: Nova categoria label_custom_field: Campo personalizado -label_custom_field_plural: Campos personalizado +label_custom_field_plural: Campos personalizados label_custom_field_new: Novo campo personalizado -label_enumerations: Enumeracao -label_enumeration_new: Novo valor -label_information: Informacao -label_information_plural: Informacoes -label_please_login: Efetue login +label_enumerations: 'Tipos & Categorias' +label_enumeration_new: Novo +label_information: Informação +label_information_plural: Informações +label_please_login: Efetue o login label_register: Registre-se -label_password_lost: Perdi a senha -label_home: Pagina inicial -label_my_page: Minha pagina +label_password_lost: Perdi minha senha +label_home: Página inicial +label_my_page: Minha página label_my_account: Minha conta label_my_projects: Meus projetos -label_administration: Administracao -label_login: Login -label_logout: Logout +label_administration: Administração +label_login: Entrar +label_logout: Sair label_help: Ajuda -label_reported_issues: Tarefas reportadas -label_assigned_to_me_issues: Tarefas atribuidas a mim -label_last_login: Utima conexao -label_last_updates: Ultima alteracao -label_last_updates_plural: %d Ultimas alteracoes +label_reported_issues: Tickets reportados +label_assigned_to_me_issues: Meus tickets +label_last_login: Última conexao +label_last_updates: Última alteração +label_last_updates_plural: %d Últimas alterações label_registered_on: Registrado em label_activity: Atividade label_new: Novo -label_logged_as: Logado como +label_logged_as: "Acessando como:" label_environment: Ambiente -label_authentication: Autenticacao -label_auth_source: Modo de autenticacao -label_auth_source_new: Novo modo de autenticacao -label_auth_source_plural: Modos de autenticacao +label_authentication: Autenticação +label_auth_source: Modo de autenticação +label_auth_source_new: Novo modo de autenticação +label_auth_source_plural: Modos de autenticação label_subproject_plural: Sub-projetos -label_min_max_length: Tamanho min-max +label_min_max_length: Tamanho mín-máx label_list: Lista label_date: Data label_integer: Inteiro @@ -262,169 +263,169 @@ label_attribute: Atributo label_attribute_plural: Atributos label_download: %d Download label_download_plural: %d Downloads -label_no_data: Sem dados para mostrar -label_change_status: Mudar status -label_history: Historico +label_no_data: Nenhuma informação disponível +label_change_status: Alterar status +label_history: Histórico label_attachment: Arquivo label_attachment_new: Novo arquivo label_attachment_delete: Apagar arquivo label_attachment_plural: Arquivos -label_report: Relatorio -label_report_plural: Relatorio -label_news: Noticias -label_news_new: Adicionar noticias -label_news_plural: Noticias -label_news_latest: Ultimas noticias -label_news_view_all: Ver todas as noticias -label_change_log: Change log -label_settings: Ajustes -label_overview: Visao geral -label_version: Versao -label_version_new: Nova versao -label_version_plural: Versoes -label_confirmation: Confirmacao +label_report: Relatório +label_report_plural: Relatório +label_news: Notícia +label_news_new: Adicionar notícias +label_news_plural: Notícias +label_news_latest: Últimas notícias +label_news_view_all: Ver todas as notícias +label_change_log: Registro de alterações +label_settings: Configurações +label_overview: Visão geral +label_version: Versão +label_version_new: Nova versão +label_version_plural: Versões +label_confirmation: Confirmação label_export_to: Exportar para label_read: Ler... -label_public_projects: Projetos publicos +label_public_projects: Projetos públicos label_open_issues: Aberto -label_open_issues_plural: Abertos +label_open_issues_plural: Abertos label_closed_issues: Fechado label_closed_issues_plural: Fechados label_total: Total -label_permissions: Permissoes +label_permissions: Permissões label_current_status: Status atual label_new_statuses_allowed: Novo status permitido label_all: todos label_none: nenhum -label_next: Proximo +label_next: Próximo label_previous: Anterior label_used_by: Usado por label_details: Detalhes label_add_note: Adicionar nota -label_per_page: Por pagina -label_calendar: Calendario -label_months_from: Meses de +label_per_page: Por página +label_calendar: Calendário +label_months_from: meses a partir de label_gantt: Gantt label_internal: Interno -label_last_changes: utlimas %d mudancas -label_change_view_all: Mostrar todas as mudancas -label_personalize_page: Personalizar esta pagina -label_comment: Comentario -label_comment_plural: Comentarios -label_comment_add: Adicionar comentario -label_comment_added: Comentario adicionado -label_comment_delete: Apagar comentario +label_last_changes: últimas %d alteraçoes +label_change_view_all: Mostrar todas as alteraçoes +label_personalize_page: Personalizar esta página +label_comment: Comentário +label_comment_plural: Comentários +label_comment_add: Adicionar comentário +label_comment_added: Comentário adicionado +label_comment_delete: Apagar comentário label_query: Consulta personalizada label_query_plural: Consultas personalizadas label_query_new: Nova consulta label_filter_add: Adicionar filtro label_filter_plural: Filtros -label_equals: e -label_not_equals: nao e -label_in_less_than: e maior que -label_in_more_than: e menor que +label_equals: é +label_not_equals: não é +label_in_less_than: é maior que +label_in_more_than: é menor que label_in: em label_today: hoje -label_this_week: this week +label_this_week: esta semana label_less_than_ago: faz menos de label_more_than_ago: faz mais de -label_ago: dias atras -label_contains: contem -label_not_contains: nao contem +label_ago: dias atrás +label_contains: contém +label_not_contains: não contem label_day_plural: dias -label_repository: Repository -label_browse: Browse -label_modification: %d change -label_modification_plural: %d changes -label_revision: Revision -label_revision_plural: Revisions -label_added: added -label_modified: modified -label_deleted: deleted -label_latest_revision: Latest revision -label_latest_revision_plural: Latest revisions -label_view_revisions: View revisions -label_max_size: Maximum size +label_repository: Repositório +label_browse: Procurar +label_modification: %d alteração +label_modification_plural: %d alterações +label_revision: Revisão +label_revision_plural: Revisões +label_added: adicionado +label_modified: modificado +label_deleted: excluído +label_latest_revision: Última revisão +label_latest_revision_plural: Últimas revisões +label_view_revisions: Visualizar revisões +label_max_size: Tamanho máximo label_on: 'em' -label_sort_highest: Mover para o inicio +label_sort_highest: Mover para o início label_sort_higher: Mover para cima label_sort_lower: Mover para baixo label_sort_lowest: Mover para o fim -label_roadmap: Roadmap -label_roadmap_due_in: Due in -label_roadmap_overdue: %s late -label_roadmap_no_issues: Sem tarefas para essa versao +label_roadmap: Planejamento +label_roadmap_due_in: Previsão em +label_roadmap_overdue: %s atrasado +label_roadmap_no_issues: Sem tickets para esta versão label_search: Busca label_result_plural: Resultados label_all_words: Todas as palavras label_wiki: Wiki -label_wiki_edit: Wiki edit -label_wiki_edit_plural: Wiki edits -label_wiki_page: Wiki page -label_wiki_page_plural: Wiki pages -label_index_by_title: Index by title -label_index_by_date: Index by date -label_current_version: Versao atual -label_preview: Previa +label_wiki_edit: Editar Wiki +label_wiki_edit_plural: Edições Wiki +label_wiki_page: Página Wiki +label_wiki_page_plural: Páginas Wiki +label_index_by_title: Ãndice por título +label_index_by_date: Ãndice por data +label_current_version: Versão atual +label_preview: Pré-visualizar label_feed_plural: Feeds -label_changes_details: Detalhes de todas as mudancas -label_issue_tracking: Tarefas +label_changes_details: Detalhes de todas as alterações +label_issue_tracking: Tickets label_spent_time: Tempo gasto label_f_hour: %.2f hora label_f_hour_plural: %.2f horas label_time_tracking: Tempo trabalhado -label_change_plural: Mudancas -label_statistics: Estatisticas -label_commits_per_month: Commits por mes +label_change_plural: Mudanças +label_statistics: Estatísticas +label_commits_per_month: Commits por mês label_commits_per_author: Commits por autor -label_view_diff: Ver diferencas +label_view_diff: Ver diferenças label_diff_inline: inline -label_diff_side_by_side: side by side -label_options: Opcoes +label_diff_side_by_side: lado a lado +label_options: Opções label_copy_workflow_from: Copiar workflow de -label_permissions_report: Relatorio de permissoes -label_watched_issues: Watched issues -label_related_issues: Related issues -label_applied_status: Applied status -label_loading: Loading... -label_relation_new: New relation -label_relation_delete: Delete relation -label_relates_to: related to -label_duplicates: duplicates -label_blocks: blocks -label_blocked_by: blocked by -label_precedes: precedes -label_follows: follows -label_end_to_start: end to start -label_end_to_end: end to end -label_start_to_start: start to start -label_start_to_end: start to end -label_stay_logged_in: Stay logged in -label_disabled: disabled -label_show_completed_versions: Show completed versions -label_me: me -label_board: Forum -label_board_new: New forum -label_board_plural: Forums -label_topic_plural: Topics -label_message_plural: Messages -label_message_last: Last message -label_message_new: New message -label_reply_plural: Replies -label_send_information: Send account information to the user -label_year: Year -label_month: Month -label_week: Week -label_date_from: From -label_date_to: To -label_language_based: Language based -label_sort_by: Sort by %s -label_send_test_email: Send a test email -label_feeds_access_key_created_on: RSS access key created %s ago -label_module_plural: Modules -label_added_time_by: Added by %s %s ago -label_updated_time: Updated %s ago -label_jump_to_a_project: Jump to a project... +label_permissions_report: Relatório de permissões +label_watched_issues: Tickes acompanhados +label_related_issues: Tickets relacionados +label_applied_status: Status aplicado +label_loading: Carregando... +label_relation_new: Nova relação +label_relation_delete: Excluir relação +label_relates_to: relacionado a +label_duplicates: duplicado de +label_blocks: bloqueia +label_blocked_by: bloqueado por +label_precedes: precede +label_follows: segue +label_end_to_start: fim para o início +label_end_to_end: fim para fim +label_start_to_start: início para início +label_start_to_end: início para fim +label_stay_logged_in: Permanecer logado +label_disabled: desabilitado +label_show_completed_versions: Exibir versões completas +label_me: eu +label_board: Fórum +label_board_new: Novo fórum +label_board_plural: Fóruns +label_topic_plural: Tópicos +label_message_plural: Mensagens +label_message_last: Última mensagem +label_message_new: Nova mensagem +label_reply_plural: Respostas +label_send_information: Enviar informação de conta para o usuário +label_year: Ano +label_month: Mês +label_week: Semana +label_date_from: De +label_date_to: Para +label_language_based: Com base no idioma +label_sort_by: Ordenar por %s +label_send_test_email: Enviar um email de teste +label_feeds_access_key_created_on: chave de acesso RSS criada %s atrás +label_module_plural: Módulos +label_added_time_by: Adicionado por %s %s atrás +label_updated_time: Atualizado %s atrás +label_jump_to_a_project: Ir para o projeto... button_login: Login button_submit: Enviar @@ -436,7 +437,7 @@ button_create: Criar button_test: Testar button_edit: Editar button_add: Adicionar -button_change: Mudar +button_change: Alterar button_apply: Aplicar button_clear: Limpar button_lock: Bloquear @@ -450,59 +451,59 @@ button_cancel: Cancelar button_activate: Ativar button_sort: Ordenar button_log_time: Tempo de trabalho -button_rollback: Voltar para esta versao -button_watch: Watch -button_unwatch: Unwatch -button_reply: Reply -button_archive: Archive -button_unarchive: Unarchive -button_reset: Reset -button_rename: Rename +button_rollback: Voltar para esta versão +button_watch: Acompanhar +button_unwatch: Não Acompanhar +button_reply: Responder +button_archive: Arquivar +button_unarchive: Desarquivar +button_reset: Redefinir +button_rename: Renomear status_active: ativo status_registered: registrado status_locked: bloqueado -text_select_mail_notifications: Selecionar acoes para ser enviado uma notificacao por email -text_regexp_info: eg. ^[A-Z0-9]+$ -text_min_max_length_info: 0 siginifica sem restricao -text_project_destroy_confirmation: Voce tem certeza que deseja deletar este projeto e todas os dados relacionados? +text_select_mail_notifications: Selecionar ações para ser enviado uma notificação por email +text_regexp_info: ex. ^[A-Z0-9]+$ +text_min_max_length_info: 0 siginifica sem restrição +text_project_destroy_confirmation: Você tem certeza que deseja excluir este projeto e todos os dados relacionados? text_workflow_edit: Selecione uma regra e um tipo de tarefa para editar o workflow -text_are_you_sure: Voce tem certeza ? +text_are_you_sure: Você tem certeza? text_journal_changed: alterado de %s para %s text_journal_set_to: setar para %s text_journal_deleted: apagado -text_tip_task_begin_day: tarefa comeca neste dia +text_tip_task_begin_day: tarefa inicia neste dia text_tip_task_end_day: tarefa termina neste dia -text_tip_task_begin_end_day: tarefa comeca e termina neste dia -text_project_identifier_info: 'Letras minusculas (a-z), numeros e tracos permitido.
    Uma vez salvo, o identificador nao pode ser mudado.' -text_caracters_maximum: %d maximo de caracteres +text_tip_task_begin_end_day: tarefa inicia e termina neste dia +text_project_identifier_info: 'Letras minúsculas (a-z), números e traços permitidos.
    Uma vez salvo, o identificador não pode ser alterado.' +text_caracters_maximum: máximo %d caracteres text_length_between: Tamanho entre %d e %d caracteres. text_tracker_no_workflow: Sem workflow definido para este tipo. -text_unallowed_characters: Unallowed characters -text_comma_separated: Multiple values allowed (comma separated). -text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages -text_issue_added: Tarefa %s foi incluída (by %s). -text_issue_updated: Tarefa %s foi alterada (by %s). -text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ? -text_issue_category_destroy_question: Some issues (%d) are assigned to this category. What do you want to do ? -text_issue_category_destroy_assignments: Remove category assignments -text_issue_category_reassign_to: Reassing issues to this category +text_unallowed_characters: Caracteres não permitidos +text_comma_separated: Múltiplos valores são permitidos (separados por vírgula). +text_issues_ref_in_commit_messages: Referenciando e fixando tickets nas mensagens de commit +text_issue_added: Tarefa %s foi incluída (por %s). +text_issue_updated: Tarefa %s foi alterada (por %s). +text_wiki_destroy_confirmation: Você tem certeza que deseja excluir este wiki e todo o seu conteúdo? +text_issue_category_destroy_question: Alguns tickets (%d) estão atribuídos a esta categoria. O que você deseja fazer? +text_issue_category_destroy_assignments: Remover atribuições da categoria +text_issue_category_reassign_to: Redefinir tickets para esta categoria -default_role_manager: Analista de Negocio ou Gerente de Projeto +default_role_manager: Gerente default_role_developper: Desenvolvedor -default_role_reporter: Analista de Suporte -default_tracker_bug: Bug -default_tracker_feature: Implementacao +default_role_reporter: Informante +default_tracker_bug: Problema +default_tracker_feature: Implementação default_tracker_support: Suporte default_issue_status_new: Novo -default_issue_status_assigned: Atribuido +default_issue_status_assigned: Atribuído default_issue_status_resolved: Resolvido default_issue_status_feedback: Feedback default_issue_status_closed: Fechado default_issue_status_rejected: Rejeitado -default_doc_category_user: Documentacao do usuario -default_doc_category_tech: Documentacao do tecnica +default_doc_category_user: Documentação do usuário +default_doc_category_tech: Documentação técnica default_priority_low: Baixo default_priority_normal: Normal default_priority_high: Alto @@ -514,107 +515,124 @@ default_activity_development: Desenvolvimento enumeration_issue_priorities: Prioridade das tarefas enumeration_doc_categories: Categorias de documento enumeration_activities: Atividades (time tracking) -label_file_plural: Files +label_file_plural: Arquivos label_changeset_plural: Changesets -field_column_names: Columns -label_default_columns: Default columns -setting_issue_list_default_columns: Default columns displayed on the issue list -setting_repositories_encodings: Repositories encodings -notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." -label_bulk_edit_selected_issues: Bulk edit selected issues -label_no_change_option: (No change) -notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." -label_theme: Theme -label_default: Default -label_search_titles_only: Search titles only -label_nobody: nobody -button_change_password: Change password -text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." -label_user_mail_option_selected: "For any event on the selected projects only..." -label_user_mail_option_all: "For any event on all my projects" -label_user_mail_option_none: "Only for things I watch or I'm involved in" -setting_emails_footer: Emails footer -label_float: Float -button_copy: Copy -mail_body_account_information_external: You can use your "%s" account to log in. -mail_body_account_information: Your account information -setting_protocol: Protocol -label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" -setting_time_format: Time format -label_registration_activation_by_email: account activation by email -mail_subject_account_activation_request: %s account activation request -mail_body_account_activation_request: 'A new user (%s) has registered. His account his pending your approval:' -label_registration_automatic_activation: automatic account activation -label_registration_manual_activation: manual account activation -notice_account_pending: "Your account was created and is now pending administrator approval." -field_time_zone: Time zone -text_caracters_minimum: Must be at least %d characters long. -setting_bcc_recipients: Blind carbon copy recipients (bcc) -button_annotate: Annotate -label_issues_by: Issues by %s -field_searchable: Searchable -label_display_per_page: 'Per page: %s' -setting_per_page_options: Objects per page options -label_age: Age -notice_default_data_loaded: Default configuration successfully loaded. -text_load_default_configuration: Load the default configuration -text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." -error_can_t_load_default_data: "Default configuration could not be loaded: %s" -button_update: Update -label_change_properties: Change properties -label_general: General -label_repository_plural: Repositories -label_associated_revisions: Associated revisions -setting_user_format: Users display format -text_status_changed_by_changeset: Applied in changeset %s. -label_more: More -text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?' +field_column_names: Colunas +label_default_columns: Colunas padrão +setting_issue_list_default_columns: Colunas padrão visíveis na lista de tickets +setting_repositories_encodings: Codificação dos repositórios +notice_no_issue_selected: "Nenhum ticket está selecionado! Por favor, marque os tickets que você deseja alterar." +label_bulk_edit_selected_issues: Edição em massa dos tickets selecionados. +label_no_change_option: (Sem alteração) +notice_failed_to_save_issues: "Problema ao salvar %d ticket(s) no %d selecionado: %s." +label_theme: Tema +label_default: Padrão +label_search_titles_only: Pesquisar somente títulos +label_nobody: ninguém +button_change_password: Alterar senha +text_user_mail_option: "Para projetos não selecionados, você somente receberá notificações sobre o que você acompanha ou está envolvido (ex. tickets que você é autor ou está atribuído)" +label_user_mail_option_selected: "Para qualquer evento somente no(s) projeto(s) selecionado(s)..." +label_user_mail_option_all: "Para qualquer evento em todos os meus projetos" +label_user_mail_option_none: "Somente eventos que eu acompanho ou estou envolvido" +setting_emails_footer: Rodapé dos emails +label_float: Flutuante +button_copy: Copiar +mail_body_account_information_external: Você pode usar sua conta "%s" para entrar. +mail_body_account_information: Informações de sua conta +setting_protocol: Protocolo +label_user_mail_no_self_notified: "Eu não desejo ser notificado de minhas próprias modificações" +setting_time_format: Formato de data +label_registration_activation_by_email: ativação de conta por email +mail_subject_account_activation_request: %s requisição de ativação de conta +mail_body_account_activation_request: 'Um novo usuário (%s) se registrou. A conta está aguardando sua aprovação:' +label_registration_automatic_activation: ativação automática de conta +label_registration_manual_activation: ativação manual de conta +notice_account_pending: "Sua conta foi criada e está aguardando aprovação do administrador." +field_time_zone: Fuso-horário +text_caracters_minimum: Precisa ter ao menos %d caracteres. +setting_bcc_recipients: Destinatários com cópia oculta (cco) +button_annotate: Anotar +label_issues_by: Tickets por %s +field_searchable: Pesquisável +label_display_per_page: 'Por página: %s' +setting_per_page_options: Opções de itens por página +notice_default_data_loaded: Configuração padrão carregada com sucesso. +text_load_default_configuration: Carregar a configuração padrão +text_no_configuration_data: "Os Papéis, tipos de tickets, status de tickets e workflows não foram configurados ainda.\nÉ altamente recomendado carregar as configurações padrão. Você poderá modificar estas configurações assim que carregadas." +error_can_t_load_default_data: "Configuração padrão não pôde ser carregada: %s" +button_update: Atualizar +label_change_properties: Alterar propriedades +label_general: Geral +label_repository_plural: Repositórios +label_associated_revisions: Revisões associadas +setting_user_format: Formato de visualização dos usuários +text_status_changed_by_changeset: Aplicado no changeset %s. +label_more: Mais +text_issues_destroy_confirmation: 'Você tem certeza que deseja excluir o(s) ticket(s) selecionado(s)?' label_scm: SCM -text_select_project_modules: 'Select modules to enable for this project:' -label_issue_added: Issue added -label_issue_updated: Issue updated -label_document_added: Document added -label_message_posted: Message added -label_file_added: File added -label_news_added: News added -project_module_boards: Boards -project_module_issue_tracking: Issue tracking +text_select_project_modules: 'Selecione módulos para habilitar para este projeto:' +label_issue_added: Ticket adicionado +label_issue_updated: Ticket atualizado +label_document_added: Documento adicionado +label_message_posted: Mensagem enviada +label_file_added: Arquivo adicionado +label_news_added: Notícia adicionada +project_module_boards: Fóruns +project_module_issue_tracking: Gerenciamento de Tickets project_module_wiki: Wiki -project_module_files: Files -project_module_documents: Documents -project_module_repository: Repository -project_module_news: News -project_module_time_tracking: Time tracking -text_file_repository_writable: File repository writable -text_default_administrator_account_changed: Default administrator account changed -text_rmagick_available: RMagick available (optional) -button_configure: Configure +project_module_files: Arquivos +project_module_documents: Documentos +project_module_repository: Repositório +project_module_news: Notícias +project_module_time_tracking: Gerenciamento de tempo +text_file_repository_writable: Repositório de arquivos gravável +text_default_administrator_account_changed: Conta de administrador padrão modificada +text_rmagick_available: RMagick disponível (opcional) +button_configure: Configuração label_plugins: Plugins -label_ldap_authentication: LDAP authentication +label_ldap_authentication: autenticação LDAP label_downloads_abbr: D/L -label_this_month: this month -label_last_n_days: last %d days -label_all_time: all time -label_this_year: this year -label_date_range: Date range -label_last_week: last week -label_yesterday: yesterday -label_last_month: last month -label_add_another_file: Add another file -label_optional_description: Optional description -text_destroy_time_entries_question: %.02f hours were reported on the issues you are about to delete. What do you want to do ? -error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' -text_assign_time_entries_to_project: Assign reported hours to the project -text_destroy_time_entries: Delete reported hours -text_reassign_time_entries: 'Reassign reported hours to this issue:' -setting_activity_days_default: Days displayed on project activity -label_chronological_order: In chronological order -field_comments_sorting: Display comments -label_reverse_chronological_order: In reverse chronological order -label_preferences: Preferences -setting_display_subprojects_issues: Display subprojects issues on main projects by default -label_overall_activity: Overall activity -setting_default_projects_public: New projects are public by default -error_scm_annotate: "The entry does not exist or can not be annotated." -label_planning: Planning -text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_this_month: este mês +label_last_n_days: últimos %d dias +label_all_time: todo o tempo +label_this_year: este ano +label_date_range: Intervalo de datas +label_last_week: última semana +label_yesterday: ontem +label_last_month: último mês +label_add_another_file: Adicionar outro arquivo +label_optional_description: Descrição opcional +text_destroy_time_entries_question: %.02f horas foram reportadas neste ticket que você está excluindo. O que você deseja fazer? +error_issue_not_found_in_project: 'O ticket não foi encontrado ou não pertence a este projeto' +text_assign_time_entries_to_project: Atribuir horas reportadas para o projeto +text_destroy_time_entries: Excluir horas reportadas +text_reassign_time_entries: 'Redefinir horas reportadas para este ticket:' +setting_activity_days_default: Dias visualizados na atividade do projeto +label_chronological_order: Em ordem cronológica +field_comments_sorting: Visualizar comentários +label_reverse_chronological_order: Em order cronológica reversa +label_preferences: Preferências +setting_display_subprojects_issues: Visualizar tickets dos subprojetos nos projetos principais por padrão +label_overall_activity: Atividade geral +setting_default_projects_public: Novos projetos são públicos por padrão +error_scm_annotate: "Esta entrada não existe ou não pode ser anotada." +label_planning: Planejamento +text_subprojects_destroy_warning: 'Seu(s) subprojeto(s): %s também serão excluídos.' +label_age: Age +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/pt.yml b/groups/lang/pt.yml index 6f51c8ed2..5562ca4ac 100644 --- a/groups/lang/pt.yml +++ b/groups/lang/pt.yml @@ -48,6 +48,7 @@ general_text_no: 'não' general_text_yes: 'sim' general_lang_name: 'Português' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_day_names: Segunda,Terça,Quarta,Quinta,Sexta,Sábado,Domingo @@ -489,7 +490,7 @@ text_issue_category_destroy_question: Some issues (%d) are assigned to this cate text_issue_category_destroy_assignments: Remove category assignments text_issue_category_reassign_to: Reassing issues to this category -default_role_manager: Analista de Negócio ou Gerente de Projeto +default_role_manager: Gerente de Projeto default_role_developper: Desenvolvedor default_role_reporter: Analista de Suporte default_tracker_bug: Bug @@ -618,3 +619,20 @@ setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/ro.yml b/groups/lang/ro.yml index 59edfeb70..5bb49ecec 100644 --- a/groups/lang/ro.yml +++ b/groups/lang/ro.yml @@ -48,6 +48,7 @@ general_text_no: 'nu' general_text_yes: 'da' general_lang_name: 'Română' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_day_names: Luni,Marti,Miercuri,Joi,Vineri,Sambata,Duminica @@ -497,7 +498,7 @@ default_issue_status_new: Nou default_issue_status_assigned: Atribuit default_issue_status_resolved: Rezolvat default_issue_status_feedback: Feedback -default_issue_status_closed: Rezolvat +default_issue_status_closed: Closed default_issue_status_rejected: Respins default_doc_category_user: Documentatie default_doc_category_tech: Documentatie tehnica @@ -618,3 +619,20 @@ setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/ru.yml b/groups/lang/ru.yml index f69009847..01cdcd478 100644 --- a/groups/lang/ru.yml +++ b/groups/lang/ru.yml @@ -48,6 +48,7 @@ general_text_no: 'Ðет' general_text_yes: 'Да' general_lang_name: 'Russian (РуÑÑкий)' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: UTF-8 general_pdf_encoding: UTF-8 general_day_names: Понедельник,Вторник,Среда,Четверг,ПÑтница,Суббота,ВоÑкреÑенье @@ -108,7 +109,7 @@ field_author: Ðвтор field_created_on: Создано field_updated_on: Обновлено field_field_format: Формат -field_is_for_all: Ð”Ð»Ñ Ð²Ñех форматов +field_is_for_all: Ð”Ð»Ñ Ð²Ñех проектов field_possible_values: Возможные Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ field_regexp: РегулÑрное выражение field_min_length: ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° @@ -163,7 +164,7 @@ field_comments: Комментарий field_url: URL field_start_page: Ð¡Ñ‚Ð°Ñ€Ñ‚Ð¾Ð²Ð°Ñ Ñтраница field_subproject: Подпроект -field_hours: ЧаÑ(а)(ов) +field_hours: ЧаÑ(а,ов) field_activity: ДеÑтельноÑть field_spent_on: Дата field_identifier: Ун. идентификатор @@ -239,9 +240,9 @@ label_issue_status_new: Ðовый ÑÑ‚Ð°Ñ‚ÑƒÑ label_issue_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸ label_issue_category_plural: Категории задачи label_issue_category_new: ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ -label_custom_field: Поле клиента -label_custom_field_plural: ÐŸÐ¾Ð»Ñ ÐºÐ»Ð¸ÐµÐ½Ñ‚Ð° -label_custom_field_new: Ðовое поле клиента +label_custom_field: ÐаÑтраиваемое поле +label_custom_field_plural: ÐаÑтраиваемые Ð¿Ð¾Ð»Ñ +label_custom_field_new: Ðовое наÑтраиваемое поле label_enumerations: Справочники label_enumeration_new: Ðовое значение label_information: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ @@ -276,7 +277,7 @@ label_min_max_length: ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ - МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½ label_list: СпиÑок label_date: Дата label_integer: Целый -label_float: Свободный +label_float: С плавающей точкой label_boolean: ЛогичеÑкий label_string: ТекÑÑ‚ label_text: Длинный текÑÑ‚ @@ -332,13 +333,13 @@ label_internal: Внутренний label_last_changes: менее %d изменений label_change_view_all: ПроÑмотреть вÑе Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ label_personalize_page: ПерÑонализировать данную Ñтраницу -label_comment: Комментировать +label_comment: комментарий label_comment_plural: Комментарии label_comment_add: ОÑтавить комментарий label_comment_added: Добавленный комментарий label_comment_delete: Удалить комментарии -label_query: Ð—Ð°Ð¿Ñ€Ð¾Ñ ÐºÐ»Ð¸ÐµÐ½Ñ‚Ð° -label_query_plural: ЗапроÑÑ‹ клиентов +label_query: Сохраненный Ð·Ð°Ð¿Ñ€Ð¾Ñ +label_query_plural: Сохраненные запроÑÑ‹ label_query_new: Ðовый Ð·Ð°Ð¿Ñ€Ð¾Ñ label_filter_add: Добавить фильтр label_filter_plural: Фильтры @@ -406,7 +407,7 @@ label_diff_side_by_side: Ñ€Ñдом label_options: Опции label_copy_workflow_from: Скопировать поÑледовательноÑть дейÑтвий из label_permissions_report: Отчет о правах доÑтупа -label_watched_issues: ПроÑмотренные задачи +label_watched_issues: ПроÑматриваемые задачи label_related_issues: СвÑзанные задачи label_applied_status: Применимый ÑÑ‚Ð°Ñ‚ÑƒÑ label_loading: Загрузка... @@ -461,7 +462,7 @@ label_user_mail_option_selected: "Ð”Ð»Ñ Ð²Ñех Ñобытий только в label_user_mail_option_none: "Только Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, что Ñ Ð¿Ñ€Ð¾Ñматриваю или в чем Ñ ÑƒÑ‡Ð°Ñтвую" label_user_mail_no_self_notified: "Ðе извещать об изменениÑÑ… которые Ñ Ñделал Ñам" label_registration_activation_by_email: Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ ÑƒÑ‡ÐµÑ‚Ð½Ñ‹Ñ… запиÑей по email -label_registration_automatic_activation: автоматичеÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ ÑƒÑ‡Ñ‚ÐµÐ½Ñ‹Ñ… запиÑей +label_registration_automatic_activation: автоматичеÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ ÑƒÑ‡ÐµÑ‚Ð½Ñ‹Ñ… запиÑей label_registration_manual_activation: активировать учетные запиÑи вручную label_age: ВозраÑÑ‚ label_change_properties: Изменить ÑвойÑтва @@ -604,7 +605,7 @@ label_date_range: временной интервал label_last_week: поÑледнÑÑ Ð½ÐµÐ´ÐµÐ»ÑŽ label_yesterday: вчера label_last_month: поÑледний меÑÑц -label_add_another_file: Добавить ещё один файл +label_add_another_file: Добавить ещё один файл label_optional_description: ОпиÑание (выборочно) text_destroy_time_entries_question: Ð’Ñ‹ ÑобираетеÑÑŒ удалить %.02f чаÑа(ов) прикрепленных за Ñтой задачей. error_issue_not_found_in_project: Задача не была найдена или не прикреплена к Ñтому проекту @@ -621,4 +622,21 @@ label_overall_activity: Ð¡Ð²Ð¾Ð´Ð½Ð°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾Ñть setting_default_projects_public: Ðовые проекты ÑвлÑÑŽÑ‚ÑÑ Ð¿ÑƒÐ±Ð»Ð¸Ñ‡Ð½Ñ‹Ð¼Ð¸ error_scm_annotate: "Данные отÑутÑтвуют или не могут быть подпиÑаны." label_planning: Планирование -text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +text_subprojects_destroy_warning: 'Подпроекты: %s также будут удалены.' +label_and_its_subprojects: %s и вÑе подпроекты +mail_body_reminder: "%d назначенных на Ð²Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡ на Ñледующие %d дней:" +mail_subject_reminder: "%d назначенных на Ð²Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡ в ближайшие дни" +text_user_wrote: '%s напиÑал:' +label_duplicated_by: дублируетÑÑ +setting_enabled_scm: ЗадейÑтвовать SCM +text_enumeration_category_reassign_to: 'Ðазначить им Ñледующее значение:' +text_enumeration_destroy_question: '%d объект(а,ов) ÑвÑзаны Ñ Ñтим значением.' +label_incoming_emails: Приём Ñообщений +label_generate_key: Сгенерировать ключ +setting_mail_handler_api_enabled: Включить веб-ÑÐµÑ€Ð²Ð¸Ñ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñщих Ñообщений +setting_mail_handler_api_key: API ключ +text_email_delivery_not_configured: "Параметры работы Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð²Ñ‹Ð¼ Ñервером не наÑтроены и Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ email не активна.\nÐаÑтроить параметры Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ SMTP Ñервера вы можете в файле config/email.yml. Ð”Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ перезапуÑтите приложение." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/sr.yml b/groups/lang/sr.yml index d9869c362..566a46d9f 100644 --- a/groups/lang/sr.yml +++ b/groups/lang/sr.yml @@ -48,6 +48,7 @@ general_text_no: 'ne' general_text_yes: 'da' general_lang_name: 'Srpski' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_day_names: Ponedeljak, Utorak, Sreda, Äetvrtak, Petak, Subota, Nedelja @@ -619,3 +620,20 @@ setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/sv.yml b/groups/lang/sv.yml index c0f691230..4cb1f073b 100644 --- a/groups/lang/sv.yml +++ b/groups/lang/sv.yml @@ -48,6 +48,7 @@ general_text_no: 'nej' general_text_yes: 'ja' general_lang_name: 'Svenska' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_day_names: MÃ¥ndag,Tisdag,Onsdag,Torsdag,Fredag,Lördag,Söndag @@ -619,3 +620,20 @@ setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/th.yml b/groups/lang/th.yml new file mode 100644 index 000000000..2c3977de2 --- /dev/null +++ b/groups/lang/th.yml @@ -0,0 +1,641 @@ +_gloc_rule_default: '|n| n==1 ? "" : "_plural" ' + +actionview_datehelper_select_day_prefix: +actionview_datehelper_select_month_names: มà¸à¸£à¸²à¸„ม,à¸à¸¸à¸¡à¸ à¸²à¸žà¸±à¸™à¸˜à¹Œ,มีนาคม,เมษายน,พฤษภาคม,มิถุนายน,à¸à¸£à¸à¸Žà¸²à¸„ม,สิงหาคม,à¸à¸±à¸™à¸¢à¸²à¸¢à¸™,ตุลาคม,พฤศจิà¸à¸²à¸¢à¸™,ธันวาคม +actionview_datehelper_select_month_names_abbr: ม.ค.,à¸.พ.,มี.ค.,เม.ย.,พ.ค.,มิ.ย.,à¸.ค.,ส.ค.,à¸.ย.,ต.ค.,พ.ย.,ธ.ค. +actionview_datehelper_select_month_prefix: +actionview_datehelper_select_year_prefix: +actionview_datehelper_time_in_words_day: 1 วัน +actionview_datehelper_time_in_words_day_plural: %d วัน +actionview_datehelper_time_in_words_hour_about: ประมาณ 1 ชั่วโมง +actionview_datehelper_time_in_words_hour_about_plural: ประมาณ %d ชั่วโมง +actionview_datehelper_time_in_words_hour_about_single: ประมาณ 1 ชั่วโมง +actionview_datehelper_time_in_words_minute: 1 นาที +actionview_datehelper_time_in_words_minute_half: ครึ่งนาที +actionview_datehelper_time_in_words_minute_less_than: ไม่ถึงนาที +actionview_datehelper_time_in_words_minute_plural: %d นาที +actionview_datehelper_time_in_words_minute_single: 1 นาที +actionview_datehelper_time_in_words_second_less_than: ไม่ถึงวินาที +actionview_datehelper_time_in_words_second_less_than_plural: ไม่ถึง %d วินาที +actionview_instancetag_blank_option: à¸à¸£à¸¸à¸“าเลือภ+ +activerecord_error_inclusion: ไม่อยู่ในรายà¸à¸²à¸£ +activerecord_error_exclusion: ถูà¸à¸ªà¸‡à¸§à¸™à¹„ว้ +activerecord_error_invalid: ไม่ถูà¸à¸•้อง +activerecord_error_confirmation: พิมพ์ไม่เหมือนเดิม +activerecord_error_accepted: ต้องยอมรับ +activerecord_error_empty: ต้องเติม +activerecord_error_blank: ต้องเติม +activerecord_error_too_long: ยาวเà¸à¸´à¸™à¹„ป +activerecord_error_too_short: สั้นเà¸à¸´à¸™à¹„ป +activerecord_error_wrong_length: ความยาวไม่ถูà¸à¸•้อง +activerecord_error_taken: ถูà¸à¹ƒà¸Šà¹‰à¹„ปà¹à¸¥à¹‰à¸§ +activerecord_error_not_a_number: ไม่ใช่ตัวเลข +activerecord_error_not_a_date: ไม่ใช่วันที่ ที่ถูà¸à¸•้อง +activerecord_error_greater_than_start_date: ต้องมาà¸à¸à¸§à¹ˆà¸²à¸§à¸±à¸™à¹€à¸£à¸´à¹ˆà¸¡ +activerecord_error_not_same_project: ไม่ได้อยู่ในโครงà¸à¸²à¸£à¹€à¸”ียวà¸à¸±à¸™ +activerecord_error_circular_dependency: ความสัมพันธ์อ้างอิงเป็นวงà¸à¸¥à¸¡ + +general_fmt_age: %d ปี +general_fmt_age_plural: %d ปี +general_fmt_date: %%d/%%B/%%Y +general_fmt_datetime: %%d/%%B/%%Y %%H:%%M +general_fmt_datetime_short: %%d %%b, %%H:%%M +general_fmt_time: %%H:%%M +general_text_No: 'ไม่' +general_text_Yes: 'ใช่' +general_text_no: 'ไม่' +general_text_yes: 'ใช่' +general_lang_name: 'Thai (ไทย)' +general_csv_separator: ',' +general_csv_decimal_separator: '.' +general_csv_encoding: Windows-874 +general_pdf_encoding: cp874 +general_day_names: จันทร์,อังคาร,พุธ,พฤหัสบดี,ศุà¸à¸£à¹Œ,เสาร์,อาทิตย์ +general_first_day_of_week: '1' + +notice_account_updated: บัà¸à¸Šà¸µà¹„ด้ถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹à¸¥à¹‰à¸§. +notice_account_invalid_creditentials: ชื้ผู้ใช้หรือรหัสผ่านไม่ถูà¸à¸•้อง +notice_account_password_updated: รหัสได้ถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹à¸¥à¹‰à¸§. +notice_account_wrong_password: รหัสผ่านไม่ถูà¸à¸•้อง +notice_account_register_done: บัà¸à¸Šà¸µà¸–ูà¸à¸ªà¸£à¹‰à¸²à¸‡à¹à¸¥à¹‰à¸§. à¸à¸£à¸¸à¸“าเช็คเมล์ à¹à¸¥à¹‰à¸§à¸„ลิ๊à¸à¸—ี่ลิงค์ในอีเมล์เพื่อเปิดใช้บัà¸à¸Šà¸µ +notice_account_unknown_email: ไม่มีผู้ใช้ที่ใช้อีเมล์นี้. +notice_can_t_change_password: บัà¸à¸Šà¸µà¸™à¸µà¹‰à¹ƒà¸Šà¹‰à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตนจาà¸à¹à¸«à¸¥à¹ˆà¸‡à¸ à¸²à¸¢à¸™à¸­à¸. ไม่สามารถปลี่ยนรหัสผ่านได้. +notice_account_lost_email_sent: เราได้ส่งอีเมล์พร้อมวิธีà¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸£à¸«à¸±à¸µà¸ªà¸œà¹ˆà¸²à¸™à¹ƒà¸«à¸¡à¹ˆà¹ƒà¸«à¹‰à¸„ุณà¹à¸¥à¹‰à¸§ à¸à¸£à¸¸à¸“าเช็คเมล์. +notice_account_activated: บัà¸à¸Šà¸µà¸‚องคุณได้เปิดใช้à¹à¸¥à¹‰à¸§. ตอนนี้คุณสามารถเข้าสู่ระบบได้à¹à¸¥à¹‰à¸§. +notice_successful_create: สร้างเสร็จà¹à¸¥à¹‰à¸§. +notice_successful_update: ปรับปรุงเสร็จà¹à¸¥à¹‰à¸§. +notice_successful_delete: ลบเสร็จà¹à¸¥à¹‰à¸§. +notice_successful_connection: ติดต่อสำเร็จà¹à¸¥à¹‰à¸§. +notice_file_not_found: หน้าที่คุณต้องà¸à¸²à¸£à¸”ูไม่มีอยู่จริง หรือถูà¸à¸¥à¸šà¹„ปà¹à¸¥à¹‰à¸§. +notice_locking_conflict: ข้อมูลถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹‚ดยผู้ใช้คนอื่น. +notice_not_authorized: คุณไม่มีสิทธิเข้าถึงหน้านี้. +notice_email_sent: อีเมล์ได้ถูà¸à¸ªà¹ˆà¸‡à¸–ึง %s +notice_email_error: เà¸à¸´à¸”ความผิดพลาดขณะà¸à¸³à¸ªà¹ˆà¸‡à¸­à¸µà¹€à¸¡à¸¥à¹Œ (%s) +notice_feeds_access_key_reseted: RSS access key ของคุณถูภreset à¹à¸¥à¹‰à¸§. +notice_failed_to_save_issues: "%d ปัà¸à¸«à¸²à¸ˆà¸²à¸ %d ปัà¸à¸«à¸²à¸—ี่ถูà¸à¹€à¸¥à¸·à¸­à¸à¹„ม่สามารถจัดเà¸à¹‡à¸š: %s." +notice_no_issue_selected: "ไม่มีปัà¸à¸«à¸²à¸—ี่ถูà¸à¹€à¸¥à¸·à¸­à¸! à¸à¸£à¸¸à¸“าเลือà¸à¸›à¸±à¸à¸«à¸²à¸—ี่คุณต้องà¸à¸²à¸£à¹à¸à¹‰à¹„ข." +notice_account_pending: "บัà¸à¸Šà¸µà¸‚องคุณสร้างเสร็จà¹à¸¥à¹‰à¸§ ขณะนี้รอà¸à¸²à¸£à¸­à¸™à¸¸à¸¡à¸±à¸•ิจาà¸à¸œà¸¹à¹‰à¸šà¸£à¸´à¸«à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£." +notice_default_data_loaded: ค่าเริ่มต้นโหลดเสร็จà¹à¸¥à¹‰à¸§. + +error_can_t_load_default_data: "ค่าเริ่มต้นโหลดไม่สำเร็จ: %s" +error_scm_not_found: "ไม่พบรุ่นที่ต้องà¸à¸²à¸£à¹ƒà¸™à¹à¸«à¸¥à¹ˆà¸‡à¹€à¸à¹‡à¸šà¸•้นฉบับ." +error_scm_command_failed: "เà¸à¸´à¸”ความผิดพลาดในà¸à¸²à¸£à¹€à¸‚้าถึงà¹à¸«à¸¥à¹ˆà¸‡à¹€à¸à¹‡à¸šà¸•้นฉบับ: %s" +error_scm_annotate: "entry ไม่มีอยู่จริง หรือไม่สามารถเขียนหมายเหตุประà¸à¸­à¸š." +error_issue_not_found_in_project: 'ไม่พบปัà¸à¸«à¸²à¸™à¸µà¹‰ หรือปัà¸à¸«à¸²à¹„ม่ได้อยู่ในโครงà¸à¸²à¸£à¸™à¸µà¹‰' + +mail_subject_lost_password: รหัสผ่าน %s ของคุณ +mail_body_lost_password: 'คลิ๊à¸à¸—ี่ลิงค์ต่อไปนี้เพื่อเปลี่ยนรหัสผ่าน:' +mail_subject_register: เปิดบัà¸à¸Šà¸µ %s ของคุณ +mail_body_register: 'คลิ๊à¸à¸—ี่ลิงค์ต่อไปนี้เพื่อเปลี่ยนรหัสผ่าน:' +mail_body_account_information_external: คุณสามารถใช้บัà¸à¸Šà¸µ "%s" เพื่อเข้าสู่ระบบ. +mail_body_account_information: ข้อมูลบัà¸à¸Šà¸µà¸‚องคุณ +mail_subject_account_activation_request: à¸à¸£à¸¸à¸“าเปิดบัà¸à¸Šà¸µ %s +mail_body_account_activation_request: 'ผู้ใช้ใหม่ (%s) ได้ลงทะเบียน. บัà¸à¸Šà¸µà¸‚องเขาà¸à¸³à¸¥à¸±à¸‡à¸£à¸­à¸­à¸™à¸¸à¸¡à¸±à¸•ิ:' + +gui_validation_error: 1 ข้อผิดพลาด +gui_validation_error_plural: %d ข้อผิดพลาด + +field_name: ชื่อ +field_description: รายละเอียด +field_summary: สรุปย่อ +field_is_required: ต้องใส่ +field_firstname: ชื่อ +field_lastname: นามสà¸à¸¸à¸¥ +field_mail: อีเมล์ +field_filename: à¹à¸Ÿà¹‰à¸¡ +field_filesize: ขนาด +field_downloads: ดาวน์โหลด +field_author: ผู้à¹à¸•่ง +field_created_on: สร้าง +field_updated_on: ปรับปรุง +field_field_format: รูปà¹à¸šà¸š +field_is_for_all: สำหรับทุà¸à¹‚ครงà¸à¸²à¸£ +field_possible_values: ค่าที่เป็นไปได้ +field_regexp: Regular expression +field_min_length: สั้นสุด +field_max_length: ยาวสุด +field_value: ค่า +field_category: ประเภท +field_title: ชื่อเรื่อง +field_project: โครงà¸à¸²à¸£ +field_issue: ปัà¸à¸«à¸² +field_status: สถานะ +field_notes: บันทึภ+field_is_closed: ปัà¸à¸«à¸²à¸ˆà¸š +field_is_default: ค่าเริ่มต้น +field_tracker: à¸à¸²à¸£à¸•ิดตาม +field_subject: เรื่อง +field_due_date: วันครบà¸à¸³à¸«à¸™à¸” +field_assigned_to: มอบหมายให้ +field_priority: ความสำคัภ+field_fixed_version: รุ่น +field_user: ผู้ใช้ +field_role: บทบาท +field_homepage: หน้าà¹à¸£à¸ +field_is_public: สาธารณะ +field_parent: โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢à¸‚อง +field_is_in_chlog: ปัà¸à¸«à¸²à¹à¸ªà¸”งใน รายà¸à¸²à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ +field_is_in_roadmap: ปัà¸à¸«à¸²à¹à¸ªà¸”งใน à¹à¸œà¸™à¸‡à¸²à¸™ +field_login: ชื่อที่ใช้เข้าระบบ +field_mail_notification: à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ือนทางอีเมล์ +field_admin: ผู้บริหารจัดà¸à¸²à¸£ +field_last_login_on: เข้าระบบครั้งสุดท้าย +field_language: ภาษา +field_effective_date: วันที่ +field_password: รหัสผ่าน +field_new_password: รหัสผ่านใหม่ +field_password_confirmation: ยืนยันรหัสผ่าน +field_version: รุ่น +field_type: ชนิด +field_host: โฮสต์ +field_port: พอร์ต +field_account: บัà¸à¸Šà¸µ +field_base_dn: Base DN +field_attr_login: เข้าระบบ attribute +field_attr_firstname: ชื่อ attribute +field_attr_lastname: นามสà¸à¸¸à¸¥ attribute +field_attr_mail: อีเมล์ attribute +field_onthefly: สร้างผู้ใช้ทันที +field_start_date: เริ่ม +field_done_ratio: %% สำเร็จ +field_auth_source: วิธีà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตน +field_hide_mail: ซ่อนอีเมล์ของฉัน +field_comments: ความเห็น +field_url: URL +field_start_page: หน้าเริ่มต้น +field_subproject: โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢ +field_hours: ชั่วโมง +field_activity: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ +field_spent_on: วันที่ +field_identifier: ชื่อเฉพาะ +field_is_filter: ใช้เป็นตัวà¸à¸£à¸­à¸‡ +field_issue_to_id: ปัà¸à¸«à¸²à¸—ี่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง +field_delay: เลื่อน +field_assignable: ปัà¸à¸«à¸²à¸ªà¸²à¸¡à¸²à¸£à¸–มอบหมายให้คนที่ทำบทบาทนี้ +field_redirect_existing_links: ย้ายจุดเชื่อมโยงนี้ +field_estimated_hours: เวลาที่ใช้โดยประมาณ +field_column_names: สดมภ์ +field_time_zone: ย่านเวลา +field_searchable: ค้นหาได้ +field_default_value: ค่าเริ่มต้น +field_comments_sorting: à¹à¸ªà¸”งความเห็น + +setting_app_title: ชื่อโปรà¹à¸à¸£à¸¡ +setting_app_subtitle: ชื่อโปรà¹à¸à¸£à¸¡à¸£à¸­à¸‡ +setting_welcome_text: ข้อความต้อนรับ +setting_default_language: ภาษาเริ่มต้น +setting_login_required: ต้องป้อนผู้ใช้-รหัสผ่าน +setting_self_registration: ลงทะเบียนด้วยตนเอง +setting_attachment_max_size: ขนาดà¹à¸Ÿà¹‰à¸¡à¹à¸™à¸šà¸ªà¸¹à¸‡à¸ªà¸¸à¸” +setting_issues_export_limit: à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸­à¸­à¸à¸›à¸±à¸à¸«à¸²à¸ªà¸¹à¸‡à¸ªà¸¸à¸” +setting_mail_from: อีเมล์ที่ใช้ส่ง +setting_bcc_recipients: ไม่ระบุชื่อผู้รับ (bcc) +setting_host_name: ชื่อโฮสต์ +setting_text_formatting: à¸à¸²à¸£à¸ˆà¸±à¸”รูปà¹à¸šà¸šà¸‚้อความ +setting_wiki_compression: บีบอัดประวัติ Wiki +setting_feeds_limit: จำนวน Feed +setting_default_projects_public: โครงà¸à¸²à¸£à¹ƒà¸«à¸¡à¹ˆà¸¡à¸µà¸„่าเริ่มต้นเป็น สาธารณะ +setting_autofetch_changesets: ดึง commits อัตโนมัติ +setting_sys_api_enabled: เปิดใช้ WS สำหรับà¸à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£à¸—ี่เà¸à¹‡à¸šà¸•้นฉบับ +setting_commit_ref_keywords: คำสำคัภReferencing +setting_commit_fix_keywords: คำสำคัภFixing +setting_autologin: เข้าระบบอัตโนมัติ +setting_date_format: รูปà¹à¸šà¸šà¸§à¸±à¸™à¸—ี่ +setting_time_format: รูปà¹à¸šà¸šà¹€à¸§à¸¥à¸² +setting_cross_project_issue_relations: อนุà¸à¸²à¸•ให้ระบุปัà¸à¸«à¸²à¸‚้ามโครงà¸à¸²à¸£ +setting_issue_list_default_columns: สดมภ์เริ่มต้นà¹à¸ªà¸”งในรายà¸à¸²à¸£à¸›à¸±à¸à¸«à¸² +setting_repositories_encodings: à¸à¸²à¸£à¹€à¸‚้ารหัสที่เà¸à¹‡à¸šà¸•้นฉบับ +setting_emails_footer: คำลงท้ายอีเมล์ +setting_protocol: Protocol +setting_per_page_options: ตัวเลือà¸à¸ˆà¸³à¸™à¸§à¸™à¸•่อหน้า +setting_user_format: รูปà¹à¸šà¸šà¸à¸²à¸£à¹à¸ªà¸”งชื่อผู้ใช้ +setting_activity_days_default: จำนวนวันที่à¹à¸ªà¸”งในà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚องโครงà¸à¸²à¸£ +setting_display_subprojects_issues: à¹à¸ªà¸”งปัà¸à¸«à¸²à¸‚องโครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢à¹ƒà¸™à¹‚ครงà¸à¸²à¸£à¸«à¸¥à¸±à¸ + +project_module_issue_tracking: à¸à¸²à¸£à¸•ิดตามปัà¸à¸«à¸² +project_module_time_tracking: à¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸§à¸¥à¸² +project_module_news: ข่าว +project_module_documents: เอà¸à¸ªà¸²à¸£ +project_module_files: à¹à¸Ÿà¹‰à¸¡ +project_module_wiki: Wiki +project_module_repository: ที่เà¸à¹‡à¸šà¸•้นฉบับ +project_module_boards: à¸à¸£à¸°à¸”านข้อความ + +label_user: ผู้ใช้ +label_user_plural: ผู้ใช้ +label_user_new: ผู้ใช้ใหม่ +label_project: โครงà¸à¸²à¸£ +label_project_new: โครงà¸à¸²à¸£à¹ƒà¸«à¸¡à¹ˆ +label_project_plural: โครงà¸à¸²à¸£ +label_project_all: โครงà¸à¸²à¸£à¸—ั้งหมด +label_project_latest: โครงà¸à¸²à¸£à¸¥à¹ˆà¸²à¸ªà¸¸à¸” +label_issue: ปัà¸à¸«à¸² +label_issue_new: ปัà¸à¸«à¸²à¹ƒà¸«à¸¡à¹ˆ +label_issue_plural: ปัà¸à¸«à¸² +label_issue_view_all: ดูปัà¸à¸«à¸²à¸—ั้งหมด +label_issues_by: ปัà¸à¸«à¸²à¹‚ดย %s +label_issue_added: ปัà¸à¸«à¸²à¸–ูà¸à¹€à¸žà¸´à¹ˆà¸¡ +label_issue_updated: ปัà¸à¸«à¸²à¸–ูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡ +label_document: เอà¸à¸ªà¸²à¸£ +label_document_new: เอà¸à¸ªà¸²à¸£à¹ƒà¸«à¸¡à¹ˆ +label_document_plural: เอà¸à¸ªà¸²à¸£ +label_document_added: เอà¸à¸ªà¸²à¸£à¸–ูà¸à¹€à¸žà¸´à¹ˆà¸¡ +label_role: บทบาท +label_role_plural: บทบาท +label_role_new: บทบาทใหม่ +label_role_and_permissions: บทบาทà¹à¸¥à¸°à¸ªà¸´à¸—ธิ +label_member: สมาชิภ+label_member_new: สมาชิà¸à¹ƒà¸«à¸¡à¹ˆ +label_member_plural: สมาชิภ+label_tracker: à¸à¸²à¸£à¸•ิดตาม +label_tracker_plural: à¸à¸²à¸£à¸•ิดตาม +label_tracker_new: à¸à¸²à¸£à¸•ิดตามใหม่ +label_workflow: ลำดับงาน +label_issue_status: สถานะของปัà¸à¸«à¸² +label_issue_status_plural: สถานะของปัà¸à¸«à¸² +label_issue_status_new: สถานะใหม +label_issue_category: ประเภทของปัà¸à¸«à¸² +label_issue_category_plural: ประเภทของปัà¸à¸«à¸² +label_issue_category_new: ประเภทใหม่ +label_custom_field: เขตข้อมูลà¹à¸šà¸šà¸£à¸°à¸šà¸¸à¹€à¸­à¸‡ +label_custom_field_plural: เขตข้อมูลà¹à¸šà¸šà¸£à¸°à¸šà¸¸à¹€à¸­à¸‡ +label_custom_field_new: สร้างเขตข้อมูลà¹à¸šà¸šà¸£à¸°à¸šà¸¸à¹€à¸­à¸‡ +label_enumerations: รายà¸à¸²à¸£ +label_enumeration_new: สร้างใหม่ +label_information: ข้อมูล +label_information_plural: ข้อมูล +label_please_login: à¸à¸£à¸¸à¸“าเข้าระบบà¸à¹ˆà¸­à¸™ +label_register: ลงทะเบียน +label_password_lost: ลืมรหัสผ่าน +label_home: หน้าà¹à¸£à¸ +label_my_page: หน้าของฉัน +label_my_account: บัà¸à¸Šà¸µà¸‚องฉัน +label_my_projects: โครงà¸à¸²à¸£à¸‚องฉัน +label_administration: บริหารจัดà¸à¸²à¸£ +label_login: เข้าระบบ +label_logout: ออà¸à¸£à¸°à¸šà¸š +label_help: ช่วยเหลือ +label_reported_issues: ปัà¸à¸«à¸²à¸—ี่à¹à¸ˆà¹‰à¸‡à¹„ว้ +label_assigned_to_me_issues: ปัà¸à¸«à¸²à¸—ี่มอบหมายให้ฉัน +label_last_login: ติดต่อครั้งสุดท้าย +label_last_updates: ปรับปรุงครั้งสุดท้าย +label_last_updates_plural: %d ปรับปรุงครั้งสุดท้าย +label_registered_on: ลงทะเบียนเมื่อ +label_activity: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ +label_activity_plural: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ +label_activity_latest: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸¥à¹ˆà¸²à¸ªà¸¸à¸” +label_overall_activity: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¹‚ดยรวม +label_new: ใหม่ +label_logged_as: เข้าระบบในชื่อ +label_environment: สภาพà¹à¸§à¸”ล้อม +label_authentication: à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตน +label_auth_source: วิธีà¸à¸²à¸£à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตน +label_auth_source_new: สร้างวิธีà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตนใหม่ +label_auth_source_plural: วิธีà¸à¸²à¸£ Authentication +label_subproject_plural: โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢ +label_min_max_length: สั้น-ยาว สุดที่ +label_list: รายà¸à¸²à¸£ +label_date: วันที่ +label_integer: จำนวนเต็ม +label_float: จำนวนจริง +label_boolean: ถูà¸à¸œà¸´à¸” +label_string: ข้อความ +label_text: ข้อความขนาดยาว +label_attribute: คุณลัà¸à¸©à¸“ะ +label_attribute_plural: คุณลัà¸à¸©à¸“ะ +label_download: %d ดาวน์โหลด +label_download_plural: %d ดาวน์โหลด +label_no_data: จำนวนข้อมูลที่à¹à¸ªà¸”ง +label_change_status: เปลี่ยนสถานะ +label_history: ประวัติ +label_attachment: à¹à¸Ÿà¹‰à¸¡ +label_attachment_new: à¹à¸Ÿà¹‰à¸¡à¹ƒà¸«à¸¡à¹ˆ +label_attachment_delete: ลบà¹à¸Ÿà¹‰à¸¡ +label_attachment_plural: à¹à¸Ÿà¹‰à¸¡ +label_file_added: à¹à¸Ÿà¹‰à¸¡à¸–ูà¸à¹€à¸žà¸´à¹ˆà¸¡ +label_report: รายงาน +label_report_plural: รายงาน +label_news: ข่าว +label_news_new: เพิ่มข่าว +label_news_plural: ข่าว +label_news_latest: ข่าวล่าสุด +label_news_view_all: ดูข่าวทั้งหมด +label_news_added: ข่าวถูà¸à¹€à¸žà¸´à¹ˆà¸¡ +label_change_log: บันทึà¸à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ +label_settings: ปรับà¹à¸•่ง +label_overview: ภาพรวม +label_version: รุ่น +label_version_new: รุ่นใหม่ +label_version_plural: รุ่น +label_confirmation: ยืนยัน +label_export_to: 'รูปà¹à¸šà¸šà¸­à¸·à¹ˆà¸™à¹† :' +label_read: อ่าน... +label_public_projects: โครงà¸à¸²à¸£à¸ªà¸²à¸˜à¸²à¸£à¸“ะ +label_open_issues: เปิด +label_open_issues_plural: เปิด +label_closed_issues: ปิด +label_closed_issues_plural: ปิด +label_total: จำนวนรวม +label_permissions: สิทธิ +label_current_status: สถานะปัจจุบัน +label_new_statuses_allowed: อนุà¸à¸²à¸•ให้มีสถานะใหม่ +label_all: ทั้งหมด +label_none: ไม่มี +label_nobody: ไม่มีใคร +label_next: ต่อไป +label_previous: à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸² +label_used_by: ถูà¸à¹ƒà¸Šà¹‰à¹‚ดย +label_details: รายละเอียด +label_add_note: เพิ่มบันทึภ+label_per_page: ต่อหน้า +label_calendar: ปà¸à¸´à¸—ิน +label_months_from: เดือนจาภ+label_gantt: Gantt +label_internal: ภายใน +label_last_changes: last %d เปลี่ยนà¹à¸›à¸¥à¸‡ +label_change_view_all: ดูà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸—ั้งหมด +label_personalize_page: ปรับà¹à¸•่งหน้านี้ +label_comment: ความเห็น +label_comment_plural: ความเห็น +label_comment_add: เพิ่มความเห็น +label_comment_added: ความเห็นถูà¸à¹€à¸žà¸´à¹ˆà¸¡ +label_comment_delete: ลบความเห็น +label_query: à¹à¸šà¸šà¸ªà¸­à¸šà¸–ามà¹à¸šà¸šà¸à¸³à¸«à¸™à¸”เอง +label_query_plural: à¹à¸šà¸šà¸ªà¸­à¸šà¸–ามà¹à¸šà¸šà¸à¸³à¸«à¸™à¸”เอง +label_query_new: à¹à¸šà¸šà¸ªà¸­à¸šà¸–ามใหม่ +label_filter_add: เพิ่มตัวà¸à¸£à¸­à¸‡ +label_filter_plural: ตัวà¸à¸£à¸­à¸‡ +label_equals: คือ +label_not_equals: ไม่ใช่ +label_in_less_than: น้อยà¸à¸§à¹ˆà¸² +label_in_more_than: มาà¸à¸à¸§à¹ˆà¸² +label_in: ในช่วง +label_today: วันนี้ +label_all_time: ตลอดเวลา +label_yesterday: เมื่อวาน +label_this_week: อาทิตย์นี้ +label_last_week: อาทิตย์ที่à¹à¸¥à¹‰à¸§ +label_last_n_days: %d วันย้อนหลัง +label_this_month: เดือนนี้ +label_last_month: เดือนที่à¹à¸¥à¹‰à¸§ +label_this_year: ปีนี้ +label_date_range: ช่วงวันที่ +label_less_than_ago: น้อยà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¸§à¸±à¸™ +label_more_than_ago: มาà¸à¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¸§à¸±à¸™ +label_ago: วันผ่านมาà¹à¸¥à¹‰à¸§ +label_contains: มี... +label_not_contains: ไม่มี... +label_day_plural: วัน +label_repository: ที่เà¸à¹‡à¸šà¸•้นฉบับ +label_repository_plural: ที่เà¸à¹‡à¸šà¸•้นฉบับ +label_browse: เปิดหา +label_modification: %d เปลี่ยนà¹à¸›à¸¥à¸‡ +label_modification_plural: %d เปลี่ยนà¹à¸›à¸¥à¸‡ +label_revision: à¸à¸²à¸£à¹à¸à¹‰à¹„ข +label_revision_plural: à¸à¸²à¸£à¹à¸à¹‰à¹„ข +label_associated_revisions: à¸à¸²à¸£à¹à¸à¹‰à¹„ขที่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง +label_added: ถูà¸à¹€à¸žà¸´à¹ˆà¸¡ +label_modified: ถูà¸à¹à¸à¹‰à¹„ข +label_deleted: ถูà¸à¸¥à¸š +label_latest_revision: รุ่นà¸à¸²à¸£à¹à¸à¹‰à¹„ขล่าสุด +label_latest_revision_plural: รุ่นà¸à¸²à¸£à¹à¸à¹‰à¹„ขล่าสุด +label_view_revisions: ดูà¸à¸²à¸£à¹à¸à¹‰à¹„ข +label_max_size: ขนาดใหà¸à¹ˆà¸ªà¸¸à¸” +label_on: 'ใน' +label_sort_highest: ย้ายไปบนสุด +label_sort_higher: ย้ายขึ้น +label_sort_lower: ย้ายลง +label_sort_lowest: ย้ายไปล่างสุด +label_roadmap: à¹à¸œà¸™à¸‡à¸²à¸™ +label_roadmap_due_in: ถึงà¸à¸³à¸«à¸™à¸”ใน +label_roadmap_overdue: %s ช้าà¸à¸§à¹ˆà¸²à¸à¸³à¸«à¸™à¸” +label_roadmap_no_issues: ไม่มีปัà¸à¸«à¸²à¸ªà¸³à¸«à¸£à¸±à¸šà¸£à¸¸à¹ˆà¸™à¸™à¸µà¹‰ +label_search: ค้นหา +label_result_plural: ผลà¸à¸²à¸£à¸„้นหา +label_all_words: ทุà¸à¸„ำ +label_wiki: Wiki +label_wiki_edit: à¹à¸à¹‰à¹„ข Wiki +label_wiki_edit_plural: à¹à¸à¹‰à¹„ข Wiki +label_wiki_page: หน้า Wiki +label_wiki_page_plural: หน้า Wiki +label_index_by_title: เรียงตามชื่อเรื่อง +label_index_by_date: เรียงตามวัน +label_current_version: รุ่นปัจจุบัน +label_preview: ตัวอย่างà¸à¹ˆà¸­à¸™à¸ˆà¸±à¸”เà¸à¹‡à¸š +label_feed_plural: Feeds +label_changes_details: รายละเอียดà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸—ั้งหมด +label_issue_tracking: ติดตามปัà¸à¸«à¸² +label_spent_time: เวลาที่ใช้ +label_f_hour: %.2f ชั่วโมง +label_f_hour_plural: %.2f ชั่วโมง +label_time_tracking: ติดตามà¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸§à¸¥à¸² +label_change_plural: เปลี่ยนà¹à¸›à¸¥à¸‡ +label_statistics: สถิติ +label_commits_per_month: Commits ต่อเดือน +label_commits_per_author: Commits ต่อผู้à¹à¸•่ง +label_view_diff: ดูความà¹à¸•à¸à¸•่าง +label_diff_inline: inline +label_diff_side_by_side: side by side +label_options: ตัวเลือภ+label_copy_workflow_from: คัดลอà¸à¸¥à¸³à¸”ับงานจาภ+label_permissions_report: รายงานสิทธิ +label_watched_issues: เà¸à¹‰à¸²à¸”ูปัà¸à¸«à¸² +label_related_issues: ปัà¸à¸«à¸²à¸—ี่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง +label_applied_status: จัดเà¸à¹‡à¸šà¸ªà¸–านะ +label_loading: à¸à¸³à¸¥à¸±à¸‡à¹‚หลด... +label_relation_new: ความสัมพันธ์ใหม่ +label_relation_delete: ลบความสัมพันธ์ +label_relates_to: สัมพันธ์à¸à¸±à¸š +label_duplicates: ซ้ำ +label_blocks: à¸à¸µà¸”à¸à¸±à¸™ +label_blocked_by: à¸à¸µà¸”à¸à¸±à¸™à¹‚ดย +label_precedes: นำหน้า +label_follows: ตามหลัง +label_end_to_start: จบ-เริ่ม +label_end_to_end: จบ-จบ +label_start_to_start: เริ่ม-เริ่ม +label_start_to_end: เริ่ม-จบ +label_stay_logged_in: อยู่ในระบบต่อ +label_disabled: ไม่ใช้งาน +label_show_completed_versions: à¹à¸ªà¸”งรุ่นที่สมบูรณ์ +label_me: ฉัน +label_board: สภาà¸à¸²à¹à¸Ÿ +label_board_new: สร้างสภาà¸à¸²à¹à¸Ÿ +label_board_plural: สภาà¸à¸²à¹à¸Ÿ +label_topic_plural: หัวข้อ +label_message_plural: ข้อความ +label_message_last: ข้อความล่าสุด +label_message_new: เขียนข้อความใหม่ +label_message_posted: ข้อความถูà¸à¹€à¸žà¸´à¹ˆà¸¡à¹à¸¥à¹‰à¸§ +label_reply_plural: ตอบà¸à¸¥à¸±à¸š +label_send_information: ส่งรายละเอียดของบัà¸à¸Šà¸µà¹ƒà¸«à¹‰à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰ +label_year: ปี +label_month: เดือน +label_week: สัปดาห์ +label_date_from: จาภ+label_date_to: ถึง +label_language_based: ขึ้นอยู่à¸à¸±à¸šà¸ à¸²à¸©à¸²à¸‚องผู้ใช้ +label_sort_by: เรียงโดย %s +label_send_test_email: ส่งจดหมายทดสอบ +label_feeds_access_key_created_on: RSS access key สร้างเมื่อ %s ที่ผ่านมา +label_module_plural: ส่วนประà¸à¸­à¸š +label_added_time_by: เพิ่มโดย %s %s ที่ผ่านมา +label_updated_time: ปรับปรุง %s ที่ผ่านมา +label_jump_to_a_project: ไปที่โครงà¸à¸²à¸£... +label_file_plural: à¹à¸Ÿà¹‰à¸¡ +label_changeset_plural: à¸à¸¥à¸¸à¹ˆà¸¡à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ +label_default_columns: สดมภ์เริ่มต้น +label_no_change_option: (ไม่เปลี่ยนà¹à¸›à¸¥à¸‡) +label_bulk_edit_selected_issues: à¹à¸à¹‰à¹„ขปัà¸à¸«à¸²à¸—ี่เลือà¸à¸—ั้งหมด +label_theme: ชุดรูปà¹à¸šà¸š +label_default: ค่าเริ่มต้น +label_search_titles_only: ค้นหาจาà¸à¸Šà¸·à¹ˆà¸­à¹€à¸£à¸·à¹ˆà¸­à¸‡à¹€à¸—่านั้น +label_user_mail_option_all: "ทุà¸à¹† เหตุà¸à¸²à¸£à¸“์ในโครงà¸à¸²à¸£à¸‚องฉัน" +label_user_mail_option_selected: "ทุà¸à¹† เหตุà¸à¸²à¸£à¸“์ในโครงà¸à¸²à¸£à¸—ี่เลือà¸..." +label_user_mail_option_none: "เฉพาะสิ่งที่ฉันเลือà¸à¸«à¸£à¸·à¸­à¸¡à¸µà¸ªà¹ˆà¸§à¸™à¹€à¸à¸µà¹ˆà¸¢à¸§à¸‚้อง" +label_user_mail_no_self_notified: "ฉันไม่ต้องà¸à¸²à¸£à¹„ด้รับà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ือนในสิ่งที่ฉันทำเอง" +label_registration_activation_by_email: เปิดบัà¸à¸Šà¸µà¸œà¹ˆà¸²à¸™à¸­à¸µà¹€à¸¡à¸¥à¹Œ +label_registration_manual_activation: อนุมัติโดยผู้บริหารจัดà¸à¸²à¸£ +label_registration_automatic_activation: เปิดบัà¸à¸Šà¸µà¸­à¸±à¸•โนมัติ +label_display_per_page: 'ต่อหน้า: %s' +label_age: อายุ +label_change_properties: เปลี่ยนคุณสมบัติ +label_general: ทั่วๆ ไป +label_more: อื่น ๆ +label_scm: ตัวจัดà¸à¸²à¸£à¸•้นฉบับ +label_plugins: ส่วนเสริม +label_ldap_authentication: à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตนโดยใช้ LDAP +label_downloads_abbr: D/L +label_optional_description: รายละเอียดเพิ่มเติม +label_add_another_file: เพิ่มà¹à¸Ÿà¹‰à¸¡à¸­à¸·à¹ˆà¸™à¹† +label_preferences: ค่าที่ชอบใจ +label_chronological_order: เรียงจาà¸à¹€à¸à¹ˆà¸²à¹„ปใหม่ +label_reverse_chronological_order: เรียงจาà¸à¹ƒà¸«à¸¡à¹ˆà¹„ปเà¸à¹ˆà¸² +label_planning: à¸à¸²à¸£à¸§à¸²à¸‡à¹à¸œà¸™ + +button_login: เข้าระบบ +button_submit: จัดส่งข้อมูล +button_save: จัดเà¸à¹‡à¸š +button_check_all: เลือà¸à¸—ั้งหมด +button_uncheck_all: ไม่เลือà¸à¸—ั้งหมด +button_delete: ลบ +button_create: สร้าง +button_test: ทดสอบ +button_edit: à¹à¸à¹‰à¹„ข +button_add: เพิ่ม +button_change: เปลี่ยนà¹à¸›à¸¥à¸‡ +button_apply: ประยุà¸à¸•์ใช้ +button_clear: ล้างข้อความ +button_lock: ล็อค +button_unlock: ยà¸à¹€à¸¥à¸´à¸à¸à¸²à¸£à¸¥à¹‡à¸­à¸„ +button_download: ดาวน์โหลด +button_list: รายà¸à¸²à¸£ +button_view: มุมมอง +button_move: ย้าย +button_back: à¸à¸¥à¸±à¸š +button_cancel: ยà¸à¹€à¸¥à¸´à¸ +button_activate: เปิดใช้ +button_sort: จัดเรียง +button_log_time: บันทึà¸à¹€à¸§à¸¥à¸² +button_rollback: ถอยà¸à¸¥à¸±à¸šà¸¡à¸²à¸—ี่รุ่นนี้ +button_watch: เà¸à¹‰à¸²à¸”ู +button_unwatch: เลิà¸à¹€à¸à¹‰à¸²à¸”ู +button_reply: ตอบà¸à¸¥à¸±à¸š +button_archive: เà¸à¹‡à¸šà¹€à¸‚้าโà¸à¸”ัง +button_unarchive: เอาออà¸à¸ˆà¸²à¸à¹‚à¸à¸”ัง +button_reset: เริ่มใหมท +button_rename: เปลี่ยนชื่อ +button_change_password: เปลี่ยนรหัสผ่าน +button_copy: คัดลอภ+button_annotate: หมายเหตุประà¸à¸­à¸š +button_update: ปรับปรุง +button_configure: ปรับà¹à¸•่ง + +status_active: เปิดใช้งานà¹à¸¥à¹‰à¸§ +status_registered: รอà¸à¸²à¸£à¸­à¸™à¸¸à¸¡à¸±à¸•ิ +status_locked: ล็อค + +text_select_mail_notifications: เลือà¸à¸à¸²à¸£à¸à¸£à¸°à¸—ำที่ต้องà¸à¸²à¸£à¹ƒà¸«à¹‰à¸ªà¹ˆà¸‡à¸­à¸µà¹€à¸¡à¸¥à¹Œà¹à¸ˆà¹‰à¸‡. +text_regexp_info: ตัวอย่าง ^[A-Z0-9]+$ +text_min_max_length_info: 0 หมายถึงไม่จำà¸à¸±à¸” +text_project_destroy_confirmation: คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¹„หมว่าต้องà¸à¸²à¸£à¸¥à¸šà¹‚ครงà¸à¸²à¸£à¹à¸¥à¸°à¸‚้อมูลที่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้่อง ? +text_subprojects_destroy_warning: 'โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢: %s จะถูà¸à¸¥à¸šà¸”้วย.' +text_workflow_edit: เลือà¸à¸šà¸—บาทà¹à¸¥à¸°à¸à¸²à¸£à¸•ิดตาม เพื่อà¹à¸à¹‰à¹„ขลำดับงาน +text_are_you_sure: คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¹„หม ? +text_journal_changed: เปลี่ยนà¹à¸›à¸¥à¸‡à¸ˆà¸²à¸ %s เป็น %s +text_journal_set_to: ตั้งค่าเป็น %s +text_journal_deleted: ถูà¸à¸¥à¸š +text_tip_task_begin_day: งานที่เริ่มวันนี้ +text_tip_task_end_day: งานที่จบวันนี้ +text_tip_task_begin_end_day: งานที่เริ่มà¹à¸¥à¸°à¸ˆà¸šà¸§à¸±à¸™à¸™à¸µà¹‰ +text_project_identifier_info: 'ภาษาอังà¸à¸¤à¸©à¸•ัวเล็à¸(a-z), ตัวเลข(0-9) à¹à¸¥à¸°à¸‚ีด (-) เท่านั้น.
    เมื่อจัดเà¸à¹‡à¸šà¹à¸¥à¹‰à¸§, ชื่อเฉพาะไม่สามารถเปลี่ยนà¹à¸›à¸¥à¸‡à¹„ด้' +text_caracters_maximum: สูงสุด %d ตัวอัà¸à¸©à¸£. +text_caracters_minimum: ต้องยาวอย่างน้อย %d ตัวอัà¸à¸©à¸£. +text_length_between: ความยาวระหว่าง %d ถึง %d ตัวอัà¸à¸©à¸£. +text_tracker_no_workflow: ไม่ได้บัà¸à¸à¸±à¸•ิลำดับงานสำหรับà¸à¸²à¸£à¸•ิดตามนี้ +text_unallowed_characters: ตัวอัà¸à¸©à¸£à¸•้องห้าม +text_comma_separated: ใส่ได้หลายค่า โดยคั่นด้วยลูà¸à¸™à¹‰à¸³( ,). +text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages +text_issue_added: ปัà¸à¸«à¸² %s ถูà¸à¹à¸ˆà¹‰à¸‡à¹‚ดย %s. +text_issue_updated: ปัà¸à¸«à¸² %s ถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹‚ดย %s. +text_wiki_destroy_confirmation: คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¸«à¸£à¸·à¸­à¸§à¹ˆà¸²à¸•้องà¸à¸²à¸£à¸¥à¸š wiki นี้พร้อมทั้งเนี้อหา? +text_issue_category_destroy_question: บางปัà¸à¸«à¸² (%d) อยู่ในประเภทนี้. คุณต้องà¸à¸²à¸£à¸—ำอย่างไร ? +text_issue_category_destroy_assignments: ลบประเภทนี้ +text_issue_category_reassign_to: ระบุปัà¸à¸«à¸²à¹ƒà¸™à¸›à¸£à¸°à¹€à¸ à¸—นี้ +text_user_mail_option: "ในโครงà¸à¸²à¸£à¸—ี่ไม่ได้เลือà¸, คุณจะได้รับà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸—ี่คุณเà¸à¹‰à¸²à¸”ูหรือมีส่วนเà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง (เช่นปัà¸à¸«à¸²à¸—ี่คุณà¹à¸ˆà¹‰à¸‡à¹„ว้หรือได้รับมอบหมาย)." +text_no_configuration_data: "บทบาท, à¸à¸²à¸£à¸•ิดตาม, สถานะปัà¸à¸«à¸² à¹à¸¥à¸°à¸¥à¸³à¸”ับงานยังไม่ได้ถูà¸à¸•ั้งค่า.\nขอà¹à¸™à¸°à¸™à¸³à¹ƒà¸«à¹‰à¹‚หลดค่าเริ่มต้น. คุณสามารถà¹à¸à¹‰à¹„ขค่าได้หลังจาà¸à¹‚หลดà¹à¸¥à¹‰à¸§." +text_load_default_configuration: โหลดค่าเริ่มต้น +text_status_changed_by_changeset: ประยุà¸à¸•์ใช้ในà¸à¸¥à¸¸à¹ˆà¸¡à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ %s. +text_issues_destroy_confirmation: 'คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¹„หมว่าต้องà¸à¸²à¸£à¸¥à¸šà¸›à¸±à¸à¸«à¸²(ทั้งหลาย)ที่เลือà¸à¹„ว้?' +text_select_project_modules: 'เลือà¸à¸ªà¹ˆà¸§à¸™à¸›à¸£à¸°à¸à¸­à¸šà¸—ี่ต้องà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™à¸ªà¸³à¸«à¸£à¸±à¸šà¹‚ครงà¸à¸²à¸£à¸™à¸µà¹‰:' +text_default_administrator_account_changed: ค่าเริ่มต้นของบัà¸à¸Šà¸µà¸œà¸¹à¹‰à¸šà¸£à¸´à¸«à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£à¸–ูà¸à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ +text_file_repository_writable: ที่เà¸à¹‡à¸šà¸•้นฉบับสามารถเขียนได้ +text_rmagick_available: RMagick มีให้ใช้ (เป็นตัวเลือà¸) +text_destroy_time_entries_question: %.02f ชั่วโมงที่ถูà¸à¹à¸ˆà¹‰à¸‡à¹ƒà¸™à¸›à¸±à¸à¸«à¸²à¸™à¸µà¹‰à¸ˆà¸°à¹‚ดนลบ. คุณต้องà¸à¸²à¸£à¸—ำอย่างไร? +text_destroy_time_entries: ลบเวลาที่รายงานไว้ +text_assign_time_entries_to_project: ระบุเวลาที่ใช้ในโครงà¸à¸²à¸£à¸™à¸µà¹‰ +text_reassign_time_entries: 'ระบุเวลาที่ใช้ในโครงà¸à¸²à¸£à¸™à¸µà¹ˆà¸­à¸µà¸à¸„รั้ง:' + +default_role_manager: ผู้จัดà¸à¸²à¸£ +default_role_developper: ผู้พัฒนา +default_role_reporter: ผู้รายงาน +default_tracker_bug: บั๊ภ+default_tracker_feature: ลัà¸à¸©à¸“ะเด่น +default_tracker_support: สนับสนุน +default_issue_status_new: เà¸à¸´à¸”ขึ้น +default_issue_status_assigned: รับมอบหมาย +default_issue_status_resolved: ดำเนินà¸à¸²à¸£ +default_issue_status_feedback: รอคำตอบ +default_issue_status_closed: จบ +default_issue_status_rejected: ยà¸à¹€à¸¥à¸´à¸ +default_doc_category_user: เอà¸à¸ªà¸²à¸£à¸‚องผู้ใช้ +default_doc_category_tech: เอà¸à¸ªà¸²à¸£à¸—างเทคนิค +default_priority_low: ต่ำ +default_priority_normal: ปà¸à¸•ิ +default_priority_high: สูง +default_priority_urgent: เร่งด่วน +default_priority_immediate: ด่วนมาภ+default_activity_design: ออà¸à¹à¸šà¸š +default_activity_development: พัฒนา + +enumeration_issue_priorities: ความสำคัà¸à¸‚องปัà¸à¸«à¸² +enumeration_doc_categories: ประเภทเอà¸à¸ªà¸²à¸£ +enumeration_activities: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ (ใช้ในà¸à¸²à¸£à¸•ิดตามเวลา) +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/uk.yml b/groups/lang/uk.yml index a52a05603..7ba152413 100644 --- a/groups/lang/uk.yml +++ b/groups/lang/uk.yml @@ -48,6 +48,7 @@ general_text_no: 'ÐÑ–' general_text_yes: 'Так' general_lang_name: 'Ukrainian (УкраїнÑька)' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: UTF-8 general_pdf_encoding: UTF-8 general_day_names: Понеділок,Вівторок,Середа,Четвер,П'ÑтницÑ,Субота,ÐÐµÐ´Ñ–Ð»Ñ @@ -620,3 +621,20 @@ setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +text_user_wrote: '%s wrote:' +label_duplicated_by: duplicated by +setting_enabled_scm: Enabled SCM +text_enumeration_category_reassign_to: 'Reassign them to this value:' +text_enumeration_destroy_question: '%d objects are assigned to this value.' +label_incoming_emails: Incoming emails +label_generate_key: Generate a key +setting_mail_handler_api_enabled: Enable WS for incoming emails +setting_mail_handler_api_key: API key +text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/zh-tw.yml b/groups/lang/zh-tw.yml index a0c7fafb3..6a1441364 100644 --- a/groups/lang/zh-tw.yml +++ b/groups/lang/zh-tw.yml @@ -48,6 +48,7 @@ general_text_no: 'å¦' general_text_yes: '是' general_lang_name: 'Traditional Chinese (ç¹é«”中文)' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: Big5 general_pdf_encoding: Big5 general_day_names: 星期一,星期二,星期三,星期四,星期五,星期六,星期日 @@ -91,6 +92,8 @@ mail_body_account_information_external: 您å¯ä»¥ä½¿ç”¨ "%s" 帳號登入 Redmin mail_body_account_information: 您的 Redmine 帳號資訊 mail_subject_account_activation_request: Redmine 帳號啟用需求通知 mail_body_account_activation_request: 'æœ‰ä½æ–°ç”¨æˆ¶ (%s) 已經完æˆè¨»å†Šï¼Œæ­£ç­‰å€™æ‚¨çš„審核:' +mail_subject_reminder: "您有 %d 個項目å³å°‡åˆ°æœŸ" +mail_body_reminder: "%d 個指派給您的項目,將於 %d 天之內到期:" gui_validation_error: 1 個錯誤 gui_validation_error_plural: %d 個錯誤 @@ -110,7 +113,7 @@ field_created_on: 建立日期 field_updated_on: æ›´æ–° field_field_format: æ ¼å¼ field_is_for_all: 給所有專案 -field_possible_values: Possible values +field_possible_values: å¯èƒ½å€¼ field_regexp: æ­£è¦è¡¨ç¤ºå¼ field_min_length: 最å°é•·åº¦ field_max_length: 最大長度 @@ -153,9 +156,9 @@ field_account: 帳戶 field_base_dn: Base DN field_attr_login: 登入屬性 field_attr_firstname: å字屬性 -field_attr_lastname: Lastname attribute -field_attr_mail: Email attribute -field_onthefly: On-the-fly user creation +field_attr_lastname: å§“æ°å±¬æ€§ +field_attr_mail: é›»å­éƒµä»¶ä¿¡ç®±å±¬æ€§ +field_onthefly: 峿™‚建立使用者 field_start_date: 開始日期 field_done_ratio: 完æˆç™¾åˆ†æ¯” field_auth_source: èªè­‰æ¨¡å¼ @@ -168,13 +171,13 @@ field_hours: å°æ™‚ field_activity: 活動 field_spent_on: 日期 field_identifier: 代碼 -field_is_filter: Used as a filter -field_issue_to_id: Related issue +field_is_filter: ç”¨ä¾†ä½œç‚ºéŽæ¿¾å™¨ +field_issue_to_id: 相關項目 field_delay: 逾期 field_assignable: é …ç›®å¯è¢«åˆ†æ´¾è‡³æ­¤è§’色 -field_redirect_existing_links: Redirect existing links +field_redirect_existing_links: 釿–°å°Žå‘ç¾æœ‰é€£çµ field_estimated_hours: é ä¼°å·¥æ™‚ -field_column_names: Columns +field_column_names: æ¬„ä½ field_time_zone: æ™‚å€ field_searchable: å¯ç”¨åšæœå°‹æ¢ä»¶ field_default_value: é è¨­å€¼ @@ -193,7 +196,7 @@ setting_bcc_recipients: 使用密件副本 (BCC) setting_host_name: 主機å稱 setting_text_formatting: æ–‡å­—æ ¼å¼ setting_wiki_compression: 壓縮 Wiki æ­·å²æ–‡ç«  -setting_feeds_limit: Feed content limit +setting_feeds_limit: RSS æ–°èžé™åˆ¶ setting_autofetch_changesets: 自動å–å¾—é€äº¤ç‰ˆæ¬¡ setting_default_projects_public: 新建立之專案é è¨­ç‚ºã€Œå…¬é–‹ã€ setting_sys_api_enabled: 啟用管ç†ç‰ˆæœ¬åº«ä¹‹ç¶²é æœå‹™ (Web Service) @@ -210,7 +213,10 @@ setting_protocol: å”定 setting_per_page_options: æ¯é é¡¯ç¤ºå€‹æ•¸é¸é … setting_user_format: ä½¿ç”¨è€…é¡¯ç¤ºæ ¼å¼ setting_activity_days_default: 專案活動顯示天數 -setting_display_subprojects_issues: é è¨­æ–¼ä¸»æŽ§å°ˆæ¡ˆä¸­é¡¯ç¤ºå¾žå±¬å°ˆæ¡ˆçš„é …ç›® +setting_display_subprojects_issues: é è¨­æ–¼çˆ¶å°ˆæ¡ˆä¸­é¡¯ç¤ºå­å°ˆæ¡ˆçš„é …ç›® +setting_enabled_scm: 啟用的 SCM +setting_mail_handler_api_enabled: 啟用處ç†å‚³å…¥é›»å­éƒµä»¶çš„æœå‹™ +setting_mail_handler_api_key: API 金鑰 project_module_issue_tracking: 項目追蹤 project_module_time_tracking: 工時追蹤 @@ -291,6 +297,7 @@ label_auth_source: èªè­‰æ¨¡å¼ label_auth_source_new: 建立新èªè­‰æ¨¡å¼ label_auth_source_plural: èªè­‰æ¨¡å¼æ¸…å–® label_subproject_plural: å­å°ˆæ¡ˆ +label_and_its_subprojects: %s 與其å­å°ˆæ¡ˆ label_min_max_length: æœ€å° - 最大 長度 label_list: 清單 label_date: 日期 @@ -327,7 +334,7 @@ label_version_new: 建立新的版本 label_version_plural: 版本 label_confirmation: ç¢ºèª label_export_to: 匯出至 -label_read: Read... +label_read: 讀å–... label_public_projects: 公開專案 label_open_issues: 進行中 label_open_issues_plural: 進行中 @@ -339,7 +346,7 @@ label_current_status: ç›®å‰ç‹€æ…‹ label_new_statuses_allowed: å¯è®Šæ›´è‡³ä»¥ä¸‹ç‹€æ…‹ label_all: 全部 label_none: 空值 -label_nobody: nobody +label_nobody: ç„¡å label_next: ä¸‹ä¸€é  label_previous: ä¸Šä¸€é  label_used_by: Used by @@ -349,7 +356,7 @@ label_per_page: æ¯é  label_calendar: 日曆 label_months_from: 個月, 開始月份 label_gantt: 甘特圖 -label_internal: Internal +label_internal: 內部 label_last_changes: 最近 %d 個變更 label_change_view_all: 檢視所有變更 label_personalize_page: è‡ªè¨‚ç‰ˆé¢ @@ -426,7 +433,7 @@ label_issue_tracking: 項目追蹤 label_spent_time: 耗用時間 label_f_hour: %.2f å°æ™‚ label_f_hour_plural: %.2f å°æ™‚ -label_time_tracking: Time tracking +label_time_tracking: 工時追蹤 label_change_plural: 變更 label_statistics: 統計資訊 label_commits_per_month: 便œˆä»½çµ±è¨ˆé€äº¤æ¬¡æ•¸ @@ -445,14 +452,15 @@ label_relation_new: å»ºç«‹æ–°é—œè¯ label_relation_delete: åˆªé™¤é—œè¯ label_relates_to: é—œè¯è‡³ label_duplicates: å·²é‡è¤‡ +label_duplicated_by: èˆ‡å¾Œé¢æ‰€åˆ—é …ç›®é‡è¤‡ label_blocks: 阻擋 label_blocked_by: 被阻擋 label_precedes: 優先於 label_follows: 跟隨於 -label_end_to_start: end to start -label_end_to_end: end to end -label_start_to_start: start to start -label_start_to_end: start to end +label_end_to_start: çµæŸâ”€é–‹å§‹ +label_end_to_end: çµæŸâ”€çµæŸ +label_start_to_start: 開始─開始 +label_start_to_end: é–‹å§‹â”€çµæŸ label_stay_logged_in: ç¶­æŒå·²ç™»å…¥ç‹€æ…‹ label_disabled: 關閉 label_show_completed_versions: 顯示已完æˆçš„版本 @@ -496,7 +504,7 @@ label_registration_activation_by_email: é€éŽé›»å­éƒµä»¶å•Ÿç”¨å¸³æˆ¶ label_registration_manual_activation: 手動啟用帳戶 label_registration_automatic_activation: 自動啟用帳戶 label_display_per_page: 'æ¯é é¡¯ç¤º: %s 個' -label_age: Age +label_age: 年齡 label_change_properties: 變更屬性 label_general: 一般 label_more: 更多 » @@ -510,6 +518,8 @@ label_preferences: å好é¸é … label_chronological_order: 以時間由é è‡³è¿‘æŽ’åº label_reverse_chronological_order: ä»¥æ™‚é–“ç”±è¿‘è‡³é æŽ’åº label_planning: 計劃表 +label_incoming_emails: 傳入的電å­éƒµä»¶ +label_generate_key: 產生金鑰 button_login: 登入 button_submit: é€å‡º @@ -527,10 +537,10 @@ button_clear: 清除 button_lock: 鎖定 button_unlock: 解除鎖定 button_download: 下載 -button_list: List +button_list: 清單 button_view: 檢視 button_move: 移動 -button_back: Back +button_back: 返回 button_cancel: å–æ¶ˆ button_activate: 啟用 button_sort: æŽ’åº @@ -557,6 +567,7 @@ text_select_mail_notifications: 鏿“‡æ¬²å¯„é€æé†’é€šçŸ¥éƒµä»¶ä¹‹å‹•ä½œ text_regexp_info: eg. ^[A-Z0-9]+$ text_min_max_length_info: 0 代表「ä¸é™åˆ¶ã€ text_project_destroy_confirmation: 您確定è¦åˆªé™¤é€™å€‹å°ˆæ¡ˆå’Œå…¶ä»–相關資料? +text_subprojects_destroy_warning: '下列å­å°ˆæ¡ˆï¼š %s 將一併被刪除。' text_workflow_edit: 鏿“‡è§’色與追蹤標籤以設定其工作æµç¨‹ text_are_you_sure: 確定執行? text_journal_changed: 從 %s 變更為 %s @@ -579,8 +590,8 @@ text_wiki_destroy_confirmation: 您確定è¦åˆªé™¤é€™å€‹ wiki 和其中的所有 text_issue_category_destroy_question: 有 (%d) 個項目被指派到此分類. è«‹é¸æ“‡æ‚¨æƒ³è¦çš„動作? text_issue_category_destroy_assignments: 移除這些項目的分類 text_issue_category_reassign_to: 釿–°æŒ‡æ´¾é€™äº›é …目至其它分類 -text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." -text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." +text_user_mail_option: "å°æ–¼é‚£äº›æœªè¢«é¸æ“‡çš„å°ˆæ¡ˆï¼Œå°‡åªæœƒæŽ¥æ”¶åˆ°æ‚¨æ­£åœ¨è§€å¯Ÿä¸­ï¼Œæˆ–是åƒèˆ‡ä¸­çš„項目通知。(「åƒèˆ‡ä¸­çš„é …ç›®ã€åŒ…嫿‚¨å»ºç«‹çš„æˆ–是指派給您的項目)" +text_no_configuration_data: "角色ã€è¿½è¹¤å™¨ã€é …目狀態與æµç¨‹å°šæœªè¢«è¨­å®šå®Œæˆã€‚\n強烈建議您先載入é è¨­çš„è¨­å®šï¼Œç„¶å¾Œä¿®æ”¹æˆæ‚¨æƒ³è¦çš„設定。" text_load_default_configuration: 載入é è¨­çµ„æ…‹ text_status_changed_by_changeset: 已套用至變更集 %s. text_issues_destroy_confirmation: 'ç¢ºå®šåˆªé™¤å·²é¸æ“‡çš„項目?' @@ -592,6 +603,10 @@ text_destroy_time_entries_question: 您å³å°‡åˆªé™¤çš„項目已報工 %.02f å° text_destroy_time_entries: 刪除已報工的時數 text_assign_time_entries_to_project: 指定已報工的時數至專案中 text_reassign_time_entries: '釿–°æŒ‡å®šå·²å ±å·¥çš„æ™‚數至此項目:' +text_user_wrote: '%s å…ˆå‰æåˆ°:' +text_enumeration_destroy_question: 'ç›®å‰æœ‰ %d 個物件使用此列舉值。' +text_enumeration_category_reassign_to: '釿–°è¨­å®šå…¶åˆ—舉值為:' +text_email_delivery_not_configured: "您尚未設定電å­éƒµä»¶å‚³é€æ–¹å¼ï¼Œå› æ­¤æé†’é¸é …已被åœç”¨ã€‚\n請在 config/email.yml 中設定 SMTP ä¹‹å¾Œï¼Œé‡æ–°å•Ÿå‹• Redmine,以啟用電å­éƒµä»¶æé†’é¸é …。" default_role_manager: 管ç†äººå“¡ default_role_developper: 開發人員 @@ -618,4 +633,7 @@ default_activity_development: 開發 enumeration_issue_priorities: 項目優先權 enumeration_doc_categories: 文件分類 enumeration_activities: 活動 (時間追蹤) -text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +field_parent_title: Parent page +label_issue_watchers: Watchers +setting_commit_logs_encoding: Commit messages encoding +button_quote: Quote diff --git a/groups/lang/zh.yml b/groups/lang/zh.yml index 12fb8cb3e..bfe551093 100644 --- a/groups/lang/zh.yml +++ b/groups/lang/zh.yml @@ -48,6 +48,7 @@ general_text_no: 'å¦' general_text_yes: '是' general_lang_name: 'Simplified Chinese (简体中文)' general_csv_separator: ',' +general_csv_decimal_separator: '.' general_csv_encoding: gb2312 general_pdf_encoding: gb2312 general_day_names: 星期一,星期二,星期三,星期四,星期五,星期六,星期日 @@ -91,6 +92,8 @@ mail_body_account_information_external: 您å¯ä»¥ä½¿ç”¨æ‚¨çš„ "%s" å¸å·æ¥ç™» mail_body_account_information: 您的å¸å·ä¿¡æ¯ mail_subject_account_activation_request: %så¸å·æ¿€æ´»è¯·æ±‚ mail_body_account_activation_request: '新用户(%sï¼‰å·²å®Œæˆæ³¨å†Œï¼Œæ­£åœ¨ç­‰å€™æ‚¨çš„审核:' +mail_subject_reminder: "%d 个问题需è¦å°½å¿«è§£å†³" +mail_body_reminder: "指派给您的 %d 个问题需è¦åœ¨ %d 天内完æˆï¼š" gui_validation_error: 1 个错误 gui_validation_error_plural: %d 个错误 @@ -179,6 +182,7 @@ field_time_zone: 时区 field_searchable: å¯ç”¨ä½œæœç´¢æ¡ä»¶ field_default_value: 默认值 field_comments_sorting: 显示注释 +field_parent_title: ä¸Šçº§é¡µé¢ setting_app_title: åº”ç”¨ç¨‹åºæ ‡é¢˜ setting_app_subtitle: 应用程åºå­æ ‡é¢˜ @@ -198,19 +202,23 @@ setting_default_projects_public: 新建项目默认为公开项目 setting_autofetch_changesets: 自动获å–程åºå˜æ›´ setting_sys_api_enabled: å¯ç”¨ç”¨äºŽç‰ˆæœ¬åº“管ç†çš„Web Service setting_commit_ref_keywords: 用于引用问题的关键字 -setting_commit_fix_keywords: 用于修订问题的关键字 +setting_commit_fix_keywords: 用于解决问题的关键字 setting_autologin: 自动登录 setting_date_format: æ—¥æœŸæ ¼å¼ setting_time_format: æ—¶é—´æ ¼å¼ setting_cross_project_issue_relations: å…许ä¸åŒé¡¹ç›®ä¹‹é—´çš„é—®é¢˜å…³è” setting_issue_list_default_columns: 问题列表中显示的默认列 setting_repositories_encodings: ç‰ˆæœ¬åº“ç¼–ç  +setting_commit_logs_encoding: æäº¤æ³¨é‡Šçš„ç¼–ç  setting_emails_footer: 邮件签å setting_protocol: åè®® setting_per_page_options: æ¯é¡µæ˜¾ç¤ºæ¡ç›®ä¸ªæ•°çš„设置 setting_user_format: ç”¨æˆ·æ˜¾ç¤ºæ ¼å¼ setting_activity_days_default: 在项目活动中显示的天数 setting_display_subprojects_issues: 在项目页é¢ä¸Šé»˜è®¤æ˜¾ç¤ºå­é¡¹ç›®çš„问题 +setting_enabled_scm: å¯ç”¨ SCM +setting_mail_handler_api_enabled: å¯ç”¨ç”¨äºŽæŽ¥æ”¶é‚®ä»¶çš„Web Service +setting_mail_handler_api_key: API key project_module_issue_tracking: 问题跟踪 project_module_time_tracking: 时间跟踪 @@ -257,9 +265,9 @@ label_issue_status_new: æ–°å»ºé—®é¢˜çŠ¶æ€ label_issue_category: 问题类别 label_issue_category_plural: 问题类别 label_issue_category_new: 新建问题类别 -label_custom_field: 自定义字段 -label_custom_field_plural: 自定义字段 -label_custom_field_new: 新建自定义字段 +label_custom_field: 自定义属性 +label_custom_field_plural: 自定义属性 +label_custom_field_new: 新建自定义属性 label_enumerations: 枚举值 label_enumeration_new: 新建枚举值 label_information: ä¿¡æ¯ @@ -291,6 +299,7 @@ label_auth_source: è®¤è¯æ¨¡å¼ label_auth_source_new: æ–°å»ºè®¤è¯æ¨¡å¼ label_auth_source_plural: è®¤è¯æ¨¡å¼ label_subproject_plural: å­é¡¹ç›® +label_and_its_subprojects: %s åŠå…¶å­é¡¹ç›® label_min_max_length: æœ€å° - 最大 长度 label_list: 列表 label_date: 日期 @@ -445,6 +454,7 @@ label_relation_new: æ–°å»ºå…³è” label_relation_delete: åˆ é™¤å…³è” label_relates_to: å…³è”到 label_duplicates: é‡å¤ +label_duplicated_by: 与其é‡å¤ label_blocks: 阻挡 label_blocked_by: 被阻挡 label_precedes: 优先于 @@ -510,6 +520,9 @@ label_preferences: 首选项 label_chronological_order: æŒ‰æ—¶é—´é¡ºåº label_reverse_chronological_order: 按时间顺åºï¼ˆå€’åºï¼‰ label_planning: 计划 +label_incoming_emails: 接收邮件 +label_generate_key: 生æˆä¸€ä¸ªkey +label_issue_watchers: 跟踪者 button_login: 登录 button_submit: æäº¤ @@ -554,9 +567,10 @@ status_registered: 已注册 status_locked: å·²é”定 text_select_mail_notifications: 选择需è¦å‘é€é‚®ä»¶é€šçŸ¥çš„动作 -text_regexp_info: eg. ^[A-Z0-9]+$ +text_regexp_info: 例如:^[A-Z0-9]+$ text_min_max_length_info: 0 表示没有é™åˆ¶ text_project_destroy_confirmation: 您确信è¦åˆ é™¤è¿™ä¸ªé¡¹ç›®ä»¥åŠæ‰€æœ‰ç›¸å…³çš„æ•°æ®å—? +text_subprojects_destroy_warning: '以下å­é¡¹ç›®ä¹Ÿå°†è¢«åŒæ—¶åˆ é™¤ï¼š%s' text_workflow_edit: 选择角色和跟踪标签æ¥ç¼–辑工作æµç¨‹ text_are_you_sure: 您确定? text_journal_changed: 从 %s å˜æ›´ä¸º %s @@ -572,7 +586,7 @@ text_length_between: 长度必须在 %d 到 %d 个字符之间。 text_tracker_no_workflow: 此跟踪标签未定义工作æµç¨‹ text_unallowed_characters: éžæ³•字符 text_comma_separated: å¯ä»¥ä½¿ç”¨å¤šä¸ªå€¼ï¼ˆç”¨é€—å·,分开)。 -text_issues_ref_in_commit_messages: 在æäº¤ä¿¡æ¯ä¸­å¼•用和修订问题 +text_issues_ref_in_commit_messages: 在æäº¤ä¿¡æ¯ä¸­å¼•用和解决问题 text_issue_added: 问题 %s 已由 %s æäº¤ã€‚ text_issue_updated: 问题 %s 已由 %s 更新。 text_wiki_destroy_confirmation: 您确定è¦åˆ é™¤è¿™ä¸ª wiki åŠå…¶æ‰€æœ‰å†…容å—? @@ -592,6 +606,10 @@ text_destroy_time_entries_question: 您è¦åˆ é™¤çš„问题已ç»ä¸ŠæŠ¥äº† %.02f text_destroy_time_entries: åˆ é™¤ä¸ŠæŠ¥çš„å·¥ä½œé‡ text_assign_time_entries_to_project: å°†å·²ä¸ŠæŠ¥çš„å·¥ä½œé‡æäº¤åˆ°é¡¹ç›®ä¸­ text_reassign_time_entries: 'å°†å·²ä¸ŠæŠ¥çš„å·¥ä½œé‡æŒ‡å®šåˆ°æ­¤é—®é¢˜ï¼š' +text_user_wrote: '%s 写到:' +text_enumeration_category_reassign_to: '将它们关è”到新的枚举值:' +text_enumeration_destroy_question: '%d 个对象被关è”到了这个枚举值。' +text_email_delivery_not_configured: "邮件傿•°å°šæœªé…置,因此邮件通知功能已被ç¦ç”¨ã€‚\n请在config/email.yml中é…置您的SMTPæœåŠ¡å™¨ä¿¡æ¯å¹¶é‡æ–°å¯åŠ¨ä»¥ä½¿å…¶ç”Ÿæ•ˆã€‚" default_role_manager: 管ç†äººå‘˜ default_role_developper: å¼€å‘人员 @@ -618,4 +636,4 @@ default_activity_development: å¼€å‘ enumeration_issue_priorities: 问题优先级 enumeration_doc_categories: 文档类别 enumeration_activities: 活动(时间跟踪) -text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' +button_quote: Quote diff --git a/groups/lib/SVG/Graph/Graph.rb b/groups/lib/SVG/Graph/Graph.rb index 403a0202b..a5e1ea732 100644 --- a/groups/lib/SVG/Graph/Graph.rb +++ b/groups/lib/SVG/Graph/Graph.rb @@ -829,7 +829,7 @@ module SVG @doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } + %q{"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"} ) if style_sheet && style_sheet != '' - @doc << ProcessingInstruction.new( "xml-stylesheet", + @doc << Instruction.new( "xml-stylesheet", %Q{href="#{style_sheet}" type="text/css"} ) end @root = @doc.add_element( "svg", { diff --git a/groups/lib/redcloth.rb b/groups/lib/redcloth.rb index 7e0c71839..df19de22d 100644 --- a/groups/lib/redcloth.rb +++ b/groups/lib/redcloth.rb @@ -299,6 +299,8 @@ class RedCloth < String hard_break text unless @lite_mode refs text + # need to do this before text is split by #blocks + block_textile_quotes text blocks text end inline text @@ -376,13 +378,13 @@ class RedCloth < String re = case rtype when :limit - /(^|[>\s]) + /(^|[>\s\(]) (#{rcq}) (#{C}) (?::(\S+?))? ([^\s\-].*?[^\s\-]|\w) #{rcq} - (?=[[:punct:]]|\s|$)/x + (?=[[:punct:]]|\s|\)|$)/x else /(#{rcq}) (#{C}) @@ -502,26 +504,19 @@ class RedCloth < String tatts = shelve( tatts ) if tatts rows = [] - fullrow. - split( /\|$/m ). - delete_if { |x| x.empty? }. - each do |row| - + fullrow.each_line do |row| ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m - cells = [] - #row.split( /\(?!\[\[[^\]])|(?![^\[]\]\])/ ).each do |cell| - row.split( /\|(?![^\[\|]*\]\])/ ).each do |cell| + row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell| + next if cell == '|' ctyp = 'd' ctyp = 'h' if cell =~ /^_/ catts = '' catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/ - unless cell.strip.empty? - catts = shelve( catts ) if catts - cells << "\t\t\t#{ cell }" - end + catts = shelve( catts ) if catts + cells << "\t\t\t#{ cell }" end ratts = shelve( ratts ) if ratts rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" @@ -576,6 +571,29 @@ class RedCloth < String lines.join( "\n" ) end end + + QUOTES_RE = /(^>+([^\n]*?)\n?)+/m + QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m + + def block_textile_quotes( text ) + text.gsub!( QUOTES_RE ) do |match| + lines = match.split( /\n/ ) + quotes = '' + indent = 0 + lines.each do |line| + line =~ QUOTES_CONTENT_RE + bq,content = $1, $2 + l = bq.count('>') + if l != indent + quotes << ("\n\n" + (l>indent ? '
    ' * (l-indent) : '
    ' * (indent-l)) + "\n\n") + indent = l + end + quotes << (content + "\n") + end + quotes << ("\n" + '' * indent + "\n\n") + quotes + end + end CODE_RE = /(\W) @ @@ -726,7 +744,7 @@ class RedCloth < String end MARKDOWN_RULE_RE = /^(#{ - ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' ) + ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' ) })$/ def block_markdown_rule( text ) @@ -764,11 +782,11 @@ class RedCloth < String ([\s\[{(]|[#{PUNCT}])? # $pre " # start (#{C}) # $atts - ([^"]+?) # $text + ([^"\n]+?) # $text \s? (?:\(([^)]+?)\)(?="))? # $title ": - (\S+?) # $url + ([\w\/]\S+?) # $url (\/)? # $slash ([^\w\/;]*?) # $post (?=<|\s|$) @@ -1131,10 +1149,10 @@ class RedCloth < String end end - ALLOWED_TAGS = %w(redpre pre code) + ALLOWED_TAGS = %w(redpre pre code notextile) def escape_html_tags(text) - text.gsub!(%r{<(\/?(\w+)[^>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "<#{$1}#{'>' if $3}" } + text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "<#{$1}#{'>' unless $3.blank?}" } end end diff --git a/groups/lib/redmine.rb b/groups/lib/redmine.rb index 2697e8f5f..33d33752b 100644 --- a/groups/lib/redmine.rb +++ b/groups/lib/redmine.rb @@ -1,5 +1,6 @@ require 'redmine/access_control' require 'redmine/menu_manager' +require 'redmine/activity' require 'redmine/mime_type' require 'redmine/core_ext' require 'redmine/themes' @@ -11,7 +12,7 @@ rescue LoadError # RMagick is not available end -REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git ) +REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem ) # Permissions Redmine::AccessControl.map do |map| @@ -32,9 +33,9 @@ Redmine::AccessControl.map do |map| :queries => :index, :reports => :issue_report}, :public => true map.permission :add_issues, {:issues => :new} - map.permission :edit_issues, {:issues => [:edit, :bulk_edit, :destroy_attachment]} + map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :destroy_attachment]} map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]} - map.permission :add_issue_notes, {:issues => :edit} + map.permission :add_issue_notes, {:issues => [:edit, :reply]} map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :move_issues, {:issues => :move}, :require => :loggedin @@ -45,6 +46,9 @@ Redmine::AccessControl.map do |map| # Gantt & calendar map.permission :view_gantt, :projects => :gantt map.permission :view_calendar, :projects => :calendar + # Watchers + map.permission :view_issue_watchers, {} + map.permission :add_issue_watchers, {:watchers => :new} end map.project_module :time_tracking do |map| @@ -76,6 +80,7 @@ Redmine::AccessControl.map do |map| map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special] map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment] + map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member end map.project_module :repository do |map| @@ -87,25 +92,25 @@ Redmine::AccessControl.map do |map| map.project_module :boards do |map| map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true - map.permission :add_messages, {:messages => [:new, :reply]} + map.permission :add_messages, {:messages => [:new, :reply, :quote]} map.permission :edit_messages, {:messages => :edit}, :require => :member map.permission :delete_messages, {:messages => :destroy}, :require => :member end end Redmine::MenuManager.map :top_menu do |menu| - menu.push :home, :home_url, :html => { :class => 'home' } + menu.push :home, :home_path, :html => { :class => 'home' } menu.push :my_page, { :controller => 'my', :action => 'page' }, :html => { :class => 'mypage' }, :if => Proc.new { User.current.logged? } menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural, :html => { :class => 'projects' } - menu.push :administration, { :controller => 'admin', :action => 'index' }, :html => { :class => 'admin' }, :if => Proc.new { User.current.admin? } - menu.push :help, Redmine::Info.help_url, :html => { :class => 'help' } + menu.push :administration, { :controller => 'admin', :action => 'index' }, :html => { :class => 'admin' }, :if => Proc.new { User.current.admin? }, :last => true + menu.push :help, Redmine::Info.help_url, :html => { :class => 'help' }, :last => true end Redmine::MenuManager.map :account_menu do |menu| - menu.push :login, :signin_url, :html => { :class => 'login' }, :if => Proc.new { !User.current.logged? } + menu.push :login, :signin_path, :html => { :class => 'login' }, :if => Proc.new { !User.current.logged? } menu.push :register, { :controller => 'account', :action => 'register' }, :html => { :class => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? } menu.push :my_account, { :controller => 'my', :action => 'account' }, :html => { :class => 'myaccount' }, :if => Proc.new { User.current.logged? } - menu.push :logout, :signout_url, :html => { :class => 'logout' }, :if => Proc.new { User.current.logged? } + menu.push :logout, :signout_path, :html => { :class => 'logout' }, :if => Proc.new { User.current.logged? } end Redmine::MenuManager.map :application_menu do |menu| @@ -129,5 +134,15 @@ Redmine::MenuManager.map :project_menu do |menu| menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural menu.push :repository, { :controller => 'repositories', :action => 'show' }, :if => Proc.new { |p| p.repository && !p.repository.new_record? } - menu.push :settings, { :controller => 'projects', :action => 'settings' } + menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true +end + +Redmine::Activity.map do |activity| + activity.register :issues, :class_name => %w(Issue Journal) + activity.register :changesets + activity.register :news + activity.register :documents, :class_name => %w(Document Attachment) + activity.register :files, :class_name => 'Attachment' + activity.register :wiki_pages, :class_name => 'WikiContent::Version', :default => false + activity.register :messages, :default => false end diff --git a/groups/lib/redmine/activity.rb b/groups/lib/redmine/activity.rb new file mode 100644 index 000000000..565a53f36 --- /dev/null +++ b/groups/lib/redmine/activity.rb @@ -0,0 +1,46 @@ +# Redmine - project management software +# Copyright (C) 2006-2008 Jean-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 Redmine + module Activity + + mattr_accessor :available_event_types, :default_event_types, :providers + + @@available_event_types = [] + @@default_event_types = [] + @@providers = Hash.new {|h,k| h[k]=[] } + + class << self + def map(&block) + yield self + end + + # Registers an activity provider + def register(event_type, options={}) + options.assert_valid_keys(:class_name, :default) + + event_type = event_type.to_s + providers = options[:class_name] || event_type.classify + providers = ([] << providers) unless providers.is_a?(Array) + + @@available_event_types << event_type unless @@available_event_types.include?(event_type) + @@default_event_types << event_type unless options[:default] == false + @@providers[event_type] += providers + end + end + end +end diff --git a/groups/lib/redmine/activity/fetcher.rb b/groups/lib/redmine/activity/fetcher.rb new file mode 100644 index 000000000..adaead564 --- /dev/null +++ b/groups/lib/redmine/activity/fetcher.rb @@ -0,0 +1,79 @@ +# Redmine - project management software +# Copyright (C) 2006-2008 Jean-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 Redmine + module Activity + # Class used to retrieve activity events + class Fetcher + attr_reader :user, :project, :scope + + # Needs to be unloaded in development mode + @@constantized_providers = Hash.new {|h,k| h[k] = Redmine::Activity.providers[k].collect {|t| t.constantize } } + + def initialize(user, options={}) + options.assert_valid_keys(:project, :with_subprojects) + @user = user + @project = options[:project] + @options = options + + @scope = event_types + end + + # Returns an array of available event types + def event_types + return @event_types unless @event_types.nil? + + @event_types = Redmine::Activity.available_event_types + @event_types = @event_types.select {|o| @user.allowed_to?("view_#{o}".to_sym, @project)} if @project + @event_types + end + + # Yields to filter the activity scope + def scope_select(&block) + @scope = @scope.select {|t| yield t } + end + + # Sets the scope + def scope=(s) + @scope = s & event_types + end + + # Resets the scope to the default scope + def default_scope! + @scope = Redmine::Activity.default_event_types + end + + # Returns an array of events for the given date range + def events(from, to) + e = [] + + @scope.each do |event_type| + constantized_providers(event_type).each do |provider| + e += provider.find_events(event_type, @user, from, to, @options) + end + end + e + end + + private + + def constantized_providers(event_type) + @@constantized_providers[event_type] + end + end + end +end diff --git a/groups/lib/redmine/core_ext/string/conversions.rb b/groups/lib/redmine/core_ext/string/conversions.rb index 7444445b0..41149f5ea 100644 --- a/groups/lib/redmine/core_ext/string/conversions.rb +++ b/groups/lib/redmine/core_ext/string/conversions.rb @@ -32,7 +32,7 @@ module Redmine #:nodoc: end # 2,5 => 2.5 s.gsub!(',', '.') - s.to_f + begin; Kernel.Float(s); rescue; nil; end end end end diff --git a/groups/lib/redmine/imap.rb b/groups/lib/redmine/imap.rb new file mode 100644 index 000000000..a6cd958cd --- /dev/null +++ b/groups/lib/redmine/imap.rb @@ -0,0 +1,51 @@ +# redMine - project management software +# Copyright (C) 2006-2008 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'net/imap' + +module Redmine + module IMAP + class << self + def check(imap_options={}, options={}) + host = imap_options[:host] || '127.0.0.1' + port = imap_options[:port] || '143' + ssl = !imap_options[:ssl].nil? + folder = imap_options[:folder] || 'INBOX' + + imap = Net::IMAP.new(host, port, ssl) + imap.login(imap_options[:username], imap_options[:password]) unless imap_options[:username].nil? + imap.select(folder) + imap.search(['NOT', 'SEEN']).each do |message_id| + msg = imap.fetch(message_id,'RFC822')[0].attr['RFC822'] + logger.debug "Receiving message #{message_id}" if logger && logger.debug? + if MailHandler.receive(msg, options) + imap.store(message_id, "+FLAGS", [:Seen, :Deleted]) + else + imap.store(message_id, "+FLAGS", [:Seen]) + end + end + imap.expunge + end + + private + + def logger + RAILS_DEFAULT_LOGGER + end + end + end +end diff --git a/groups/lib/redmine/menu_manager.rb b/groups/lib/redmine/menu_manager.rb index af54b41fe..f6431928e 100644 --- a/groups/lib/redmine/menu_manager.rb +++ b/groups/lib/redmine/menu_manager.rb @@ -80,9 +80,10 @@ module Redmine else item.url end - #url = (project && item.url.is_a?(Hash)) ? {item.param => project}.merge(item.url) : (item.url.is_a?(Symbol) ? send(item.url) : item.url) + caption = item.caption(project) + caption = l(caption) if caption.is_a?(Symbol) links << content_tag('li', - link_to(l(item.caption), url, (current_menu_item == item.name ? item.html_options.merge(:class => 'selected') : item.html_options))) + link_to(h(caption), url, (current_menu_item == item.name ? item.html_options.merge(:class => 'selected') : item.html_options))) end end links.empty? ? nil : content_tag('ul', links.join("\n")) @@ -91,11 +92,9 @@ module Redmine class << self def map(menu_name) - mapper = Mapper.new - yield mapper @items ||= {} - @items[menu_name.to_sym] ||= [] - @items[menu_name.to_sym] += mapper.items + mapper = Mapper.new(menu_name.to_sym, @items) + yield mapper end def items(menu_name) @@ -108,17 +107,46 @@ module Redmine end class Mapper + def initialize(menu, items) + items[menu] ||= [] + @menu = menu + @menu_items = items[menu] + end + + @@last_items_count = Hash.new {|h,k| h[k] = 0} + # Adds an item at the end of the menu. Available options: # * param: the parameter name that is used for the project id (default is :id) - # * if: a proc that is called before rendering the item, the item is displayed only if it returns true - # * caption: the localized string key that is used as the item label + # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true + # * caption that can be: + # * a localized string Symbol + # * a String + # * a Proc that can take the project as argument + # * before, after: specify where the menu item should be inserted (eg. :after => :activity) + # * last: menu item will stay at the end (eg. :last => true) # * html_options: a hash of html options that are passed to link_to def push(name, url, options={}) - items << MenuItem.new(name, url, options) + options = options.dup + + # menu item position + if before = options.delete(:before) + position = @menu_items.collect(&:name).index(before) + elsif after = options.delete(:after) + position = @menu_items.collect(&:name).index(after) + position += 1 unless position.nil? + elsif options.delete(:last) + position = @menu_items.size + @@last_items_count[@menu] += 1 + end + # default position + position ||= @menu_items.size - @@last_items_count[@menu] + + @menu_items.insert(position, MenuItem.new(name, url, options)) end - def items - @items ||= [] + # Removes a menu item + def delete(name) + @menu_items.delete_if {|i| i.name == name} end end @@ -133,13 +161,19 @@ module Redmine @url = url @condition = options[:if] @param = options[:param] || :id - @caption_key = options[:caption] + @caption = options[:caption] @html_options = options[:html] || {} end - def caption - # check if localized string exists on first render (after GLoc strings are loaded) - @caption ||= (@caption_key || (l_has_string?("label_#{@name}".to_sym) ? "label_#{@name}".to_sym : @name.to_s.humanize)) + def caption(project=nil) + if @caption.is_a?(Proc) + c = @caption.call(project).to_s + c = @name.to_s.humanize if c.blank? + c + else + # check if localized string exists on first render (after GLoc strings are loaded) + @caption_key ||= (@caption || (l_has_string?("label_#{@name}".to_sym) ? "label_#{@name}".to_sym : @name.to_s.humanize)) + end end end end diff --git a/groups/lib/redmine/platform.rb b/groups/lib/redmine/platform.rb new file mode 100644 index 000000000..f41b92f2e --- /dev/null +++ b/groups/lib/redmine/platform.rb @@ -0,0 +1,26 @@ +# Redmine - project management software +# Copyright (C) 2006-2008 Jean-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 Redmine + module Platform + class << self + def mswin? + (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i) + end + end + end +end diff --git a/groups/lib/redmine/plugin.rb b/groups/lib/redmine/plugin.rb index 36632c13e..cf6c194a2 100644 --- a/groups/lib/redmine/plugin.rb +++ b/groups/lib/redmine/plugin.rb @@ -116,6 +116,32 @@ module Redmine #:nodoc: self.instance_eval(&block) @project_module = nil end + + # Registers an activity provider. + # + # Options: + # * :class_name - one or more model(s) that provide these events (inferred from event_type by default) + # * :default - setting this option to false will make the events not displayed by default + # + # A model can provide several activity event types. + # + # Examples: + # register :news + # register :scrums, :class_name => 'Meeting' + # register :issues, :class_name => ['Issue', 'Journal'] + # + # Retrieving events: + # Associated model(s) must implement the find_events class method. + # ActiveRecord models can use acts_as_activity_provider as a way to implement this class method. + # + # The following call should return all the scrum events visible by current user that occured in the 5 last days: + # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today) + # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only + # + # Note that :view_scrums permission is required to view these events in the activity view. + def activity_provider(*args) + Redmine::Activity.register(*args) + end # Returns +true+ if the plugin can be configured. def configurable? diff --git a/groups/lib/redmine/scm/adapters/abstract_adapter.rb b/groups/lib/redmine/scm/adapters/abstract_adapter.rb index 2c254d48d..9f400880d 100644 --- a/groups/lib/redmine/scm/adapters/abstract_adapter.rb +++ b/groups/lib/redmine/scm/adapters/abstract_adapter.rb @@ -24,6 +24,29 @@ module Redmine end class AbstractAdapter #:nodoc: + class << self + # Returns the version of the scm client + # Eg: [1, 5, 0] or [] if unknown + def client_version + [] + end + + # Returns the version string of the scm client + # Eg: '1.5.0' or 'Unknown version' if unknown + def client_version_string + v = client_version || 'Unknown version' + v.is_a?(Array) ? v.join('.') : v.to_s + end + + # Returns true if the current client version is above + # or equals the given one + # If option is :unknown is set to true, it will return + # true if the client version is unknown + def client_version_above?(v, options={}) + ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown]) + end + end + def initialize(url, root_url=nil, login=nil, password=nil) @url = url @login = login if login && !login.empty? @@ -77,12 +100,16 @@ module Redmine def entries(path=nil, identifier=nil) return nil end + + def properties(path, identifier=nil) + return nil + end def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) return nil end - def diff(path, identifier_from, identifier_to=nil, type="inline") + def diff(path, identifier_from, identifier_to=nil) return nil end @@ -94,15 +121,30 @@ module Redmine path ||= '' (path[0,1]!="/") ? "/#{path}" : path end + + def with_trailling_slash(path) + path ||= '' + (path[-1,1] == "/") ? path : "#{path}/" + end + + def without_leading_slash(path) + path ||= '' + path.gsub(%r{^/+}, '') + end + + def without_trailling_slash(path) + path ||= '' + (path[-1,1] == "/") ? path[0..-2] : path + end def shell_quote(str) - if RUBY_PLATFORM =~ /mswin/ + if Redmine::Platform.mswin? '"' + str.gsub(/"/, '\\"') + '"' else "'" + str.gsub(/'/, "'\"'\"'") + "'" end end - + private def retrieve_root_url info = self.info @@ -116,10 +158,18 @@ module Redmine end def logger - RAILS_DEFAULT_LOGGER + self.class.logger end - + def shellout(cmd, &block) + self.class.shellout(cmd, &block) + end + + def self.logger + RAILS_DEFAULT_LOGGER + end + + def self.shellout(cmd, &block) logger.debug "Shelling out: #{cmd}" if logger && logger.debug? begin IO.popen(cmd, "r+") do |io| @@ -127,11 +177,22 @@ module Redmine block.call(io) if block_given? end rescue Errno::ENOENT => e + msg = strip_credential(e.message) # The command failed, log it and re-raise - logger.error("SCM command failed: #{cmd}\n with: #{e.message}") - raise CommandFailed.new(e.message) + logger.error("SCM command failed, make sure that your SCM binary (eg. svn) is in PATH (#{ENV['PATH']}): #{strip_credential(cmd)}\n with: #{msg}") + raise CommandFailed.new(msg) end end + + # Hides username/password in a given command + def self.strip_credential(cmd) + q = (Redmine::Platform.mswin? ? '"' : "'") + cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx') + end + + def strip_credential(cmd) + self.class.strip_credential(cmd) + end end class Entries < Array @@ -208,167 +269,7 @@ module Redmine end end - - # A line of Diff - class Diff - attr_accessor :nb_line_left - attr_accessor :line_left - attr_accessor :nb_line_right - attr_accessor :line_right - attr_accessor :type_diff_right - attr_accessor :type_diff_left - def initialize () - self.nb_line_left = '' - self.nb_line_right = '' - self.line_left = '' - self.line_right = '' - self.type_diff_right = '' - self.type_diff_left = '' - end - - def inspect - puts '### Start Line Diff ###' - puts self.nb_line_left - puts self.line_left - puts self.nb_line_right - puts self.line_right - end - end - - class DiffTableList < Array - def initialize (diff, type="inline") - diff_table = DiffTable.new type - diff.each do |line| - if line =~ /^(---|\+\+\+) (.*)$/ - self << diff_table if diff_table.length > 1 - diff_table = DiffTable.new type - end - a = diff_table.add_line line - end - self << diff_table unless diff_table.empty? - self - end - end - - # Class for create a Diff - class DiffTable < Hash - attr_reader :file_name, :line_num_l, :line_num_r - - # Initialize with a Diff file and the type of Diff View - # The type view must be inline or sbs (side_by_side) - def initialize(type="inline") - @parsing = false - @nb_line = 1 - @start = false - @before = 'same' - @second = true - @type = type - end - - # Function for add a line of this Diff - def add_line(line) - unless @parsing - if line =~ /^(---|\+\+\+) (.*)$/ - @file_name = $2 - return false - elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ - @line_num_l = $5.to_i - @line_num_r = $2.to_i - @parsing = true - end - else - if line =~ /^[^\+\-\s@\\]/ - self.delete(self.keys.sort.last) - @parsing = false - return false - elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ - @line_num_l = $5.to_i - @line_num_r = $2.to_i - else - @nb_line += 1 if parse_line(line, @type) - end - end - return true - end - - def inspect - puts '### DIFF TABLE ###' - puts "file : #{file_name}" - self.each do |d| - d.inspect - end - end - - private - # Test if is a Side By Side type - def sbs?(type, func) - if @start and type == "sbs" - if @before == func and @second - tmp_nb_line = @nb_line - self[tmp_nb_line] = Diff.new - else - @second = false - tmp_nb_line = @start - @start += 1 - @nb_line -= 1 - end - else - tmp_nb_line = @nb_line - @start = @nb_line - self[tmp_nb_line] = Diff.new - @second = true - end - unless self[tmp_nb_line] - @nb_line += 1 - self[tmp_nb_line] = Diff.new - else - self[tmp_nb_line] - end - end - - # Escape the HTML for the diff - def escapeHTML(line) - CGI.escapeHTML(line) - end - - def parse_line(line, type="inline") - if line[0, 1] == "+" - diff = sbs? type, 'add' - @before = 'add' - diff.line_left = escapeHTML line[1..-1] - diff.nb_line_left = @line_num_l - diff.type_diff_left = 'diff_in' - @line_num_l += 1 - true - elsif line[0, 1] == "-" - diff = sbs? type, 'remove' - @before = 'remove' - diff.line_right = escapeHTML line[1..-1] - diff.nb_line_right = @line_num_r - diff.type_diff_right = 'diff_out' - @line_num_r += 1 - true - elsif line[0, 1] =~ /\s/ - @before = 'same' - @start = false - diff = Diff.new - diff.line_right = escapeHTML line[1..-1] - diff.nb_line_right = @line_num_r - diff.line_left = escapeHTML line[1..-1] - diff.nb_line_left = @line_num_l - self[@nb_line] = diff - @line_num_l += 1 - @line_num_r += 1 - true - elsif line[0, 1] = "\\" - true - else - false - end - end - end - class Annotate attr_reader :lines, :revisions diff --git a/groups/lib/redmine/scm/adapters/bazaar_adapter.rb b/groups/lib/redmine/scm/adapters/bazaar_adapter.rb index 2225a627c..ff69e3e6b 100644 --- a/groups/lib/redmine/scm/adapters/bazaar_adapter.rb +++ b/groups/lib/redmine/scm/adapters/bazaar_adapter.rb @@ -132,7 +132,7 @@ module Redmine revisions end - def diff(path, identifier_from, identifier_to=nil, type="inline") + def diff(path, identifier_from, identifier_to=nil) path ||= '' if identifier_to identifier_to = identifier_to.to_i @@ -147,7 +147,7 @@ module Redmine end end #return nil if $? && $?.exitstatus != 0 - DiffTableList.new diff, type + diff end def cat(path, identifier=nil) diff --git a/groups/lib/redmine/scm/adapters/cvs_adapter.rb b/groups/lib/redmine/scm/adapters/cvs_adapter.rb index 37920b599..089a6b153 100644 --- a/groups/lib/redmine/scm/adapters/cvs_adapter.rb +++ b/groups/lib/redmine/scm/adapters/cvs_adapter.rb @@ -65,7 +65,7 @@ module Redmine entries = Entries.new cmd = "#{CVS_BIN} -d #{root_url} rls -ed" cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier - cmd << " #{path_with_project}" + cmd << " #{shell_quote path_with_project}" shellout(cmd) do |io| io.each_line(){|line| fields=line.chop.split('/',-1) @@ -110,7 +110,7 @@ module Redmine path_with_project="#{url}#{with_leading_slash(path)}" cmd = "#{CVS_BIN} -d #{root_url} rlog" cmd << " -d\">#{time_to_cvstime(identifier_from)}\"" if identifier_from - cmd << " #{path_with_project}" + cmd << " #{shell_quote path_with_project}" shellout(cmd) do |io| state="entry_start" @@ -227,10 +227,10 @@ module Redmine end end - def diff(path, identifier_from, identifier_to=nil, type="inline") + def diff(path, identifier_from, identifier_to=nil) logger.debug " diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" path_with_project="#{url}#{with_leading_slash(path)}" - cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{path_with_project}" + cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}" diff = [] shellout(cmd) do |io| io.each_line do |line| @@ -238,14 +238,16 @@ module Redmine end end return nil if $? && $?.exitstatus != 0 - DiffTableList.new diff, type + diff end def cat(path, identifier=nil) identifier = (identifier) ? identifier : "HEAD" logger.debug " cat path:'#{path}',identifier #{identifier}" path_with_project="#{url}#{with_leading_slash(path)}" - cmd = "#{CVS_BIN} -d #{root_url} co -r#{identifier} -p #{path_with_project}" + cmd = "#{CVS_BIN} -d #{root_url} co" + cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier + cmd << " -p #{shell_quote path_with_project}" cat = nil shellout(cmd) do |io| cat = io.read @@ -258,7 +260,7 @@ module Redmine identifier = (identifier) ? identifier : "HEAD" logger.debug " annotate path:'#{path}',identifier #{identifier}" path_with_project="#{url}#{with_leading_slash(path)}" - cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{path_with_project}" + cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{shell_quote path_with_project}" blame = Annotate.new shellout(cmd) do |io| io.each_line do |line| diff --git a/groups/lib/redmine/scm/adapters/darcs_adapter.rb b/groups/lib/redmine/scm/adapters/darcs_adapter.rb index a1d1867b1..4a5183f79 100644 --- a/groups/lib/redmine/scm/adapters/darcs_adapter.rb +++ b/groups/lib/redmine/scm/adapters/darcs_adapter.rb @@ -25,16 +25,36 @@ module Redmine # Darcs executable name DARCS_BIN = "darcs" + class << self + def client_version + @@client_version ||= (darcs_binary_version || []) + end + + def darcs_binary_version + cmd = "#{DARCS_BIN} --version" + version = nil + shellout(cmd) do |io| + # Read darcs version in first returned line + if m = io.gets.match(%r{((\d+\.)+\d+)}) + version = m[0].scan(%r{\d+}).collect(&:to_i) + end + end + return nil if $? && $?.exitstatus != 0 + version + end + end + def initialize(url, root_url=nil, login=nil, password=nil) @url = url @root_url = url end def supports_cat? - false + # cat supported in darcs 2.0.0 and higher + self.class.client_version_above?([2, 0, 0]) end - - # Get info about the svn repository + + # Get info about the darcs repository def info rev = revisions(nil,nil,nil,{:limit => 1}) rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil @@ -94,7 +114,7 @@ module Redmine revisions end - def diff(path, identifier_from, identifier_to=nil, type="inline") + def diff(path, identifier_from, identifier_to=nil) path = '*' if path.blank? cmd = "#{DARCS_BIN} diff --repodir #{@url}" if identifier_to.nil? @@ -111,9 +131,22 @@ module Redmine end end return nil if $? && $?.exitstatus != 0 - DiffTableList.new diff, type + diff end + def cat(path, identifier=nil) + cmd = "#{DARCS_BIN} show content --repodir #{@url}" + cmd << " --match \"hash #{identifier}\"" if identifier + cmd << " #{shell_quote path}" + cat = nil + shellout(cmd) do |io| + io.binmode + cat = io.read + end + return nil if $? && $?.exitstatus != 0 + cat + end + private def entry_from_xml(element, path_prefix) diff --git a/groups/lib/redmine/scm/adapters/filesystem_adapter.rb b/groups/lib/redmine/scm/adapters/filesystem_adapter.rb new file mode 100644 index 000000000..99296a090 --- /dev/null +++ b/groups/lib/redmine/scm/adapters/filesystem_adapter.rb @@ -0,0 +1,93 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# +# FileSystem adapter +# File written by Paul Rivier, at Demotera. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/abstract_adapter' +require 'find' + +module Redmine + module Scm + module Adapters + class FilesystemAdapter < AbstractAdapter + + + def initialize(url, root_url=nil, login=nil, password=nil) + @url = with_trailling_slash(url) + end + + def format_path_ends(path, leading=true, trailling=true) + path = leading ? with_leading_slash(path) : + without_leading_slash(path) + trailling ? with_trailling_slash(path) : + without_trailling_slash(path) + end + + def info + info = Info.new({:root_url => target(), + :lastrev => nil + }) + info + rescue CommandFailed + return nil + end + + def entries(path="", identifier=nil) + entries = Entries.new + Dir.new(target(path)).each do |e| + relative_path = format_path_ends((format_path_ends(path, + false, + true) + e), + false,false) + target = target(relative_path) + entries << + Entry.new({ :name => File.basename(e), + # below : list unreadable files, but dont link them. + :path => File.readable?(target) ? relative_path : "", + :kind => (File.directory?(target) ? 'dir' : 'file'), + :size => (File.directory?(target) ? nil : [File.size(target)].pack('l').unpack('L').first), + :lastrev => + Revision.new({:time => (File.mtime(target)).localtime, + }) + }) if File.exist?(target) and # paranoid test + %w{file directory}.include?(File.ftype(target)) and # avoid special types + not File.basename(e).match(/^\.+$/) # avoid . and .. + end + entries.sort_by_name + end + + def cat(path, identifier=nil) + File.new(target(path), "rb").read + end + + private + + # AbstractAdapter::target is implicitly made to quote paths. + # Here we do not shell-out, so we do not want quotes. + def target(path=nil) + #Prevent the use of .. + if path and !path.match(/(^|\/)\.\.(\/|$)/) + return "#{self.url}#{without_leading_slash(path)}" + end + return self.url + end + + end + end + end +end diff --git a/groups/lib/redmine/scm/adapters/git_adapter.rb b/groups/lib/redmine/scm/adapters/git_adapter.rb index 77604f283..30d624001 100644 --- a/groups/lib/redmine/scm/adapters/git_adapter.rb +++ b/groups/lib/redmine/scm/adapters/git_adapter.rb @@ -27,9 +27,13 @@ module Redmine # Get the revision of a particuliar file def get_rev (rev,path) - cmd="git --git-dir #{target('')} show #{shell_quote rev} -- #{shell_quote path}" if rev!='latest' and (! rev.nil?) - cmd="git --git-dir #{target('')} log -1 master -- #{shell_quote path}" if - rev=='latest' or rev.nil? + + if rev != 'latest' && !rev.nil? + cmd="#{GIT_BIN} --git-dir #{target('')} show #{shell_quote rev} -- #{shell_quote path}" + else + branch = shellout("#{GIT_BIN} --git-dir #{target('')} branch") { |io| io.grep(/\*/)[0].strip.match(/\* (.*)/)[1] } + cmd="#{GIT_BIN} --git-dir #{target('')} log -1 #{branch} -- #{shell_quote path}" + end rev=[] i=0 shellout(cmd) do |io| @@ -135,10 +139,10 @@ module Redmine def revisions(path, identifier_from, identifier_to, options={}) revisions = Revisions.new cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw " + cmd << " --reverse" if options[:reverse] cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit] cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from cmd << " #{shell_quote identifier_to} " if identifier_to - #cmd << " HEAD " if !identifier_to shellout(cmd) do |io| files=[] changeset = {} @@ -151,13 +155,18 @@ module Redmine value = $1 if (parsing_descr == 1 || parsing_descr == 2) parsing_descr = 0 - revisions << Revision.new({:identifier => changeset[:commit], - :scmid => changeset[:commit], - :author => changeset[:author], - :time => Time.parse(changeset[:date]), - :message => changeset[:description], - :paths => files - }) + revision = Revision.new({:identifier => changeset[:commit], + :scmid => changeset[:commit], + :author => changeset[:author], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => files + }) + if block_given? + yield revision + else + revisions << revision + end changeset = {} files = [] revno = revno + 1 @@ -186,21 +195,27 @@ module Redmine end end - revisions << Revision.new({:identifier => changeset[:commit], + if changeset[:commit] + revision = Revision.new({:identifier => changeset[:commit], :scmid => changeset[:commit], :author => changeset[:author], :time => Time.parse(changeset[:date]), :message => changeset[:description], :paths => files - }) if changeset[:commit] - + }) + if block_given? + yield revision + else + revisions << revision + end + end end return nil if $? && $?.exitstatus != 0 revisions end - def diff(path, identifier_from, identifier_to=nil, type="inline") + def diff(path, identifier_from, identifier_to=nil) path ||= '' if !identifier_to identifier_to = nil @@ -216,7 +231,7 @@ module Redmine end end return nil if $? && $?.exitstatus != 0 - DiffTableList.new diff, type + diff end def annotate(path, identifier=nil) diff --git a/groups/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl b/groups/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl new file mode 100644 index 000000000..b3029e6ff --- /dev/null +++ b/groups/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl @@ -0,0 +1,12 @@ +changeset = 'This template must be used with --debug option\n' +changeset_quiet = 'This template must be used with --debug option\n' +changeset_verbose = 'This template must be used with --debug option\n' +changeset_debug = '\n{author|escape}\n{date|isodate}\n\n{files}{file_adds}{file_dels}{file_copies}\n{desc|escape}\n{tags}\n\n' + +file = '{file|escape}\n' +file_add = '{file_add|escape}\n' +file_del = '{file_del|escape}\n' +file_copy = '{name|urlescape}\n' +tag = '{tag|escape}\n' +header='\n\n\n' +# footer="" \ No newline at end of file diff --git a/groups/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl b/groups/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl new file mode 100644 index 000000000..3eef85016 --- /dev/null +++ b/groups/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl @@ -0,0 +1,12 @@ +changeset = 'This template must be used with --debug option\n' +changeset_quiet = 'This template must be used with --debug option\n' +changeset_verbose = 'This template must be used with --debug option\n' +changeset_debug = '\n{author|escape}\n{date|isodate}\n\n{file_mods}{file_adds}{file_dels}{file_copies}\n{desc|escape}\n{tags}\n\n' + +file_mod = '{file_mod|escape}\n' +file_add = '{file_add|escape}\n' +file_del = '{file_del|escape}\n' +file_copy = '{name|urlescape}\n' +tag = '{tag|escape}\n' +header='\n\n\n' +# footer="" diff --git a/groups/lib/redmine/scm/adapters/mercurial_adapter.rb b/groups/lib/redmine/scm/adapters/mercurial_adapter.rb index 6f42dda06..4eed776d8 100644 --- a/groups/lib/redmine/scm/adapters/mercurial_adapter.rb +++ b/groups/lib/redmine/scm/adapters/mercurial_adapter.rb @@ -21,9 +21,45 @@ module Redmine module Scm module Adapters class MercurialAdapter < AbstractAdapter - + # Mercurial executable name HG_BIN = "hg" + TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial" + TEMPLATE_NAME = "hg-template" + TEMPLATE_EXTENSION = "tmpl" + + class << self + def client_version + @@client_version ||= (hgversion || []) + end + + def hgversion + # The hg version is expressed either as a + # release number (eg 0.9.5 or 1.0) or as a revision + # id composed of 12 hexa characters. + theversion = hgversion_from_command_line + if theversion.match(/^\d+(\.\d+)+/) + theversion.split(".").collect(&:to_i) + end + end + + def hgversion_from_command_line + %x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1] + end + + def template_path + @@template_path ||= template_path_for(client_version) + end + + def template_path_for(version) + if ((version <=> [0,9,5]) > 0) || version.empty? + ver = "1.0" + else + ver = "0.9.5" + end + "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" + end + end def info cmd = "#{HG_BIN} -R #{target('')} root" @@ -33,8 +69,8 @@ module Redmine end return nil if $? && $?.exitstatus != 0 info = Info.new({:root_url => root_url.chomp, - :lastrev => revisions(nil,nil,nil,{:limit => 1}).last - }) + :lastrev => revisions(nil,nil,nil,{:limit => 1}).last + }) info rescue CommandFailed return nil @@ -43,68 +79,78 @@ module Redmine def entries(path=nil, identifier=nil) path ||= '' entries = Entries.new - cmd = "#{HG_BIN} -R #{target('')} --cwd #{target(path)} locate" - cmd << " -r #{identifier.to_i}" if identifier - cmd << " " + shell_quote('glob:**') + cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate" + cmd << " -r " + (identifier ? identifier.to_s : "tip") + cmd << " " + shell_quote("path:#{path}") unless path.empty? shellout(cmd) do |io| io.each_line do |line| - e = line.chomp.split(%r{[\/\\]}) - entries << Entry.new({:name => e.first, - :path => (path.empty? ? e.first : "#{path}/#{e.first}"), - :kind => (e.size > 1 ? 'dir' : 'file'), - :lastrev => Revision.new - }) unless entries.detect{|entry| entry.name == e.first} + # HG uses antislashs as separator on Windows + line = line.gsub(/\\/, "/") + if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'') + e ||= line + e = e.chomp.split(%r{[\/\\]}) + entries << Entry.new({:name => e.first, + :path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"), + :kind => (e.size > 1 ? 'dir' : 'file'), + :lastrev => Revision.new + }) unless entries.detect{|entry| entry.name == e.first} + end end end return nil if $? && $?.exitstatus != 0 entries.sort_by_name end - - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + + # Fetch the revisions by using a template file that + # makes Mercurial produce a xml output. + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) revisions = Revisions.new - cmd = "#{HG_BIN} -v --encoding utf8 -R #{target('')} log" + cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{self.class.template_path}" if identifier_from && identifier_to cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}" elsif identifier_from cmd << " -r #{identifier_from.to_i}:" end cmd << " --limit #{options[:limit].to_i}" if options[:limit] + cmd << " #{path}" if path shellout(cmd) do |io| - changeset = {} - parsing_descr = false - line_feeds = 0 - - io.each_line do |line| - if line =~ /^(\w+):\s*(.*)$/ - key = $1 - value = $2 - if parsing_descr && line_feeds > 1 - parsing_descr = false - revisions << build_revision_from_changeset(changeset) - changeset = {} - end - if !parsing_descr - changeset.store key.to_sym, value - if $1 == "description" - parsing_descr = true - line_feeds = 0 - next + begin + # HG doesn't close the XML Document... + doc = REXML::Document.new(io.read << "") + doc.elements.each("log/logentry") do |logentry| + paths = [] + copies = logentry.get_elements('paths/path-copied') + logentry.elements.each("paths/path") do |path| + # Detect if the added file is a copy + if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text } + from_path = c.attributes['copyfrom-path'] + from_rev = logentry.attributes['revision'] end + paths << {:action => path.attributes['action'], + :path => "/#{path.text}", + :from_path => from_path ? "/#{from_path}" : nil, + :from_revision => from_rev ? from_rev : nil + } end + paths.sort! { |x,y| x[:path] <=> y[:path] } + + revisions << Revision.new({:identifier => logentry.attributes['revision'], + :scmid => logentry.attributes['node'], + :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), + :time => Time.parse(logentry.elements['date'].text).localtime, + :message => logentry.elements['msg'].text, + :paths => paths + }) end - if parsing_descr - changeset[:description] << line - line_feeds += 1 if line.chomp.empty? - end + rescue + logger.debug($!) end - # Add the last changeset if there is one left - revisions << build_revision_from_changeset(changeset) if changeset[:date] end return nil if $? && $?.exitstatus != 0 revisions end - def diff(path, identifier_from, identifier_to=nil, type="inline") + def diff(path, identifier_from, identifier_to=nil) path ||= '' if identifier_to identifier_to = identifier_to.to_i @@ -120,12 +166,12 @@ module Redmine end end return nil if $? && $?.exitstatus != 0 - DiffTableList.new diff, type + diff end def cat(path, identifier=nil) cmd = "#{HG_BIN} -R #{target('')} cat" - cmd << " -r #{identifier.to_i}" if identifier + cmd << " -r " + (identifier ? identifier.to_s : "tip") cmd << " #{target(path)}" cat = nil shellout(cmd) do |io| @@ -140,6 +186,7 @@ module Redmine path ||= '' cmd = "#{HG_BIN} -R #{target('')}" cmd << " annotate -n -u" + cmd << " -r " + (identifier ? identifier.to_s : "tip") cmd << " -r #{identifier.to_i}" if identifier cmd << " #{target(path)}" blame = Annotate.new @@ -152,47 +199,6 @@ module Redmine return nil if $? && $?.exitstatus != 0 blame end - - private - - # Builds a revision objet from the changeset returned by hg command - def build_revision_from_changeset(changeset) - rev_id = changeset[:changeset].to_s.split(':').first.to_i - - # Changes - paths = (rev_id == 0) ? - # Can't get changes for revision 0 with hg status - changeset[:files].to_s.split.collect{|path| {:action => 'A', :path => "/#{path}"}} : - status(rev_id) - - Revision.new({:identifier => rev_id, - :scmid => changeset[:changeset].to_s.split(':').last, - :author => changeset[:user], - :time => Time.parse(changeset[:date]), - :message => changeset[:description], - :paths => paths - }) - end - - # Returns the file changes for a given revision - def status(rev_id) - cmd = "#{HG_BIN} -R #{target('')} status --rev #{rev_id.to_i - 1}:#{rev_id.to_i}" - result = [] - shellout(cmd) do |io| - io.each_line do |line| - action, file = line.chomp.split - next unless action && file - file.gsub!("\\", "/") - case action - when 'R' - result << { :action => 'D' , :path => "/#{file}" } - else - result << { :action => action, :path => "/#{file}" } - end - end - end - result - end end end end diff --git a/groups/lib/redmine/scm/adapters/subversion_adapter.rb b/groups/lib/redmine/scm/adapters/subversion_adapter.rb index 40c7eb3f1..2b7f0192e 100644 --- a/groups/lib/redmine/scm/adapters/subversion_adapter.rb +++ b/groups/lib/redmine/scm/adapters/subversion_adapter.rb @@ -26,6 +26,25 @@ module Redmine # SVN executable name SVN_BIN = "svn" + class << self + def client_version + @@client_version ||= (svn_binary_version || []) + end + + def svn_binary_version + cmd = "#{SVN_BIN} --version" + version = nil + shellout(cmd) do |io| + # Read svn version in first returned line + if m = io.gets.match(%r{((\d+\.)+\d+)}) + version = m[0].scan(%r{\d+}).collect(&:to_i) + end + end + return nil if $? && $?.exitstatus != 0 + version + end + end + # Get info about the svn repository def info cmd = "#{SVN_BIN} info --xml #{target('')}" @@ -64,6 +83,9 @@ module Redmine begin doc = REXML::Document.new(output) doc.elements.each("lists/list/entry") do |entry| + # Skip directory if there is no commit date (usually that + # means that we don't have read access to it) + next if entry.attributes['kind'] == 'dir' && entry.elements['commit'].elements['date'].nil? entries << Entry.new({:name => entry.elements['name'].text, :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text), :kind => entry.attributes['kind'], @@ -84,7 +106,29 @@ module Redmine logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug? entries.sort_by_name end - + + def properties(path, identifier=nil) + # proplist xml output supported in svn 1.5.0 and higher + return nil unless self.class.client_version_above?([1, 5, 0]) + + identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" + cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}" + cmd << credentials_string + properties = {} + shellout(cmd) do |io| + output = io.read + begin + doc = REXML::Document.new(output) + doc.elements.each("properties/target/property") do |property| + properties[ property.attributes['name'] ] = property.text + end + rescue + end + end + return nil if $? && $?.exitstatus != 0 + properties + end + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) path ||= '' identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD" @@ -139,7 +183,7 @@ module Redmine end end return nil if $? && $?.exitstatus != 0 - DiffTableList.new diff, type + diff end def cat(path, identifier=nil) diff --git a/groups/lib/redmine/unified_diff.rb b/groups/lib/redmine/unified_diff.rb new file mode 100644 index 000000000..aa8994454 --- /dev/null +++ b/groups/lib/redmine/unified_diff.rb @@ -0,0 +1,178 @@ +# redMine - project management software +# Copyright (C) 2006-2008 Jean-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 Redmine + # Class used to parse unified diffs + class UnifiedDiff < Array + def initialize(diff, type="inline") + diff_table = DiffTable.new type + diff.each do |line| + if line =~ /^(---|\+\+\+) (.*)$/ + self << diff_table if diff_table.length > 1 + diff_table = DiffTable.new type + end + a = diff_table.add_line line + end + self << diff_table unless diff_table.empty? + self + end + end + + # Class that represents a file diff + class DiffTable < Hash + attr_reader :file_name, :line_num_l, :line_num_r + + # Initialize with a Diff file and the type of Diff View + # The type view must be inline or sbs (side_by_side) + def initialize(type="inline") + @parsing = false + @nb_line = 1 + @start = false + @before = 'same' + @second = true + @type = type + end + + # Function for add a line of this Diff + def add_line(line) + unless @parsing + if line =~ /^(---|\+\+\+) (.*)$/ + @file_name = $2 + return false + elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ + @line_num_l = $5.to_i + @line_num_r = $2.to_i + @parsing = true + end + else + if line =~ /^[^\+\-\s@\\]/ + @parsing = false + return false + elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ + @line_num_l = $5.to_i + @line_num_r = $2.to_i + else + @nb_line += 1 if parse_line(line, @type) + end + end + return true + end + + def inspect + puts '### DIFF TABLE ###' + puts "file : #{file_name}" + self.each do |d| + d.inspect + end + end + + private + # Test if is a Side By Side type + def sbs?(type, func) + if @start and type == "sbs" + if @before == func and @second + tmp_nb_line = @nb_line + self[tmp_nb_line] = Diff.new + else + @second = false + tmp_nb_line = @start + @start += 1 + @nb_line -= 1 + end + else + tmp_nb_line = @nb_line + @start = @nb_line + self[tmp_nb_line] = Diff.new + @second = true + end + unless self[tmp_nb_line] + @nb_line += 1 + self[tmp_nb_line] = Diff.new + else + self[tmp_nb_line] + end + end + + # Escape the HTML for the diff + def escapeHTML(line) + CGI.escapeHTML(line) + end + + def parse_line(line, type="inline") + if line[0, 1] == "+" + diff = sbs? type, 'add' + @before = 'add' + diff.line_left = escapeHTML line[1..-1] + diff.nb_line_left = @line_num_l + diff.type_diff_left = 'diff_in' + @line_num_l += 1 + true + elsif line[0, 1] == "-" + diff = sbs? type, 'remove' + @before = 'remove' + diff.line_right = escapeHTML line[1..-1] + diff.nb_line_right = @line_num_r + diff.type_diff_right = 'diff_out' + @line_num_r += 1 + true + elsif line[0, 1] =~ /\s/ + @before = 'same' + @start = false + diff = Diff.new + diff.line_right = escapeHTML line[1..-1] + diff.nb_line_right = @line_num_r + diff.line_left = escapeHTML line[1..-1] + diff.nb_line_left = @line_num_l + self[@nb_line] = diff + @line_num_l += 1 + @line_num_r += 1 + true + elsif line[0, 1] = "\\" + true + else + false + end + end + end + + # A line of diff + class Diff + attr_accessor :nb_line_left + attr_accessor :line_left + attr_accessor :nb_line_right + attr_accessor :line_right + attr_accessor :type_diff_right + attr_accessor :type_diff_left + + def initialize() + self.nb_line_left = '' + self.nb_line_right = '' + self.line_left = '' + self.line_right = '' + self.type_diff_right = '' + self.type_diff_left = '' + end + + def inspect + puts '### Start Line Diff ###' + puts self.nb_line_left + puts self.line_left + puts self.nb_line_right + puts self.line_right + end + end +end diff --git a/groups/lib/redmine/wiki_formatting.rb b/groups/lib/redmine/wiki_formatting.rb index 79da2a38a..8c18d547f 100644 --- a/groups/lib/redmine/wiki_formatting.rb +++ b/groups/lib/redmine/wiki_formatting.rb @@ -26,7 +26,7 @@ module Redmine class TextileFormatter < RedCloth # auto_link rule after textile rules so that it doesn't break !image_url! tags - RULES = [:textile, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros] + RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros] def initialize(*args) super @@ -45,7 +45,7 @@ module Redmine # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. # http://code.whytheluckystiff.net/redcloth/changeset/128 def hard_break( text ) - text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1
    " ) if hard_breaks + text.gsub!( /(.)\n(?!\n|\Z|>| *(>? *[#*=]+(\s|$)|[{|]))/, "\\1
    \n" ) if hard_breaks end # Patch to add code highlighting support to RedCloth @@ -56,7 +56,7 @@ module Redmine content = @pre_list[$1.to_i] if content.match(/\s?(.+)/m) content = "" + - CodeRay.scan($2, $1).html(:escape => false, :line_numbers => :inline) + CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline) end content end @@ -65,10 +65,22 @@ module Redmine # Patch to add 'table of content' support to RedCloth def textile_p_withtoc(tag, atts, cite, content) - if tag =~ /^h(\d)$/ - @toc << [$1.to_i, content] + # removes wiki links from the item + toc_item = content.gsub(/(\[\[|\]\])/, '') + # removes styles + # eg. %{color:red}Triggers% => Triggers + toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1' + + # replaces non word caracters by dashes + anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') + + unless anchor.blank? + if tag =~ /^h(\d)$/ + @toc << [$1.to_i, anchor, toc_item] + end + atts << " id=\"#{anchor}\"" + content = content + "" end - content = "" + content textile_p(tag, atts, cite, content) end @@ -81,13 +93,12 @@ module Redmine div_class = 'toc' div_class << ' right' if $1 == '>' div_class << ' left' if $1 == '<' - out = "
    " - @toc.each_with_index do |heading, index| - # remove wiki links from the item - toc_item = heading.last.gsub(/(\[\[|\]\])/, '') - out << "#{toc_item}" + out = "
      " + @toc.each do |heading| + level, anchor, toc_item = heading + out << "
    • #{toc_item}
    • \n" end - out << '
    ' + out << '' out end end @@ -126,6 +137,7 @@ module Redmine ) ( (?:https?://)| # protocol spec, or + (?:ftp://)| (?:www\.) # www.* ) ( @@ -149,12 +161,16 @@ module Redmine end end end - + # Turns all email addresses into clickable links (code from Rails). def inline_auto_mailto(text) text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do - text = $1 - %{} + mail = $1 + if text.match(/]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/) + mail + else + %{} + end end end end diff --git a/groups/lib/redmine/wiki_formatting/macros.rb b/groups/lib/redmine/wiki_formatting/macros.rb index 0848aee4e..adfc590e4 100644 --- a/groups/lib/redmine/wiki_formatting/macros.rb +++ b/groups/lib/redmine/wiki_formatting/macros.rb @@ -77,6 +77,12 @@ module Redmine content_tag('dl', out) end + desc "Displays a list of child pages." + macro :child_pages do |obj, args| + raise 'This macro applies to wiki pages only.' unless obj.is_a?(WikiContent) + render_page_hierarchy(obj.page.descendants.group_by(&:parent_id), obj.page.id) + end + desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}" macro :include do |obj, args| project = @project diff --git a/groups/lib/tabular_form_builder.rb b/groups/lib/tabular_form_builder.rb index 5b331fe3f..88e35a6d2 100644 --- a/groups/lib/tabular_form_builder.rb +++ b/groups/lib/tabular_form_builder.rb @@ -22,7 +22,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder def initialize(object_name, object, template, options, proc) set_language_if_valid options.delete(:lang) - @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc + super end (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector| diff --git a/groups/lib/tasks/email.rake b/groups/lib/tasks/email.rake new file mode 100644 index 000000000..a37b3e197 --- /dev/null +++ b/groups/lib/tasks/email.rake @@ -0,0 +1,105 @@ +# redMine - project management software +# Copyright (C) 2006-2008 Jean-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. + +namespace :redmine do + namespace :email do + + desc <<-END_DESC +Read an email from standard input. + +Issue attributes control options: + project=PROJECT identifier of the target project + tracker=TRACKER name of the target tracker + category=CATEGORY name of the target category + priority=PRIORITY name of the target priority + allow_override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + rake redmine:email:read RAILS_ENV="production" < raw_email + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: + rake redmine:email:read RAILS_ENV="production" \\ + project=foo \\ + tracker=bug \\ + allow_override=tracker,priority < raw_email +END_DESC + + task :read => :environment do + options = { :issue => {} } + %w(project tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } + options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] + + MailHandler.receive(STDIN.read, options) + end + + desc <<-END_DESC +Read emails from an IMAP server. + +Available IMAP options: + host=HOST IMAP server host (default: 127.0.0.1) + port=PORT IMAP server port (default: 143) + ssl=SSL Use SSL? (default: false) + username=USERNAME IMAP account + password=PASSWORD IMAP password + folder=FOLDER IMAP folder to read (default: INBOX) + +Issue attributes control options: + project=PROJECT identifier of the target project + tracker=TRACKER name of the target tracker + category=CATEGORY name of the target category + priority=PRIORITY name of the target priority + allow_override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + + rake redmine:email:receive_iamp RAILS_ENV="production" \\ + host=imap.foo.bar username=redmine@example.net password=xxx + + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: + + rake redmine:email:receive_iamp RAILS_ENV="production" \\ + host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\ + project=foo \\ + tracker=bug \\ + allow_override=tracker,priority +END_DESC + + task :receive_imap => :environment do + imap_options = {:host => ENV['host'], + :port => ENV['port'], + :ssl => ENV['ssl'], + :username => ENV['username'], + :password => ENV['password'], + :folder => ENV['folder']} + + options = { :issue => {} } + %w(project tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } + options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] + + Redmine::IMAP.check(imap_options, options) + end + end +end diff --git a/groups/lib/tasks/migrate_from_trac.rake b/groups/lib/tasks/migrate_from_trac.rake index 7fe1f09ac..880964ff8 100644 --- a/groups/lib/tasks/migrate_from_trac.rake +++ b/groups/lib/tasks/migrate_from_trac.rake @@ -92,12 +92,17 @@ namespace :redmine do set_table_name :milestone def due - if read_attribute(:due) > 0 + if read_attribute(:due) && read_attribute(:due) > 0 Time.at(read_attribute(:due)).to_date else nil end end + + def description + # Attribute is named descr in Trac v0.8.x + has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description) + end end class TracTicketCustom < ActiveRecord::Base @@ -126,6 +131,10 @@ namespace :redmine do File.open("#{trac_fullpath}", 'rb').read end + def description + read_attribute(:description).to_s.slice(0,255) + end + private def trac_fullpath attachment_type = read_attribute(:type) @@ -140,7 +149,10 @@ namespace :redmine do # ticket changes: only migrate status changes and comments has_many :changes, :class_name => "TracTicketChange", :foreign_key => :ticket - has_many :attachments, :class_name => "TracAttachment", :foreign_key => :id, :conditions => "#{TracMigrate::TracAttachment.table_name}.type = 'ticket'" + has_many :attachments, :class_name => "TracAttachment", + :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" + + " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'ticket'" + + ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\'' has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket def ticket_type @@ -177,7 +189,10 @@ namespace :redmine do set_table_name :wiki set_primary_key :name - has_many :attachments, :class_name => "TracAttachment", :foreign_key => :id, :conditions => "#{TracMigrate::TracAttachment.table_name}.type = 'wiki'" + has_many :attachments, :class_name => "TracAttachment", + :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" + + " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'wiki'" + + ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\'' def self.columns # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0) @@ -191,6 +206,10 @@ namespace :redmine do set_table_name :permission end + class TracSessionAttribute < ActiveRecord::Base + set_table_name :session_attribute + end + def self.find_or_create_user(username, project_member = false) return User.anonymous if username.blank? @@ -198,10 +217,23 @@ namespace :redmine do if !u # Create a new user if not found mail = username[0,limit_for(User, 'mail')] + if mail_attr = TracSessionAttribute.find_by_sid_and_name(username, 'email') + mail = mail_attr.value + end mail = "#{mail}@foo.bar" unless mail.include?("@") - u = User.new :firstname => username[0,limit_for(User, 'firstname')].gsub(/[^\w\s\'\-]/i, '-'), - :lastname => '-', - :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-') + + name = username + if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name') + name = name_attr.value + end + name =~ (/(.*)(\s+\w+)?/) + fn = $1.strip + ln = ($2 || '-').strip + + u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'), + :firstname => fn[0, limit_for(User, 'firstname')].gsub(/[^\w\s\'\-]/i, '-'), + :lastname => ln[0, limit_for(User, 'lastname')].gsub(/[^\w\s\'\-]/i, '-') + u.login = username[0,limit_for(User, 'login')].gsub(/[^a-z0-9_\-@\.]/i, '-') u.password = 'trac' u.admin = true if TracPermission.find_by_username_and_action(username, 'admin') @@ -233,7 +265,8 @@ namespace :redmine do text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} - text = text.gsub(/\[wiki:([^\s\]]+).*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} + text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} + text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"} # Links to pages UsingJustWikiCaps text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]') @@ -408,6 +441,7 @@ namespace :redmine do a.file = attachment a.author = find_or_create_user(attachment.author) a.container = i + a.description = attachment.description migrated_ticket_attachments += 1 if a.save end @@ -456,6 +490,7 @@ namespace :redmine do a = Attachment.new :created_on => attachment.time a.file = attachment a.author = find_or_create_user(attachment.author) + a.description = attachment.description a.container = p migrated_wiki_attachments += 1 if a.save end diff --git a/groups/lib/tasks/reminder.rake b/groups/lib/tasks/reminder.rake new file mode 100644 index 000000000..73844fb79 --- /dev/null +++ b/groups/lib/tasks/reminder.rake @@ -0,0 +1,39 @@ +# redMine - project management software +# Copyright (C) 2008 Jean-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. + +desc <<-END_DESC +Send reminders about issues due in the next days. + +Available options: + * days => number of days to remind about (defaults to 7) + * tracker => id of tracker (defaults to all trackers) + * project => id or identifier of project (defaults to all projects) + +Example: + rake redmine:send_reminders days=7 RAILS_ENV="production" +END_DESC + +namespace :redmine do + task :send_reminders => :environment do + options = {} + options[:days] = ENV['days'].to_i if ENV['days'] + options[:project] = ENV['project'] if ENV['project'] + options[:tracker] = ENV['tracker'].to_i if ENV['tracker'] + + Mailer.reminders(options) + end +end diff --git a/groups/lib/tasks/testing.rake b/groups/lib/tasks/testing.rake new file mode 100644 index 000000000..42f756f68 --- /dev/null +++ b/groups/lib/tasks/testing.rake @@ -0,0 +1,46 @@ +### From http://svn.geekdaily.org/public/rails/plugins/generally_useful/tasks/coverage_via_rcov.rake + +### Inspired by http://blog.labratz.net/articles/2006/12/2/a-rake-task-for-rcov +begin + require 'rcov/rcovtask' + + rcov_options = "--rails --aggregate test/coverage.data --exclude '/gems/'" + + namespace :test do + desc "Aggregate code coverage for all tests" + Rcov::RcovTask.new('coverage') do |t| + t.libs << 'test' + t.test_files = FileList['test/{unit,integration,functional}/*_test.rb'] + t.verbose = true + t.rcov_opts << rcov_options + end + + namespace :coverage do + desc "Delete coverage test data" + task :clean do + rm_f "test/coverage.data" + rm_rf "test/coverage" + end + + desc "Aggregate code coverage for all tests with HTML output" + Rcov::RcovTask.new('html') do |t| + t.libs << 'test' + t.test_files = FileList['test/{unit,integration,functional}/*_test.rb'] + t.output_dir = "test/coverage" + t.verbose = true + t.rcov_opts << rcov_options + end + + desc "Open the HTML coverage report" + task :show_results do + system "open test/coverage/index.html" + end + + task :full => "test:coverage:clean" + task :full => "test:coverage:html" + task :full => "test:coverage:show_results" + end + end +rescue LoadError + # rcov not available +end diff --git a/groups/public/help/wiki_syntax.html b/groups/public/help/wiki_syntax.html index 6a0e10022..846fe1bf7 100644 --- a/groups/public/help/wiki_syntax.html +++ b/groups/public/help/wiki_syntax.html @@ -22,13 +22,13 @@ table td h3 { font-size: 1.2em; text-align: left; } - - - - + + + + - - + - - + + - - - + + + - + - +
    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
    +
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
      lines
      of code
    @@ -36,27 +36,27 @@ table td h3 { font-size: 1.2em; text-align: left; }
     
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    Redmine links
    Link to a Wiki page[[Wiki page]]Wiki page
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:"f30e13e43"f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    Image!image_url!
    !attached_image!
    diff --git a/groups/public/images/bullet_toggle_minus.png b/groups/public/images/bullet_toggle_minus.png new file mode 100644 index 000000000..5ce75938f Binary files /dev/null and b/groups/public/images/bullet_toggle_minus.png differ diff --git a/groups/public/images/bullet_toggle_plus.png b/groups/public/images/bullet_toggle_plus.png new file mode 100644 index 000000000..b3603d30a Binary files /dev/null and b/groups/public/images/bullet_toggle_plus.png differ diff --git a/groups/public/images/comment.png b/groups/public/images/comment.png new file mode 100644 index 000000000..7bc9233ea Binary files /dev/null and b/groups/public/images/comment.png differ diff --git a/groups/public/images/expand.png b/groups/public/images/expand.png deleted file mode 100644 index 3e3aaa441..000000000 Binary files a/groups/public/images/expand.png and /dev/null differ diff --git a/groups/public/images/jstoolbar/bt_bq.png b/groups/public/images/jstoolbar/bt_bq.png new file mode 100644 index 000000000..c3af4e07f Binary files /dev/null and b/groups/public/images/jstoolbar/bt_bq.png differ diff --git a/groups/public/images/jstoolbar/bt_bq_remove.png b/groups/public/images/jstoolbar/bt_bq_remove.png new file mode 100644 index 000000000..05d5ff7c7 Binary files /dev/null and b/groups/public/images/jstoolbar/bt_bq_remove.png differ diff --git a/groups/public/images/locked.png b/groups/public/images/locked.png index c2789e35c..82d629961 100644 Binary files a/groups/public/images/locked.png and b/groups/public/images/locked.png differ diff --git a/groups/public/images/projects.png b/groups/public/images/projects.png index 244c896f0..073c7219d 100644 Binary files a/groups/public/images/projects.png and b/groups/public/images/projects.png differ diff --git a/groups/public/images/ticket_note.png b/groups/public/images/ticket_note.png new file mode 100644 index 000000000..c69db223f Binary files /dev/null and b/groups/public/images/ticket_note.png differ diff --git a/groups/public/images/unlock.png b/groups/public/images/unlock.png index e0d414978..f15fead72 100644 Binary files a/groups/public/images/unlock.png and b/groups/public/images/unlock.png differ diff --git a/groups/public/javascripts/application.js b/groups/public/javascripts/application.js index 4e8849842..3becbeb21 100644 --- a/groups/public/javascripts/application.js +++ b/groups/public/javascripts/application.js @@ -2,14 +2,27 @@ Copyright (C) 2006-2008 Jean-Philippe Lang */ 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; + var els = Element.descendants(id); + for (var i = 0; i < els.length; i++) { + if (els[i].disabled==false) { + els[i].checked = checked; } } } +function toggleCheckboxesBySelector(selector) { + boxes = $$(selector); + var all_checked = true; + for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } } + for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; } +} + +function showAndScrollTo(id, focus) { + Element.show(id); + if (focus!=null) { Form.Element.focus(focus); } + Element.scrollTo(id); +} + var fileFieldCount = 1; function addFileField() { @@ -56,7 +69,7 @@ function setPredecessorFieldsVisibility() { function promptToRemote(text, param, url) { value = prompt(text + ':'); if (value) { - new Ajax.Request(url + '?' + param + '=' + value, {asynchronous:true, evalScripts:true}); + new Ajax.Request(url + '?' + param + '=' + encodeURIComponent(value), {asynchronous:true, evalScripts:true}); return false; } } @@ -107,6 +120,15 @@ function scmEntryLoaded(id) { Element.removeClassName(id, 'loading'); } +function randomKey(size) { + var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'); + var key = ''; + for (i = 0; i < size; i++) { + key += chars[Math.floor(Math.random() * chars.length)]; + } + return key; +} + /* shows and hides ajax indicator */ Ajax.Responders.register({ onCreate: function(){ diff --git a/groups/public/javascripts/calendar/lang/calendar-he.js b/groups/public/javascripts/calendar/lang/calendar-he.js index bd92e0073..9d4c87db0 100644 --- a/groups/public/javascripts/calendar/lang/calendar-he.js +++ b/groups/public/javascripts/calendar/lang/calendar-he.js @@ -113,7 +113,7 @@ Calendar._TT["DAY_FIRST"] = "הצג %s קוד×"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. -Calendar._TT["WEEKEND"] = "6,7"; +Calendar._TT["WEEKEND"] = "5,6"; Calendar._TT["CLOSE"] = "סגור"; Calendar._TT["TODAY"] = "היו×"; diff --git a/groups/public/javascripts/calendar/lang/calendar-hu.js b/groups/public/javascripts/calendar/lang/calendar-hu.js new file mode 100644 index 000000000..0e219c123 --- /dev/null +++ b/groups/public/javascripts/calendar/lang/calendar-hu.js @@ -0,0 +1,127 @@ +// ** I18N + +// Calendar HU language +// Author: Takács Gábor +// Encoding: UTF-8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Vasárnap", + "HétfÅ‘", + "Kedd", + "Szerda", + "Csütörtök", + "Péntek", + "Szombat", + "Vasárnap"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Vas", + "Hét", + "Ked", + "Sze", + "Csü", + "Pén", + "Szo", + "Vas"); + +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 1; + +// full month names +Calendar._MN = new Array +("Január", + "Február", + "Március", + "Ãprilis", + "Május", + "Június", + "Július", + "Augusztus", + "Szeptember", + "Október", + "November", + "December"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Már", + "Ãpr", + "Máj", + "Jún", + "Júl", + "Aug", + "Szep", + "Okt", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "A naptár leírása"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + +Calendar._TT["PREV_YEAR"] = "ElÅ‘zÅ‘ év (nyomvatart = menü)"; +Calendar._TT["PREV_MONTH"] = "ElÅ‘zÅ‘ hónap (nyomvatart = menü)"; +Calendar._TT["GO_TODAY"] = "Irány a Ma"; +Calendar._TT["NEXT_MONTH"] = "KövetkezÅ‘ hónap (nyomvatart = menü)"; +Calendar._TT["NEXT_YEAR"] = "KövetkezÅ‘ év (nyomvatart = menü)"; +Calendar._TT["SEL_DATE"] = "Válasszon dátumot"; +Calendar._TT["DRAG_TO_MOVE"] = "Fogd és vidd"; +Calendar._TT["PART_TODAY"] = " (ma)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "%s megjelenítése elsÅ‘ként"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Bezár"; +Calendar._TT["TODAY"] = "Ma"; +Calendar._TT["TIME_PART"] = "(Shift-)Click vagy húzd az érték változtatásához"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y.%m.%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%B %e, %A"; + +Calendar._TT["WK"] = "hét"; +Calendar._TT["TIME"] = "IdÅ‘:"; diff --git a/groups/public/javascripts/calendar/lang/calendar-pt-br.js b/groups/public/javascripts/calendar/lang/calendar-pt-br.js index 5d4d014ce..bf7734ab3 100644 --- a/groups/public/javascripts/calendar/lang/calendar-pt-br.js +++ b/groups/public/javascripts/calendar/lang/calendar-pt-br.js @@ -2,7 +2,8 @@ // Calendar pt_BR language // Author: Adalberto Machado, -// Encoding: any +// Review: Alexandre da Silva, +// Encoding: UTF-8 // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that @@ -13,7 +14,7 @@ Calendar._DN = new Array ("Domingo", "Segunda", - "Terca", + "Terça", "Quarta", "Quinta", "Sexta", @@ -45,13 +46,13 @@ Calendar._SDN = new Array // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. -Calendar._FD = 1; +Calendar._FD = 0; // full month names Calendar._MN = new Array ("Janeiro", "Fevereiro", - "Marco", + "Março", "Abril", "Maio", "Junho", @@ -79,29 +80,30 @@ Calendar._SMN = new Array // tooltips Calendar._TT = {}; -Calendar._TT["INFO"] = "Sobre o calendario"; +Calendar._TT["INFO"] = "Sobre o calendário"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) -"Ultima versao visite: http://www.dynarch.com/projects/calendar/\n" + -"Distribuido sobre GNU LGPL. Veja http://gnu.org/licenses/lgpl.html para detalhes." + +"Última versão visite: http://www.dynarch.com/projects/calendar/\n" + +"Distribuído sobre GNU LGPL. Veja http://gnu.org/licenses/lgpl.html para detalhes." + "\n\n" + -"Selecao de data:\n" + -"- Use os botoes \xab, \xbb para selecionar o ano\n" + -"- Use os botoes " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para selecionar o mes\n" + -"- Segure o botao do mouse em qualquer um desses botoes para selecao rapida."; +"Seleção de data:\n" + +"- Use os botões \xab, \xbb para selecionar o ano\n" + +"- Use os botões " + String.fromCharCode(0x2039) + ", " + +String.fromCharCode(0x203a) + " para selecionar o mês\n" + +"- Segure o botão do mouse em qualquer um desses botões para seleção rápida."; Calendar._TT["ABOUT_TIME"] = "\n\n" + -"Selecao de hora:\n" + +"Seleção de hora:\n" + "- Clique em qualquer parte da hora para incrementar\n" + "- ou Shift-click para decrementar\n" + -"- ou clique e segure para selecao rapida."; +"- ou clique e segure para seleção rápida."; Calendar._TT["PREV_YEAR"] = "Ant. ano (segure para menu)"; -Calendar._TT["PREV_MONTH"] = "Ant. mes (segure para menu)"; +Calendar._TT["PREV_MONTH"] = "Ant. mês (segure para menu)"; Calendar._TT["GO_TODAY"] = "Hoje"; -Calendar._TT["NEXT_MONTH"] = "Prox. mes (segure para menu)"; -Calendar._TT["NEXT_YEAR"] = "Prox. ano (segure para menu)"; +Calendar._TT["NEXT_MONTH"] = "Próx. mes (segure para menu)"; +Calendar._TT["NEXT_YEAR"] = "Próx. ano (segure para menu)"; Calendar._TT["SEL_DATE"] = "Selecione a data"; Calendar._TT["DRAG_TO_MOVE"] = "Arraste para mover"; Calendar._TT["PART_TODAY"] = " (hoje)"; diff --git a/groups/public/javascripts/calendar/lang/calendar-th.js b/groups/public/javascripts/calendar/lang/calendar-th.js new file mode 100644 index 000000000..dc4809e52 --- /dev/null +++ b/groups/public/javascripts/calendar/lang/calendar-th.js @@ -0,0 +1,127 @@ +// ** I18N + +// Calendar EN language +// Author: Gampol Thitinilnithi, +// Encoding: UTF-8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("อาทิตย์", + "จันทร์", + "อังคาร", + "พุธ", + "พฤหัสบดี", + "ศุà¸à¸£à¹Œ", + "เสาร์", + "อาทิตย์"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("อา.", + "จ.", + "อ.", + "พ.", + "พฤ.", + "ศ.", + "ส.", + "อา."); + +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 1; + +// full month names +Calendar._MN = new Array +("มà¸à¸£à¸²à¸„ม", + "à¸à¸¸à¸¡à¸ à¸²à¸žà¸±à¸™à¸˜à¹Œ", + "มีนาคม", + "เมษายน", + "พฤษภาคม", + "มิถุนายน", + "à¸à¸£à¸à¸Žà¸²à¸„ม", + "สิงหาคม", + "à¸à¸±à¸™à¸¢à¸²à¸¢à¸™", + "ตุลาคม", + "พฤศจิà¸à¸²à¸¢à¸™", + "ธันวาคม"); + +// short month names +Calendar._SMN = new Array +("ม.ค.", + "à¸.พ.", + "มี.ค.", + "เม.ย.", + "พ.ค.", + "มิ.ย.", + "à¸.ค.", + "ส.ค.", + "à¸.ย.", + "ต.ค.", + "พ.ย.", + "ธ.ค."); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸›à¸à¸´à¸—ิน"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + +Calendar._TT["PREV_YEAR"] = "ปีที่à¹à¸¥à¹‰à¸§ (ถ้าà¸à¸”ค้างจะมีเมนู)"; +Calendar._TT["PREV_MONTH"] = "เดือนที่à¹à¸¥à¹‰à¸§ (ถ้าà¸à¸”ค้างจะมีเมนู)"; +Calendar._TT["GO_TODAY"] = "ไปที่วันนี้"; +Calendar._TT["NEXT_MONTH"] = "เดือนหน้า (ถ้าà¸à¸”ค้างจะมีเมนู)"; +Calendar._TT["NEXT_YEAR"] = "ปีหน้า (ถ้าà¸à¸”ค้างจะมีเมนู)"; +Calendar._TT["SEL_DATE"] = "เลือà¸à¸§à¸±à¸™"; +Calendar._TT["DRAG_TO_MOVE"] = "à¸à¸”à¹à¸¥à¹‰à¸§à¸¥à¸²à¸à¹€à¸žà¸·à¹ˆà¸­à¸¢à¹‰à¸²à¸¢"; +Calendar._TT["PART_TODAY"] = " (วันนี้)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "à¹à¸ªà¸”ง %s เป็นวันà¹à¸£à¸"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "ปิด"; +Calendar._TT["TODAY"] = "วันนี้"; +Calendar._TT["TIME_PART"] = "(Shift-)à¸à¸”หรือà¸à¸”à¹à¸¥à¹‰à¸§à¸¥à¸²à¸à¹€à¸žà¸·à¹ˆà¸­à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¸„่า"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a %e %b"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "เวลา:"; diff --git a/groups/public/javascripts/calendar/lang/calendar-zh-tw.js b/groups/public/javascripts/calendar/lang/calendar-zh-tw.js index c48d25b0e..1e759db10 100644 --- a/groups/public/javascripts/calendar/lang/calendar-zh-tw.js +++ b/groups/public/javascripts/calendar/lang/calendar-zh-tw.js @@ -84,13 +84,13 @@ Calendar._TT["INFO"] = "關於 calendar"; Calendar._TT["ABOUT"] = "DHTML 日期/時間 鏿“‡å™¨\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) -"最For latest version visit: http://www.dynarch.com/projects/calendar/\n" + -"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"最新版本å–å¾—ä½å€: http://www.dynarch.com/projects/calendar/\n" + +"使用 GNU LGPL 發行. åƒè€ƒ http://gnu.org/licenses/lgpl.html 以å–得更多關於 LGPL 之細節。" + "\n\n" + -"Date selection:\n" + -"- Use the \xab, \xbb buttons to select year\n" + -"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + -"- Hold mouse button on any of the above buttons for faster selection."; +"æ—¥æœŸé¸æ“‡æ–¹å¼:\n" + +"- 使用滑鼠點擊 \xab 〠\xbb æŒ‰éˆ•é¸æ“‡å¹´ä»½\n" + +"- 使用滑鼠點擊 " + String.fromCharCode(0x2039) + " 〠" + String.fromCharCode(0x203a) + " æŒ‰éˆ•é¸æ“‡æœˆä»½\n" + +"- 使用滑鼠點擊上述按鈕並按ä½ä¸æ”¾ï¼Œå¯é–‹å•Ÿå¿«é€Ÿé¸å–®ã€‚"; Calendar._TT["ABOUT_TIME"] = "\n\n" + "æ™‚é–“é¸æ“‡æ–¹å¼ï¼š\n" + "- ã€Œå–®æ“Šã€æ™‚分秒為éžå¢ž\n" + diff --git a/groups/public/javascripts/calendar/lang/calendar-zh.js b/groups/public/javascripts/calendar/lang/calendar-zh.js index ddb092bfa..121653fba 100644 --- a/groups/public/javascripts/calendar/lang/calendar-zh.js +++ b/groups/public/javascripts/calendar/lang/calendar-zh.js @@ -82,33 +82,33 @@ Calendar._TT = {}; Calendar._TT["INFO"] = "关于日历"; Calendar._TT["ABOUT"] = -"DHTML Date/Time Selector\n" + +"DHTML 日期/æ—¶é—´ 选择器\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) -"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + -"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"最新版本请访问: http://www.dynarch.com/projects/calendar/\n" + +"éµå¾ª GNU LGPL å‘布。详情请查阅 http://gnu.org/licenses/lgpl.html " + "\n\n" + -"Date selection:\n" + -"- Use the \xab, \xbb buttons to select year\n" + -"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + -"- Hold mouse button on any of the above buttons for faster selection."; +"日期选择:\n" + +"- 使用 \xab,\xbb 按钮选择年\n" + +"- 使用 " + String.fromCharCode(0x2039) + "," + String.fromCharCode(0x203a) + " 按钮选择月\n" + +"- 在上述按钮上按ä½ä¸æ”¾å¯ä»¥å¿«é€Ÿé€‰æ‹©"; Calendar._TT["ABOUT_TIME"] = "\n\n" + -"Time selection:\n" + -"- Click on any of the time parts to increase it\n" + -"- or Shift-click to decrease it\n" + -"- or click and drag for faster selection."; +"时间选择:\n" + +"- 点击时间的任æ„部分æ¥å¢žåŠ \n" + +"- Shift加点击æ¥å‡å°‘\n" + +"- ç‚¹å‡»åŽæ‹–动进行快速选择"; -Calendar._TT["PREV_YEAR"] = "上年 (hold for menu)"; -Calendar._TT["PREV_MONTH"] = "上月 (hold for menu)"; +Calendar._TT["PREV_YEAR"] = "上年(按ä½ä¸æ”¾æ˜¾ç¤ºèœå•)"; +Calendar._TT["PREV_MONTH"] = "上月(按ä½ä¸æ”¾æ˜¾ç¤ºèœå•)"; Calendar._TT["GO_TODAY"] = "回到今天"; -Calendar._TT["NEXT_MONTH"] = "下月 (hold for menu)"; -Calendar._TT["NEXT_YEAR"] = "下年 (hold for menu)"; +Calendar._TT["NEXT_MONTH"] = "下月(按ä½ä¸æ”¾æ˜¾ç¤ºèœå•)"; +Calendar._TT["NEXT_YEAR"] = "下年(按ä½ä¸æ”¾æ˜¾ç¤ºèœå•)"; Calendar._TT["SEL_DATE"] = "选择日期"; Calendar._TT["DRAG_TO_MOVE"] = "拖动"; Calendar._TT["PART_TODAY"] = " (今日)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. -Calendar._TT["DAY_FIRST"] = "Display %s first"; +Calendar._TT["DAY_FIRST"] = "一周开始于 %s"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 @@ -117,11 +117,11 @@ Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "关闭"; Calendar._TT["TODAY"] = "今天"; -Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; +Calendar._TT["TIME_PART"] = "Shift加点击或者拖动æ¥å˜æ›´"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; -Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; +Calendar._TT["TT_DATE_FORMAT"] = "星期%a %b%eæ—¥"; -Calendar._TT["WK"] = "wk"; -Calendar._TT["TIME"] = "Time:"; +Calendar._TT["WK"] = "周"; +Calendar._TT["TIME"] = "时间:"; diff --git a/groups/public/javascripts/context_menu.js b/groups/public/javascripts/context_menu.js index 3e2d571fa..20f0fc5a7 100644 --- a/groups/public/javascripts/context_menu.js +++ b/groups/public/javascripts/context_menu.js @@ -28,11 +28,11 @@ ContextMenu.prototype = { RightClick: function(e) { this.hideMenu(); // do not show the context menu on links - if (Event.findElement(e, 'a') != document) { return; } + if (Event.element(e).tagName == 'A') { return; } // right-click simulated by Alt+Click with Opera if (window.opera && !e.altKey) { return; } var tr = Event.findElement(e, 'tr'); - if ((tr == document) || !tr.hasClassName('hascontextmenu')) { return; } + if (tr == document || tr == undefined || !tr.hasClassName('hascontextmenu')) { return; } Event.stop(e); if (!this.isSelected(tr)) { this.unselectAll(); @@ -44,14 +44,14 @@ ContextMenu.prototype = { Click: function(e) { this.hideMenu(); - if (Event.findElement(e, 'a') != document) { return; } + if (Event.element(e).tagName == 'A') { return; } if (window.opera && e.altKey) { return; } if (Event.isLeftClick(e) || (navigator.appVersion.match(/\bMSIE\b/))) { var tr = Event.findElement(e, 'tr'); if (tr!=document && tr.hasClassName('hascontextmenu')) { // a row was clicked, check if the click was on checkbox var box = Event.findElement(e, 'input'); - if (box!=document) { + if (box!=document && box!=undefined) { // a checkbox may be clicked if (box.checked) { tr.addClassName('context-menu-selection'); @@ -90,6 +90,9 @@ ContextMenu.prototype = { } } } + else{ + this.RightClick(e); + } }, showMenu: function(e) { diff --git a/groups/public/javascripts/controls.js b/groups/public/javascripts/controls.js index 8c273f874..5aaf0bb2b 100644 --- a/groups/public/javascripts/controls.js +++ b/groups/public/javascripts/controls.js @@ -1,6 +1,6 @@ -// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan) -// (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) // Contributors: // Richard Livsey // Rahul Bhargava @@ -37,22 +37,23 @@ if(typeof Effect == 'undefined') throw("controls.js requires including script.aculo.us' effects.js library"); -var Autocompleter = {} -Autocompleter.Base = function() {}; -Autocompleter.Base.prototype = { +var Autocompleter = { } +Autocompleter.Base = Class.create({ baseInitialize: function(element, update, options) { - this.element = $(element); + element = $(element) + this.element = element; this.update = $(update); this.hasFocus = false; this.changed = false; this.active = false; this.index = 0; this.entryCount = 0; + this.oldElementValue = this.element.value; if(this.setOptions) this.setOptions(options); else - this.options = options || {}; + this.options = options || { }; this.options.paramName = this.options.paramName || this.element.name; this.options.tokens = this.options.tokens || []; @@ -74,6 +75,9 @@ Autocompleter.Base.prototype = { if(typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); + // Force carriage returns as token delimiters anyway + if (!this.options.tokens.include('\n')) + this.options.tokens.push('\n'); this.observer = null; @@ -81,15 +85,14 @@ Autocompleter.Base.prototype = { Element.hide(this.update); - Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); - Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keydown', 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) && + (Prototype.Browser.IE) && (Element.getStyle(this.update, 'position')=='absolute')) { new Insertion.After(this.update, '