summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/controllers/account_controller.rb170
-rw-r--r--app/controllers/admin_controller.rb27
-rw-r--r--app/controllers/application.rb22
-rw-r--r--app/controllers/attachments_controller.rb39
-rw-r--r--app/controllers/boards_controller.rb8
-rw-r--r--app/controllers/custom_fields_controller.rb45
-rw-r--r--app/controllers/documents_controller.rb6
-rw-r--r--app/controllers/issue_relations_controller.rb3
-rw-r--r--app/controllers/issues_controller.rb70
-rw-r--r--app/controllers/journals_controller.rb1
-rw-r--r--app/controllers/messages_controller.rb8
-rw-r--r--app/controllers/my_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb82
-rw-r--r--app/controllers/reports_controller.rb8
-rw-r--r--app/controllers/repositories_controller.rb23
-rw-r--r--app/controllers/roles_controller.rb21
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/controllers/settings_controller.rb22
-rw-r--r--app/controllers/sys_controller.rb50
-rw-r--r--app/controllers/timelog_controller.rb7
-rw-r--r--app/controllers/trackers_controller.rb4
-rw-r--r--app/controllers/users_controller.rb16
-rw-r--r--app/controllers/versions_controller.rb6
-rw-r--r--app/controllers/welcome_controller.rb6
-rw-r--r--app/controllers/wiki_controller.rb48
-rw-r--r--app/controllers/workflows_controller.rb45
-rw-r--r--app/helpers/admin_helper.rb10
-rw-r--r--app/helpers/application_helper.rb344
-rw-r--r--app/helpers/attachments_helper.rb13
-rw-r--r--app/helpers/ifpdf_helper.rb85
-rw-r--r--app/helpers/issues_helper.rb8
-rw-r--r--app/helpers/projects_helper.rb47
-rw-r--r--app/helpers/queries_helper.rb6
-rw-r--r--app/helpers/repositories_helper.rb6
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/helpers/settings_helper.rb1
-rw-r--r--app/helpers/sort_helper.rb23
-rw-r--r--app/helpers/timelog_helper.rb4
-rw-r--r--app/helpers/users_helper.rb11
-rw-r--r--app/helpers/wiki_helper.rb16
-rw-r--r--app/helpers/workflows_helper.rb (renamed from app/apis/sys_api.rb)20
-rw-r--r--app/models/attachment.rb19
-rw-r--r--app/models/auth_source.rb3
-rw-r--r--app/models/auth_source_ldap.rb4
-rw-r--r--app/models/changeset.rb47
-rw-r--r--app/models/custom_field.rb40
-rw-r--r--app/models/custom_value.rb12
-rw-r--r--app/models/document.rb8
-rw-r--r--app/models/enumeration.rb4
-rw-r--r--app/models/issue.rb79
-rw-r--r--app/models/issue_relation.rb2
-rw-r--r--app/models/issue_status.rb4
-rw-r--r--app/models/journal.rb1
-rw-r--r--app/models/mail_handler.rb134
-rw-r--r--app/models/mailer.rb132
-rw-r--r--app/models/message.rb15
-rw-r--r--app/models/news.rb11
-rw-r--r--app/models/project.rb77
-rw-r--r--app/models/query.rb156
-rw-r--r--app/models/repository.rb48
-rw-r--r--app/models/repository/darcs.rb2
-rw-r--r--app/models/repository/git.rb37
-rw-r--r--app/models/repository/subversion.rb6
-rw-r--r--app/models/role.rb4
-rw-r--r--app/models/setting.rb8
-rw-r--r--app/models/time_entry.rb4
-rw-r--r--app/models/tracker.rb4
-rw-r--r--app/models/user.rb59
-rw-r--r--app/models/version.rb47
-rw-r--r--app/models/wiki.rb19
-rw-r--r--app/models/wiki_content.rb1
-rw-r--r--app/models/wiki_page.rb6
-rw-r--r--app/models/workflow.rb19
-rw-r--r--app/views/account/login.rhtml6
-rw-r--r--app/views/account/register.rhtml9
-rw-r--r--app/views/account/show.rhtml58
-rw-r--r--app/views/admin/index.rhtml12
-rw-r--r--app/views/admin/info.rhtml19
-rw-r--r--app/views/admin/plugins.rhtml19
-rw-r--r--app/views/admin/projects.rhtml26
-rw-r--r--app/views/attachments/_links.rhtml6
-rw-r--r--app/views/auth_sources/list.rhtml7
-rw-r--r--app/views/boards/index.rhtml8
-rw-r--r--app/views/boards/show.rhtml9
-rw-r--r--app/views/common/_diff.rhtml5
-rw-r--r--app/views/common/feed.atom.rxml2
-rw-r--r--app/views/custom_fields/_form.rhtml32
-rw-r--r--app/views/custom_fields/index.rhtml (renamed from app/views/custom_fields/list.rhtml)0
-rw-r--r--app/views/documents/index.rhtml2
-rw-r--r--app/views/documents/show.rhtml2
-rw-r--r--app/views/issues/_changesets.rhtml2
-rw-r--r--app/views/issues/_edit.rhtml1
-rw-r--r--app/views/issues/_form.rhtml14
-rw-r--r--app/views/issues/_form_update.rhtml2
-rw-r--r--app/views/issues/_history.rhtml24
-rw-r--r--app/views/issues/_list.rhtml4
-rw-r--r--app/views/issues/_list_simple.rhtml13
-rw-r--r--app/views/issues/_relations.rhtml5
-rw-r--r--app/views/issues/_sidebar.rhtml18
-rw-r--r--app/views/issues/bulk_edit.rhtml10
-rw-r--r--app/views/issues/context_menu.rhtml5
-rw-r--r--app/views/issues/gantt.rfpdf188
-rw-r--r--app/views/issues/gantt.rhtml9
-rw-r--r--app/views/issues/index.rfpdf50
-rw-r--r--app/views/issues/index.rhtml17
-rw-r--r--app/views/issues/move.rhtml9
-rw-r--r--app/views/issues/new.rhtml7
-rw-r--r--app/views/issues/show.rfpdf126
-rw-r--r--app/views/issues/show.rhtml42
-rw-r--r--app/views/journals/_notes_form.rhtml5
-rw-r--r--app/views/journals/update.rjs2
-rw-r--r--app/views/layouts/_project_selector.rhtml12
-rw-r--r--app/views/layouts/base.rhtml14
-rw-r--r--app/views/mailer/layout.text.html.rhtml2
-rw-r--r--app/views/messages/show.rhtml18
-rw-r--r--app/views/my/account.rhtml9
-rw-r--r--app/views/my/blocks/_issuesassignedtome.rhtml4
-rw-r--r--app/views/my/blocks/_issuesreportedbyme.rhtml4
-rw-r--r--app/views/my/blocks/_issueswatched.rhtml6
-rw-r--r--app/views/my/blocks/_timelog.rhtml2
-rw-r--r--app/views/news/index.rhtml9
-rw-r--r--app/views/news/show.rhtml6
-rw-r--r--app/views/projects/_form.rhtml7
-rw-r--r--app/views/projects/activity.rhtml27
-rw-r--r--app/views/projects/add.rhtml1
-rw-r--r--app/views/projects/add_file.rhtml9
-rw-r--r--app/views/projects/changelog.rhtml2
-rw-r--r--app/views/projects/destroy.rhtml4
-rw-r--r--app/views/projects/index.rhtml20
-rw-r--r--app/views/projects/list_files.rhtml30
-rw-r--r--app/views/projects/roadmap.rhtml1
-rw-r--r--app/views/projects/settings/_members.rhtml2
-rw-r--r--app/views/projects/settings/_repository.rhtml5
-rw-r--r--app/views/projects/settings/_versions.rhtml1
-rw-r--r--app/views/projects/show.rhtml14
-rw-r--r--app/views/queries/_filters.rhtml5
-rw-r--r--app/views/repositories/_dir_list_content.rhtml4
-rw-r--r--app/views/repositories/_link_to_functions.rhtml10
-rw-r--r--app/views/repositories/_revisions.rhtml4
-rw-r--r--app/views/repositories/annotate.rhtml2
-rw-r--r--app/views/repositories/changes.rhtml11
-rw-r--r--app/views/repositories/committers.rhtml34
-rw-r--r--app/views/repositories/diff.rhtml16
-rw-r--r--app/views/repositories/entry.rhtml2
-rw-r--r--app/views/repositories/revision.rhtml2
-rw-r--r--app/views/repositories/revisions.rhtml7
-rw-r--r--app/views/repositories/show.rhtml9
-rw-r--r--app/views/roles/_form.rhtml4
-rw-r--r--app/views/roles/report.rhtml4
-rw-r--r--app/views/settings/_authentication.rhtml3
-rw-r--r--app/views/settings/_display.rhtml24
-rw-r--r--app/views/settings/_general.rhtml23
-rw-r--r--app/views/settings/_notifications.rhtml6
-rw-r--r--app/views/settings/_repositories.rhtml3
-rw-r--r--app/views/timelog/_list.rhtml8
-rw-r--r--app/views/timelog/details.rhtml15
-rw-r--r--app/views/timelog/edit.rhtml3
-rw-r--r--app/views/timelog/report.rhtml13
-rw-r--r--app/views/trackers/_form.rhtml17
-rw-r--r--app/views/trackers/edit.rhtml4
-rw-r--r--app/views/trackers/list.rhtml2
-rw-r--r--app/views/trackers/new.rhtml4
-rw-r--r--app/views/users/_form.rhtml3
-rw-r--r--app/views/users/_general.rhtml2
-rw-r--r--app/views/users/_memberships.rhtml2
-rw-r--r--app/views/users/list.rhtml7
-rw-r--r--app/views/welcome/robots.rhtml9
-rw-r--r--app/views/wiki/annotate.rhtml2
-rw-r--r--app/views/wiki/export.rhtml2
-rw-r--r--app/views/wiki/export_multiple.rhtml2
-rw-r--r--app/views/wiki/show.rhtml11
-rw-r--r--app/views/wiki/special_date_index.rhtml9
-rw-r--r--app/views/wiki/special_page_index.rhtml9
-rw-r--r--app/views/workflows/edit.rhtml (renamed from app/views/roles/workflow.rhtml)42
-rw-r--r--app/views/workflows/index.rhtml31
-rw-r--r--config/environment.rb2
-rw-r--r--config/initializers/10-patches.rb16
-rw-r--r--config/routes.rb244
-rw-r--r--config/settings.yml13
-rw-r--r--db/migrate/098_set_topic_authors_as_watchers.rb7
-rw-r--r--db/migrate/099_add_delete_wiki_pages_attachments_permission.rb13
-rw-r--r--db/migrate/100_add_changesets_user_id.rb9
-rw-r--r--db/migrate/101_populate_changesets_user_id.rb18
-rw-r--r--db/migrate/102_add_custom_fields_editable.rb9
-rw-r--r--db/migrate/103_set_custom_fields_editable.rb9
-rw-r--r--db/migrate/104_add_projects_lft_and_rgt.rb11
-rw-r--r--db/migrate/105_build_projects_tree.rb8
-rw-r--r--db/migrate/106_remove_projects_projects_count.rb9
-rw-r--r--db/migrate/107_add_open_id_authentication_tables.rb20
-rw-r--r--db/migrate/108_add_identity_url_to_users.rb9
-rw-r--r--doc/CHANGELOG198
-rw-r--r--doc/RUNNING_TESTS49
-rw-r--r--doc/UPGRADING2
-rw-r--r--extra/mail_handler/rdm-mailhandler.rb6
-rwxr-xr-xextra/svn/reposman.rb32
-rw-r--r--lang/bg.yml69
-rw-r--r--lang/ca.yml86
-rw-r--r--lang/cs.yml69
-rw-r--r--lang/da.yml478
-rw-r--r--lang/de.yml89
-rw-r--r--lang/en.yml77
-rw-r--r--lang/es.yml1177
-rw-r--r--lang/fi.yml81
-rw-r--r--lang/fr.yml79
-rw-r--r--lang/gl.yml694
-rw-r--r--lang/he.yml119
-rw-r--r--lang/hu.yml69
-rw-r--r--lang/it.yml103
-rw-r--r--lang/ja.yml109
-rw-r--r--lang/ko.yml437
-rw-r--r--lang/lt.yml1081
-rw-r--r--lang/mk.yml711
-rw-r--r--lang/nl.yml1183
-rw-r--r--lang/no.yml69
-rw-r--r--lang/pl.yml1208
-rw-r--r--lang/pt-br.yml81
-rw-r--r--lang/pt.yml661
-rw-r--r--lang/ro.yml69
-rw-r--r--lang/ru.yml77
-rw-r--r--lang/sk.yml714
-rw-r--r--lang/sl.yml711
-rw-r--r--lang/sr.yml361
-rw-r--r--lang/sv.yml738
-rw-r--r--lang/th.yml69
-rw-r--r--lang/tr.yml69
-rw-r--r--lang/uk.yml247
-rw-r--r--lang/vn.yml712
-rw-r--r--lang/zh-tw.yml77
-rw-r--r--lang/zh.yml77
-rw-r--r--lib/generators/redmine_plugin/redmine_plugin_generator.rb6
-rw-r--r--lib/generators/redmine_plugin/templates/README.rdoc (renamed from lib/generators/redmine_plugin/templates/README)0
-rw-r--r--lib/generators/redmine_plugin/templates/init.rb.erb (renamed from lib/generators/redmine_plugin/templates/init.rb)0
-rw-r--r--lib/generators/redmine_plugin/templates/test_helper.rb.erb (renamed from lib/generators/redmine_plugin/templates/test_helper.rb)0
-rw-r--r--lib/generators/redmine_plugin_controller/redmine_plugin_controller_generator.rb36
-rw-r--r--lib/generators/redmine_plugin_controller/templates/controller.rb.erb (renamed from lib/generators/redmine_plugin_controller/templates/controller.rb)0
-rw-r--r--lib/generators/redmine_plugin_controller/templates/functional_test.rb.erb (renamed from lib/generators/redmine_plugin_controller/templates/functional_test.rb)0
-rw-r--r--lib/generators/redmine_plugin_controller/templates/helper.rb.erb (renamed from lib/generators/redmine_plugin_controller/templates/helper.rb)0
-rw-r--r--lib/generators/redmine_plugin_model/redmine_plugin_model_generator.rb26
-rw-r--r--lib/generators/redmine_plugin_model/templates/migration.rb.erb (renamed from lib/generators/redmine_plugin_model/templates/migration.rb)0
-rw-r--r--lib/generators/redmine_plugin_model/templates/model.rb.erb (renamed from lib/generators/redmine_plugin_model/templates/model.rb)0
-rw-r--r--lib/generators/redmine_plugin_model/templates/unit_test.rb.erb (renamed from lib/generators/redmine_plugin_model/templates/unit_test.rb)0
-rw-r--r--lib/redcloth3.rb73
-rw-r--r--lib/redmine.rb40
-rw-r--r--lib/redmine/access_control.rb10
-rw-r--r--lib/redmine/activity/fetcher.rb20
-rw-r--r--lib/redmine/core_ext/string/conversions.rb4
-rw-r--r--lib/redmine/default_data/loader.rb2
-rw-r--r--lib/redmine/export/pdf.rb462
-rw-r--r--lib/redmine/hook.rb62
-rw-r--r--lib/redmine/imap.rb11
-rw-r--r--lib/redmine/menu_manager.rb53
-rw-r--r--lib/redmine/plugin.rb95
-rw-r--r--lib/redmine/scm/adapters/abstract_adapter.rb4
-rw-r--r--lib/redmine/scm/adapters/bazaar_adapter.rb3
-rw-r--r--lib/redmine/scm/adapters/cvs_adapter.rb12
-rw-r--r--lib/redmine/scm/adapters/darcs_adapter.rb31
-rw-r--r--lib/redmine/scm/adapters/git_adapter.rb13
-rw-r--r--lib/redmine/scm/adapters/subversion_adapter.rb31
-rw-r--r--lib/redmine/unified_diff.rb20
-rw-r--r--lib/redmine/utils.rb38
-rw-r--r--lib/redmine/version.rb14
-rw-r--r--lib/redmine/views/other_formats_builder.rb33
-rw-r--r--lib/redmine/wiki_formatting.rb193
-rw-r--r--lib/redmine/wiki_formatting/macros.rb40
-rw-r--r--lib/redmine/wiki_formatting/textile/formatter.rb184
-rw-r--r--lib/redmine/wiki_formatting/textile/helper.rb46
-rw-r--r--lib/tabular_form_builder.rb26
-rw-r--r--lib/tasks/email.rake17
-rw-r--r--lib/tasks/migrate_from_trac.rake336
-rw-r--r--lib/tasks/testing.rake30
-rwxr-xr-xpublic/dispatch.cgi.example2
-rwxr-xr-xpublic/dispatch.fcgi.example2
-rwxr-xr-xpublic/dispatch.rb.example2
-rw-r--r--public/help/wiki_syntax_detailed.html599
-rw-r--r--public/images/openid-bg.gifbin0 -> 328 bytes
-rw-r--r--public/images/time.pngbin404 -> 793 bytes
-rw-r--r--public/images/time_add.pngbin0 -> 827 bytes
-rw-r--r--public/images/warning.pngbin535 -> 666 bytes
-rw-r--r--public/javascripts/calendar/lang/calendar-da.js10
-rw-r--r--public/javascripts/calendar/lang/calendar-gl.js128
-rw-r--r--public/javascripts/calendar/lang/calendar-ko.js28
-rw-r--r--public/javascripts/calendar/lang/calendar-mk.js127
-rw-r--r--public/javascripts/calendar/lang/calendar-pt.js37
-rw-r--r--public/javascripts/calendar/lang/calendar-sk.js68
-rw-r--r--public/javascripts/calendar/lang/calendar-sl.js127
-rw-r--r--public/javascripts/calendar/lang/calendar-sv.js56
-rw-r--r--public/javascripts/calendar/lang/calendar-vn.js126
-rw-r--r--public/javascripts/context_menu.js2
-rw-r--r--public/javascripts/jstoolbar/jstoolbar.js179
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-da.js12
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-es.js30
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-gl.js16
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-it.js30
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-ko.js30
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-mk.js16
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-nl.js30
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-pl.js31
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-pt.js31
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-sk.js16
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-sl.js16
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-sv.js30
-rw-r--r--public/javascripts/jstoolbar/lang/jstoolbar-vn.js16
-rw-r--r--public/javascripts/jstoolbar/textile.js200
-rw-r--r--public/robots.txt4
-rw-r--r--public/stylesheets/application.css103
-rw-r--r--public/themes/classic/stylesheets/application.css6
-rw-r--r--test/fixtures/attachments.yml24
-rw-r--r--test/fixtures/changesets.yml4
-rw-r--r--test/fixtures/custom_fields.yml18
-rw-r--r--test/fixtures/custom_fields_trackers.yml9
-rw-r--r--test/fixtures/custom_values.yml48
-rw-r--r--test/fixtures/diffs/subversion.diff79
-rw-r--r--test/fixtures/enabled_modules.yml12
-rw-r--r--test/fixtures/files/testfile.txt3
-rw-r--r--test/fixtures/issues.yml34
-rw-r--r--test/fixtures/mail_handler/message_reply.eml15
-rw-r--r--test/fixtures/mail_handler/message_reply_by_subject.eml13
-rw-r--r--test/fixtures/mail_handler/ticket_html_only.eml22
-rw-r--r--test/fixtures/mail_handler/ticket_on_given_project.eml4
-rw-r--r--test/fixtures/mail_handler/ticket_reply.eml3
-rw-r--r--test/fixtures/mail_handler/ticket_with_cc.eml40
-rw-r--r--test/fixtures/mail_handler/ticket_with_custom_fields.eml41
-rw-r--r--test/fixtures/members.yml5
-rw-r--r--test/fixtures/messages.yml17
-rw-r--r--test/fixtures/projects.yml29
-rw-r--r--test/fixtures/projects_trackers.yml28
-rw-r--r--test/fixtures/roles.yml4
-rw-r--r--test/fixtures/watchers.yml4
-rw-r--r--test/fixtures/wiki_contents.yml22
-rw-r--r--test/fixtures/wiki_pages.yml14
-rw-r--r--test/functional/account_controller_test.rb79
-rw-r--r--test/functional/admin_controller_test.rb58
-rw-r--r--test/functional/application_controller_test.rb4
-rw-r--r--test/functional/attachments_controller_test.rb51
-rw-r--r--test/functional/boards_controller_test.rb43
-rw-r--r--test/functional/custom_fields_controller_test.rb61
-rw-r--r--test/functional/documents_controller_test.rb54
-rw-r--r--test/functional/issue_relations_controller_test.rb58
-rw-r--r--test/functional/issues_controller_test.rb357
-rw-r--r--test/functional/members_controller_test.rb15
-rw-r--r--test/functional/messages_controller_test.rb47
-rw-r--r--test/functional/my_controller_test.rb23
-rw-r--r--test/functional/news_controller_test.rb64
-rw-r--r--test/functional/projects_controller_test.rb308
-rw-r--r--test/functional/reports_controller_test.rb20
-rw-r--r--test/functional/repositories_controller_test.rb147
-rw-r--r--test/functional/repositories_subversion_controller_test.rb24
-rw-r--r--test/functional/roles_controller_test.rb40
-rw-r--r--test/functional/search_controller_test.rb11
-rw-r--r--test/functional/sys_api_test.rb50
-rw-r--r--test/functional/sys_controller_test.rb55
-rw-r--r--test/functional/timelog_controller_test.rb128
-rw-r--r--test/functional/trackers_controller_test.rb68
-rw-r--r--test/functional/users_controller_test.rb84
-rw-r--r--test/functional/versions_controller_test.rb4
-rw-r--r--test/functional/welcome_controller_test.rb7
-rw-r--r--test/functional/wiki_controller_test.rb129
-rw-r--r--test/functional/wikis_controller_test.rb23
-rw-r--r--test/functional/workflows_controller_test.rb84
-rw-r--r--test/integration/account_test.rb2
-rw-r--r--test/integration/admin_test.rb4
-rw-r--r--test/integration/issues_test.rb8
-rw-r--r--test/integration/projects_test.rb10
-rw-r--r--test/mocks/open_id_authentication_mock.rb45
-rw-r--r--test/test_helper.rb1
-rw-r--r--test/unit/activity_test.rb9
-rw-r--r--test/unit/attachment_test.rb7
-rw-r--r--test/unit/custom_field_test.rb18
-rw-r--r--test/unit/document_test.rb37
-rw-r--r--test/unit/enumeration_test.rb37
-rw-r--r--test/unit/helpers/application_helper_test.rb163
-rw-r--r--test/unit/issue_status_test.rb22
-rw-r--r--test/unit/issue_test.rb38
-rw-r--r--test/unit/lib/redmine/access_control_test.rb49
-rw-r--r--test/unit/lib/redmine/hook_test.rb83
-rw-r--r--test/unit/lib/redmine/plugin_test.rb78
-rw-r--r--test/unit/lib/redmine/unified_diff_test.rb42
-rw-r--r--test/unit/lib/redmine/wiki_formatting/macros_test.rb98
-rw-r--r--test/unit/mail_handler_test.rb59
-rw-r--r--test/unit/mailer_test.rb104
-rw-r--r--test/unit/message_test.rb20
-rw-r--r--test/unit/news_test.rb63
-rw-r--r--test/unit/project_test.rb109
-rw-r--r--test/unit/query_test.rb119
-rw-r--r--test/unit/repository_cvs_test.rb6
-rw-r--r--test/unit/repository_darcs_test.rb6
-rw-r--r--test/unit/repository_git_test.rb17
-rw-r--r--test/unit/repository_subversion_test.rb7
-rw-r--r--test/unit/repository_test.rb44
-rw-r--r--test/unit/time_entry_test.rb3
-rw-r--r--test/unit/user_test.rb335
-rw-r--r--test/unit/version_test.rb86
-rw-r--r--vendor/plugins/actionwebservice/CHANGELOG265
-rw-r--r--vendor/plugins/actionwebservice/README364
-rw-r--r--vendor/plugins/actionwebservice/Rakefile172
-rw-r--r--vendor/plugins/actionwebservice/TODO32
-rw-r--r--vendor/plugins/actionwebservice/init.rb7
-rw-r--r--vendor/plugins/actionwebservice/install.rb30
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service.rb66
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/api.rb297
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/base.rb38
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/casting.rb138
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/client.rb3
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/client/base.rb28
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/client/soap_client.rb113
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb58
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/container.rb3
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/container/action_controller_container.rb93
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/container/delegated_container.rb86
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/container/direct_container.rb69
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/dispatcher.rb2
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/abstract.rb207
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb379
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/invocation.rb202
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/protocol.rb4
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/protocol/abstract.rb112
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/protocol/discovery.rb37
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb176
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb235
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb122
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/scaffolding.rb283
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/struct.rb64
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/support/class_inheritable_options.rb26
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/support/signature_types.rb226
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.erb65
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml0
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.erb6
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml0
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.erb29
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml0
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.erb30
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml0
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/test_invoke.rb110
-rw-r--r--vendor/plugins/actionwebservice/lib/action_web_service/version.rb9
-rw-r--r--vendor/plugins/actionwebservice/lib/actionwebservice.rb1
-rw-r--r--vendor/plugins/actionwebservice/setup.rb1379
-rw-r--r--vendor/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb23
-rw-r--r--vendor/plugins/acts_as_attachable/init.rb2
-rw-r--r--vendor/plugins/acts_as_attachable/lib/acts_as_attachable.rb57
-rw-r--r--vendor/plugins/acts_as_watchable/lib/acts_as_watchable.rb2
-rw-r--r--vendor/plugins/awesome_nested_set/MIT-LICENSE (renamed from vendor/plugins/actionwebservice/MIT-LICENSE)3
-rw-r--r--vendor/plugins/awesome_nested_set/README.rdoc64
-rw-r--r--vendor/plugins/awesome_nested_set/Rakefile46
-rw-r--r--vendor/plugins/awesome_nested_set/awesome_nested_set.gemspec20
-rw-r--r--vendor/plugins/awesome_nested_set/init.rb1
-rw-r--r--vendor/plugins/awesome_nested_set/lib/awesome_nested_set.rb547
-rw-r--r--vendor/plugins/awesome_nested_set/lib/awesome_nested_set/compatability.rb29
-rw-r--r--vendor/plugins/awesome_nested_set/lib/awesome_nested_set/helper.rb40
-rw-r--r--vendor/plugins/awesome_nested_set/lib/awesome_nested_set/named_scope.rb140
-rw-r--r--vendor/plugins/awesome_nested_set/rails/init.rb13
-rw-r--r--vendor/plugins/awesome_nested_set/test/awesome_nested_set/helper_test.rb41
-rw-r--r--vendor/plugins/awesome_nested_set/test/awesome_nested_set_test.rb603
-rw-r--r--vendor/plugins/awesome_nested_set/test/db/database.yml18
-rw-r--r--vendor/plugins/awesome_nested_set/test/db/schema.rb23
-rw-r--r--vendor/plugins/awesome_nested_set/test/fixtures/categories.yml34
-rw-r--r--vendor/plugins/awesome_nested_set/test/fixtures/category.rb15
-rw-r--r--vendor/plugins/awesome_nested_set/test/fixtures/departments.yml3
-rw-r--r--vendor/plugins/awesome_nested_set/test/fixtures/notes.yml38
-rw-r--r--vendor/plugins/awesome_nested_set/test/test_helper.rb31
-rw-r--r--vendor/plugins/classic_pagination/lib/pagination.rb4
-rw-r--r--vendor/plugins/engines/lib/engines/rails_extensions/dependencies.rb2
-rw-r--r--vendor/plugins/gloc-1.1.0/lib/gloc.rb16
-rw-r--r--vendor/plugins/gloc-1.1.0/tasks/gloc.rake2
-rw-r--r--vendor/plugins/gravatar/MIT-LICENSE20
-rw-r--r--vendor/plugins/gravatar/README52
-rw-r--r--vendor/plugins/gravatar/Rakefile33
-rw-r--r--vendor/plugins/gravatar/about.yml7
-rw-r--r--vendor/plugins/gravatar/init.rb2
-rw-r--r--vendor/plugins/gravatar/lib/gravatar.rb67
-rw-r--r--vendor/plugins/gravatar/spec/gravatar_spec.rb37
-rw-r--r--vendor/plugins/open_id_authentication/CHANGELOG35
-rw-r--r--vendor/plugins/open_id_authentication/README231
-rw-r--r--vendor/plugins/open_id_authentication/Rakefile22
-rw-r--r--vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb11
-rw-r--r--vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb20
-rw-r--r--vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb26
-rw-r--r--vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb11
-rw-r--r--vendor/plugins/open_id_authentication/init.rb16
-rw-r--r--vendor/plugins/open_id_authentication/lib/open_id_authentication.rb241
-rw-r--r--vendor/plugins/open_id_authentication/lib/open_id_authentication/association.rb9
-rw-r--r--vendor/plugins/open_id_authentication/lib/open_id_authentication/db_store.rb55
-rw-r--r--vendor/plugins/open_id_authentication/lib/open_id_authentication/mem_cache_store.rb73
-rw-r--r--vendor/plugins/open_id_authentication/lib/open_id_authentication/nonce.rb5
-rw-r--r--vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb23
-rw-r--r--vendor/plugins/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb20
-rw-r--r--vendor/plugins/open_id_authentication/tasks/open_id_authentication_tasks.rake30
-rw-r--r--vendor/plugins/open_id_authentication/test/mem_cache_store_test.rb151
-rw-r--r--vendor/plugins/open_id_authentication/test/normalize_test.rb32
-rw-r--r--vendor/plugins/open_id_authentication/test/open_id_authentication_test.rb46
-rw-r--r--vendor/plugins/open_id_authentication/test/status_test.rb14
-rw-r--r--vendor/plugins/open_id_authentication/test/test_helper.rb17
491 files changed, 21653 insertions, 12471 deletions
diff --git a/app/controllers/account_controller.rb b/app/controllers/account_controller.rb
index 4b2ec8317..dae268f20 100644
--- a/app/controllers/account_controller.rb
+++ b/app/controllers/account_controller.rb
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# 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
@@ -24,13 +24,17 @@ class AccountController < ApplicationController
# Show user's account
def show
- @user = User.find_active(params[:id])
+ @user = User.active.find(params[:id])
@custom_values = @user.custom_values
# show only public projects and private projects that the logged in user is also a member of
@memberships = @user.memberships.select do |membership|
membership.project.is_public? || (User.current.member_of?(membership.project))
end
+
+ events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
+ @events_by_day = events.group_by(&:event_date)
+
rescue ActiveRecord::RecordNotFound
render_404
end
@@ -42,24 +46,10 @@ class AccountController < ApplicationController
self.logged_user = nil
else
# Authenticate user
- user = User.try_to_login(params[:username], params[:password])
- if user.nil?
- # Invalid credentials
- flash.now[:error] = l(:notice_account_invalid_creditentials)
- elsif user.new_record?
- # Onthefly creation failed, display the registration form to fill/fix attributes
- @user = user
- session[:auth_source_registration] = {:login => user.login, :auth_source_id => user.auth_source_id }
- render :action => 'register'
+ if Setting.openid? && using_open_id?
+ open_id_authenticate(params[:openid_url])
else
- # Valid user
- self.logged_user = user
- # generate a key and set cookie if autologin
- if params[:autologin] && Setting.autologin?
- token = Token.create(:user => user, :action => 'autologin')
- cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now }
- end
- redirect_back_or_default :controller => 'my', :action => 'page'
+ password_authentication
end
end
end
@@ -132,31 +122,14 @@ class AccountController < ApplicationController
else
@user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
+
case Setting.self_registration
when '1'
- # Email activation
- token = Token.new(:user => @user, :action => "register")
- if @user.save and token.save
- Mailer.deliver_register(token)
- flash[:notice] = l(:notice_account_register_done)
- redirect_to :action => 'login'
- end
+ register_by_email_activation(@user)
when '3'
- # Automatic activation
- @user.status = User::STATUS_ACTIVE
- if @user.save
- self.logged_user = @user
- flash[:notice] = l(:notice_account_activated)
- redirect_to :controller => 'my', :action => 'account'
- end
+ register_automatically(@user)
else
- # Manual activation by the administrator
- if @user.save
- # Sends an email to the administrators
- Mailer.deliver_account_activation_request(@user)
- flash[:notice] = l(:notice_account_pending)
- redirect_to :action => 'login'
- end
+ register_manually_by_administrator(@user)
end
end
end
@@ -187,4 +160,119 @@ private
session[:user_id] = nil
end
end
+
+ def password_authentication
+ user = User.try_to_login(params[:username], params[:password])
+ if user.nil?
+ # Invalid credentials
+ flash.now[:error] = l(:notice_account_invalid_creditentials)
+ elsif user.new_record?
+ # Onthefly creation failed, display the registration form to fill/fix attributes
+ @user = user
+ session[:auth_source_registration] = {:login => user.login, :auth_source_id => user.auth_source_id }
+ render :action => 'register'
+ else
+ # Valid user
+ successful_authentication(user)
+ end
+ end
+
+
+ def open_id_authenticate(openid_url)
+ authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url) do |result, identity_url, registration|
+ if result.successful?
+ user = User.find_or_initialize_by_identity_url(identity_url)
+ if user.new_record?
+ # Self-registration off
+ redirect_to(home_url) && return unless Setting.self_registration?
+
+ # Create on the fly
+ user.login = registration['nickname'] unless registration['nickname'].nil?
+ user.mail = registration['email'] unless registration['email'].nil?
+ user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
+ user.random_password
+ user.status = User::STATUS_REGISTERED
+
+ case Setting.self_registration
+ when '1'
+ register_by_email_activation(user) do
+ onthefly_creation_failed(user, {:login => user.login, :identity_url => identity_url })
+ end
+ when '3'
+ register_automatically(user) do
+ onthefly_creation_failed(user, {:login => user.login, :identity_url => identity_url })
+ end
+ else
+ register_manually_by_administrator(user) do
+ onthefly_creation_failed(user, {:login => user.login, :identity_url => identity_url })
+ end
+ end
+ else
+ # Existing record
+ successful_authentication(user)
+ end
+ end
+ end
+ end
+
+ def successful_authentication(user)
+ # Valid user
+ self.logged_user = user
+ # generate a key and set cookie if autologin
+ if params[:autologin] && Setting.autologin?
+ token = Token.create(:user => user, :action => 'autologin')
+ cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now }
+ end
+ redirect_back_or_default :controller => 'my', :action => 'page'
+ end
+
+ # Onthefly creation failed, display the registration form to fill/fix attributes
+ def onthefly_creation_failed(user, auth_source_options = { })
+ @user = user
+ session[:auth_source_registration] = auth_source_options unless auth_source_options.empty?
+ render :action => 'register'
+ end
+
+ # Register a user for email activation.
+ #
+ # Pass a block for behavior when a user fails to save
+ def register_by_email_activation(user, &block)
+ token = Token.new(:user => user, :action => "register")
+ if user.save and token.save
+ Mailer.deliver_register(token)
+ flash[:notice] = l(:notice_account_register_done)
+ redirect_to :action => 'login'
+ else
+ yield if block_given?
+ end
+ end
+
+ # Automatically register a user
+ #
+ # Pass a block for behavior when a user fails to save
+ def register_automatically(user, &block)
+ # Automatic activation
+ user.status = User::STATUS_ACTIVE
+ if user.save
+ self.logged_user = user
+ flash[:notice] = l(:notice_account_activated)
+ redirect_to :controller => 'my', :action => 'account'
+ else
+ yield if block_given?
+ end
+ end
+
+ # Manual activation by the administrator
+ #
+ # Pass a block for behavior when a user fails to save
+ def register_manually_by_administrator(user, &block)
+ if user.save
+ # Sends an email to the administrators
+ Mailer.deliver_account_activation_request(user)
+ flash[:notice] = l(:notice_account_pending)
+ redirect_to :action => 'login'
+ else
+ yield if block_given?
+ end
+ end
end
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb
index a6df49dcd..61d1eada7 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin_controller.rb
@@ -26,25 +26,24 @@ class AdminController < ApplicationController
end
def projects
- sort_init 'name', 'asc'
- sort_update
+ @status = params[:status] ? params[:status].to_i : 1
+ c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
- @status = params[:status] ? params[:status].to_i : 0
- conditions = nil
- conditions = ["status=?", @status] unless @status == 0
+ unless params[:name].blank?
+ name = "%#{params[:name].strip.downcase}%"
+ c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name]
+ end
- @project_count = Project.count(:conditions => conditions)
- @project_pages = Paginator.new self, @project_count,
- per_page_option,
- params['page']
- @projects = Project.find :all, :order => sort_clause,
- :conditions => conditions,
- :limit => @project_pages.items_per_page,
- :offset => @project_pages.current.offset
+ @projects = Project.find :all, :order => 'lft',
+ :conditions => c.conditions
render :action => "projects", :layout => false if request.xhr?
end
+ def plugins
+ @plugins = Redmine::Plugin.all
+ end
+
# Loads the default configuration
# (roles, trackers, statuses, workflow, enumerations)
def default_configuration
@@ -78,8 +77,8 @@ class AdminController < ApplicationController
@flags = {
:default_admin_changed => User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?,
:file_repository_writable => File.writable?(Attachment.storage_path),
+ :plugin_assets_writable => File.writable?(Engines.public_directory),
:rmagick_available => Object.const_defined?(:Magick)
}
- @plugins = Redmine::Plugin.registered_plugins
end
end
diff --git a/app/controllers/application.rb b/app/controllers/application.rb
index 238239c44..e8d0a85b7 100644
--- a/app/controllers/application.rb
+++ b/app/controllers/application.rb
@@ -46,7 +46,7 @@ class ApplicationController < ActionController::Base
def find_current_user
if session[:user_id]
# existing session
- (User.find_active(session[:user_id]) rescue nil)
+ (User.active.find(session[:user_id]) rescue nil)
elsif cookies[:autologin] && Setting.autologin?
# auto-login feature
User.find_by_autologin_key(cookies[:autologin])
@@ -82,7 +82,7 @@ class ApplicationController < ActionController::Base
def require_login
if !User.current.logged?
- redirect_to :controller => "account", :action => "login", :back_url => (request.relative_url_root + request.request_uri)
+ redirect_to :controller => "account", :action => "login", :back_url => url_for(params)
return false
end
true
@@ -126,10 +126,14 @@ class ApplicationController < ActionController::Base
def redirect_back_or_default(default)
back_url = CGI.unescape(params[:back_url].to_s)
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
+ begin
+ uri = URI.parse(back_url)
+ # do not redirect user to another host or to the login or register page
+ if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
+ redirect_to(back_url) and return
+ end
+ rescue URI::InvalidURIError
+ # redirect to default
end
end
redirect_to default
@@ -171,6 +175,7 @@ class ApplicationController < ActionController::Base
# TODO: move to model
def attach_files(obj, attachments)
attached = []
+ unsaved = []
if attachments && attachments.is_a?(Hash)
attachments.each_value do |attachment|
file = attachment['file']
@@ -179,7 +184,10 @@ class ApplicationController < ActionController::Base
:file => file,
:description => attachment['description'].to_s.strip,
:author => User.current)
- attached << a unless a.new_record?
+ a.new_record? ? (unsaved << a) : (attached << a)
+ end
+ if unsaved.any?
+ flash[:warning] = l(:warning_attachments_not_saved, unsaved.size)
end
end
attached
diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb
index 788bab94d..c55e8de25 100644
--- a/app/controllers/attachments_controller.rb
+++ b/app/controllers/attachments_controller.rb
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# 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
@@ -17,7 +17,11 @@
class AttachmentsController < ApplicationController
before_filter :find_project
-
+ before_filter :read_authorize, :except => :destroy
+ before_filter :delete_authorize, :only => :destroy
+
+ verify :method => :post, :only => :destroy
+
def show
if @attachment.is_diff?
@diff = File.new(@attachment.diskfile, "rb").read
@@ -25,31 +29,46 @@ class AttachmentsController < ApplicationController
elsif @attachment.is_text?
@content = File.new(@attachment.diskfile, "rb").read
render :action => 'file'
- elsif
+ else
download
end
end
def download
- @attachment.increment_download if @attachment.container.is_a?(Version)
+ if @attachment.container.is_a?(Version) || @attachment.container.is_a?(Project)
+ @attachment.increment_download
+ end
# images are sent inline
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
:type => @attachment.content_type,
:disposition => (@attachment.image? ? 'inline' : 'attachment')
+
+ end
+
+ def destroy
+ # Make sure association callbacks are called
+ @attachment.container.attachments.delete(@attachment)
+ redirect_to :back
+ rescue ::ActionController::RedirectBackError
+ redirect_to :controller => 'projects', :action => 'show', :id => @project
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
- 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
+
+ def read_authorize
+ @attachment.visible? ? true : deny_access
+ end
+
+ def delete_authorize
+ @attachment.deletable? ? true : deny_access
+ end
end
diff --git a/app/controllers/boards_controller.rb b/app/controllers/boards_controller.rb
index 4532a88fe..55a9737f5 100644
--- a/app/controllers/boards_controller.rb
+++ b/app/controllers/boards_controller.rb
@@ -35,12 +35,14 @@ class BoardsController < ApplicationController
end
def show
- sort_init "#{Message.table_name}.updated_on", "desc"
- sort_update
+ sort_init 'updated_on', 'desc'
+ sort_update 'created_on' => "#{Message.table_name}.created_on",
+ 'replies' => "#{Message.table_name}.replies_count",
+ 'updated_on' => "#{Message.table_name}.updated_on"
@topic_count = @board.topics.count
@topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
- @topics = @board.topics.find :all, :order => "#{Message.table_name}.sticky DESC, #{sort_clause}",
+ @topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '),
:include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset
diff --git a/app/controllers/custom_fields_controller.rb b/app/controllers/custom_fields_controller.rb
index 4589996f1..5a79e4b7b 100644
--- a/app/controllers/custom_fields_controller.rb
+++ b/app/controllers/custom_fields_controller.rb
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2009 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -19,34 +19,22 @@ class CustomFieldsController < ApplicationController
before_filter :require_admin
def index
- list
- render :action => 'list' unless request.xhr?
- end
-
- def list
@custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
@tab = params[:tab] || 'IssueCustomField'
- render :action => "list", :layout => false if request.xhr?
end
def new
- case params[:type]
- when "IssueCustomField"
- @custom_field = IssueCustomField.new(params[:custom_field])
- @custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids]
- when "UserCustomField"
- @custom_field = UserCustomField.new(params[:custom_field])
- when "ProjectCustomField"
- @custom_field = ProjectCustomField.new(params[:custom_field])
- when "TimeEntryCustomField"
- @custom_field = TimeEntryCustomField.new(params[:custom_field])
- else
- redirect_to :action => 'list'
- return
- end
+ @custom_field = begin
+ if params[:type].to_s.match(/.+CustomField$/)
+ params[:type].to_s.constantize.new(params[:custom_field])
+ end
+ rescue
+ end
+ redirect_to(:action => 'index') and return unless @custom_field.is_a?(CustomField)
+
if request.post? and @custom_field.save
flash[:notice] = l(:notice_successful_create)
- redirect_to :action => 'list', :tab => @custom_field.class.name
+ redirect_to :action => 'index', :tab => @custom_field.class.name
end
@trackers = Tracker.find(:all, :order => 'position')
end
@@ -54,11 +42,8 @@ class CustomFieldsController < ApplicationController
def edit
@custom_field = CustomField.find(params[:id])
if request.post? and @custom_field.update_attributes(params[:custom_field])
- if @custom_field.is_a? IssueCustomField
- @custom_field.trackers = params[:tracker_ids] ? Tracker.find(params[:tracker_ids]) : []
- end
flash[:notice] = l(:notice_successful_update)
- redirect_to :action => 'list', :tab => @custom_field.class.name
+ redirect_to :action => 'index', :tab => @custom_field.class.name
end
@trackers = Tracker.find(:all, :order => 'position')
end
@@ -75,14 +60,14 @@ class CustomFieldsController < ApplicationController
when 'lowest'
@custom_field.move_to_bottom
end if params[:position]
- redirect_to :action => 'list', :tab => @custom_field.class.name
+ redirect_to :action => 'index', :tab => @custom_field.class.name
end
def destroy
@custom_field = CustomField.find(params[:id]).destroy
- redirect_to :action => 'list', :tab => @custom_field.class.name
+ redirect_to :action => 'index', :tab => @custom_field.class.name
rescue
flash[:error] = "Unable to delete custom field"
- redirect_to :action => 'list'
+ redirect_to :action => 'index'
end
end
diff --git a/app/controllers/documents_controller.rb b/app/controllers/documents_controller.rb
index dbf9cd8e5..2d1c414c9 100644
--- a/app/controllers/documents_controller.rb
+++ b/app/controllers/documents_controller.rb
@@ -35,6 +35,7 @@ class DocumentsController < ApplicationController
else
@grouped = documents.group_by(&:category)
end
+ @document = @project.documents.build
render :layout => false if request.xhr?
end
@@ -70,11 +71,6 @@ class DocumentsController < ApplicationController
Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('document_added')
redirect_to :action => 'show', :id => @document
end
-
- def destroy_attachment
- @document.attachments.find(params[:attachment_id]).destroy
- redirect_to :action => 'show', :id => @document
- end
private
def find_project
diff --git a/app/controllers/issue_relations_controller.rb b/app/controllers/issue_relations_controller.rb
index 2ca3f0d68..8a41c3830 100644
--- a/app/controllers/issue_relations_controller.rb
+++ b/app/controllers/issue_relations_controller.rb
@@ -21,6 +21,9 @@ class IssueRelationsController < ApplicationController
def new
@relation = IssueRelation.new(params[:relation])
@relation.issue_from = @issue
+ if params[:relation] && !params[:relation][:issue_to_id].blank?
+ @relation.issue_to = Issue.visible.find_by_id(params[:relation][:issue_to_id])
+ end
@relation.save if request.post?
respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb
index 04f78092b..557347bba 100644
--- a/app/controllers/issues_controller.rb
+++ b/app/controllers/issues_controller.rb
@@ -18,11 +18,11 @@
class IssuesController < ApplicationController
menu_item :new_issue, :only => :new
- before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment]
+ before_filter :find_issue, :only => [:show, :edit, :reply]
before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
- before_filter :find_project, :only => [:new, :update_form, :preview, :gantt, :calendar]
- before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu]
- before_filter :find_optional_project, :only => [:index, :changes]
+ before_filter :find_project, :only => [:new, :update_form, :preview]
+ before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
+ before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
accept_key_auth :index, :changes
helper :journals
@@ -30,8 +30,6 @@ class IssuesController < ApplicationController
include ProjectsHelper
helper :custom_fields
include CustomFieldsHelper
- helper :ifpdf
- include IfpdfHelper
helper :issue_relations
include IssueRelationsHelper
helper :watchers
@@ -43,11 +41,13 @@ class IssuesController < ApplicationController
include SortHelper
include IssuesHelper
helper :timelog
+ include Redmine::Export::PDF
def index
- sort_init "#{Issue.table_name}.id", "desc"
- sort_update
retrieve_query
+ sort_init 'id', 'desc'
+ sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
+
if @query.valid?
limit = per_page_option
respond_to do |format|
@@ -67,7 +67,7 @@ class IssuesController < ApplicationController
format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
- format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
+ format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') }
end
else
# Send html if the query is not valid
@@ -78,9 +78,10 @@ class IssuesController < ApplicationController
end
def changes
- sort_init "#{Issue.table_name}.id", "desc"
- sort_update
retrieve_query
+ sort_init 'id', 'desc'
+ sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
+
if @query.valid?
@journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
:conditions => @query.statement,
@@ -104,7 +105,7 @@ class IssuesController < ApplicationController
respond_to do |format|
format.html { render :template => 'issues/show.rhtml' }
format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
- format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
+ format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
end
end
@@ -121,7 +122,10 @@ class IssuesController < ApplicationController
render :nothing => true, :layout => true
return
end
- @issue.attributes = params[:issue]
+ if params[:issue].is_a?(Hash)
+ @issue.attributes = params[:issue]
+ @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
+ end
@issue.author = User.current
default_status = IssueStatus.default
@@ -143,7 +147,9 @@ class IssuesController < ApplicationController
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
+ call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
+ redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
+ { :action => 'show', :id => @issue })
return
end
end
@@ -176,9 +182,12 @@ class IssuesController < ApplicationController
@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)}
+
+ call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
+
if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
# Log spend time
- if current_role.allowed_to?(:log_time)
+ if User.current.allowed_to?(:log_time, @project)
@time_entry.save
end
if !journal.new_record?
@@ -186,6 +195,7 @@ class IssuesController < ApplicationController
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
end
+ call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
end
end
@@ -222,6 +232,7 @@ class IssuesController < ApplicationController
assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
+ custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
unsaved_issue_ids = []
@issues.each do |issue|
@@ -233,6 +244,7 @@ class IssuesController < ApplicationController
issue.start_date = params[:start_date] unless params[:start_date].blank?
issue.due_date = params[:due_date] unless params[:due_date].blank?
issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
+ issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
# Don't save any change to the issue if the user is not authorized to apply the requested status
if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
@@ -253,7 +265,8 @@ class IssuesController < ApplicationController
end
# Find potential statuses the user could be allowed to switch issues to
@available_statuses = Workflow.find(:all, :include => :new_status,
- :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
+ :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
+ @custom_fields = @project.issue_custom_fields.select {|f| f.field_format == 'list'}
end
def move
@@ -261,7 +274,7 @@ class IssuesController < ApplicationController
# find projects to which the user is allowed to move the issue
if User.current.admin?
# admin is allowed to move issues to any active (visible) project
- @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
+ @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
else
User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
end
@@ -273,7 +286,7 @@ class IssuesController < ApplicationController
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)
+ unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker, params[:copy_options])
end
if unsaved_issue_ids.empty?
flash[:notice] = l(:notice_successful_update) unless @issues.empty?
@@ -310,17 +323,6 @@ class IssuesController < ApplicationController
@issues.each(&:destroy)
redirect_to :action => 'index', :project_id => @project
end
-
- def destroy_attachment
- a = @issue.attachments.find(params[:attachment_id])
- a.destroy
- journal = @issue.init_journal(User.current)
- journal.details << JournalDetail.new(:property => 'attachment',
- :prop_key => a.id,
- :old_value => a.filename)
- journal.save
- redirect_to :action => 'show', :id => @issue
- end
def gantt
@gantt = Redmine::Helpers::Gantt.new(params)
@@ -348,8 +350,8 @@ class IssuesController < ApplicationController
respond_to do |format|
format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
- format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
- format.pdf { send_data(render(:template => "issues/gantt.rfpdf", :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-gantt.pdf") }
+ format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.png") } if @gantt.respond_to?('to_image')
+ format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
end
end
@@ -450,9 +452,9 @@ private
end
def find_optional_project
- return true unless params[:project_id]
- @project = Project.find(params[:project_id])
- authorize
+ @project = Project.find(params[:project_id]) unless params[:project_id].blank?
+ allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
+ allowed ? true : deny_access
rescue ActiveRecord::RecordNotFound
render_404
end
diff --git a/app/controllers/journals_controller.rb b/app/controllers/journals_controller.rb
index 6df54f098..d479855f2 100644
--- a/app/controllers/journals_controller.rb
+++ b/app/controllers/journals_controller.rb
@@ -22,6 +22,7 @@ class JournalsController < ApplicationController
if request.post?
@journal.update_attributes(:notes => params[:notes]) if params[:notes]
@journal.destroy if @journal.details.empty? && @journal.notes.blank?
+ call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id }
format.js { render :action => 'update' }
diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb
index 79b4b616a..af39efb21 100644
--- a/app/controllers/messages_controller.rb
+++ b/app/controllers/messages_controller.rb
@@ -19,7 +19,7 @@ class MessagesController < ApplicationController
menu_item :boards
before_filter :find_board, :only => [:new, :preview]
before_filter :find_message, :except => [:new, :preview]
- before_filter :authorize, :except => :preview
+ before_filter :authorize, :except => [:preview, :edit, :destroy]
verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
verify :xhr => true, :only => :quote
@@ -30,7 +30,7 @@ class MessagesController < ApplicationController
# Show a topic and its replies
def show
- @replies = @topic.children
+ @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}])
@replies.reverse! if User.current.wants_comments_in_reverse_order?
@reply = Message.new(:subject => "RE: #{@message.subject}")
render :action => "show", :layout => false if request.xhr?
@@ -65,7 +65,8 @@ class MessagesController < ApplicationController
# Edit a message
def edit
- if params[:message] && User.current.allowed_to?(:edit_messages, @project)
+ render_403 and return false unless @message.editable_by?(User.current)
+ if params[:message]
@message.locked = params[:message]['locked']
@message.sticky = params[:message]['sticky']
end
@@ -78,6 +79,7 @@ class MessagesController < ApplicationController
# Delete a messages
def destroy
+ render_403 and return false unless @message.destroyable_by?(User.current)
@message.destroy
redirect_to @message.parent.nil? ?
{ :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
diff --git a/app/controllers/my_controller.rb b/app/controllers/my_controller.rb
index 1cfa3e531..5e1b9d2c8 100644
--- a/app/controllers/my_controller.rb
+++ b/app/controllers/my_controller.rb
@@ -19,6 +19,7 @@ class MyController < ApplicationController
before_filter :require_login
helper :issues
+ helper :custom_fields
BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
'issuesreportedbyme' => :label_reported_issues,
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index fc33336a0..a75e4120a 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -29,12 +29,16 @@ class ProjectsController < ApplicationController
before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
accept_key_auth :activity
+ after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller|
+ if controller.request.post?
+ controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
+ end
+ end
+
helper :sort
include SortHelper
helper :custom_fields
include CustomFieldsHelper
- helper :ifpdf
- include IfpdfHelper
helper :issues
helper IssuesHelper
helper :queries
@@ -45,17 +49,14 @@ class ProjectsController < ApplicationController
# Lists visible projects
def index
- projects = Project.find :all,
- :conditions => Project.visible_by(User.current),
- :include => :parent
respond_to do |format|
format.html {
- @project_tree = projects.group_by {|p| p.parent || p}
- @project_tree.keys.each {|p| @project_tree[p] -= [p]}
+ @projects = Project.visible.find(:all, :order => 'lft')
}
format.atom {
- render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
- :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
+ projects = Project.visible.find(:all, :order => 'created_on DESC',
+ :limit => Setting.feeds_limit.to_i)
+ render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
}
end
end
@@ -64,9 +65,6 @@ class ProjectsController < ApplicationController
def add
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
- @root_projects = Project.find(:all,
- :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
- :order => 'name')
@project = Project.new(params[:project])
if request.get?
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
@@ -76,6 +74,7 @@ class ProjectsController < ApplicationController
else
@project.enabled_module_names = params[:enabled_modules]
if @project.save
+ @project.set_parent!(params[:project]['parent_id']) if User.current.admin? && params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'admin', :action => 'projects'
end
@@ -84,20 +83,26 @@ class ProjectsController < ApplicationController
# Show @project
def show
+ if params[:jump]
+ # try to redirect to the requested menu item
+ redirect_to_project_menu_item(@project, params[:jump]) && return
+ end
+
@members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
- @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
+ @subprojects = @project.children.visible
+ @ancestors = @project.ancestors.visible
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@trackers = @project.rolled_up_trackers
cond = @project.project_condition(Setting.display_subprojects_issues?)
- Issue.visible_by(User.current) do
- @open_issues_by_tracker = Issue.count(:group => :tracker,
+
+ @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
:include => [:project, :status, :tracker],
:conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
- @total_issues_by_tracker = Issue.count(:group => :tracker,
+ @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
:include => [:project, :status, :tracker],
:conditions => cond)
- end
+
TimeEntry.visible_by(User.current) do
@total_hours = TimeEntry.sum(:hours,
:include => :project,
@@ -107,9 +112,6 @@ class ProjectsController < ApplicationController
end
def settings
- @root_projects = Project.find(:all,
- :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
- :order => 'name')
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@issue_category ||= IssueCategory.new
@member ||= @project.members.new
@@ -123,6 +125,7 @@ class ProjectsController < ApplicationController
if request.post?
@project.attributes = params[:project]
if @project.save
+ @project.set_parent!(params[:project]['parent_id']) if User.current.admin? && params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project
else
@@ -188,18 +191,26 @@ class ProjectsController < ApplicationController
def add_file
if request.post?
- @version = @project.versions.find_by_id(params[:version_id])
- attachments = attach_files(@version, params[:attachments])
- Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('file_added')
+ container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
+ attachments = attach_files(container, params[:attachments])
+ if !attachments.empty? && Setting.notified_events.include?('file_added')
+ Mailer.deliver_attachments_added(attachments)
+ end
redirect_to :controller => 'projects', :action => 'list_files', :id => @project
+ return
end
@versions = @project.versions.sort
end
def list_files
- sort_init "#{Attachment.table_name}.filename", "asc"
- sort_update
- @versions = @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
+ sort_init 'filename', 'asc'
+ sort_update 'filename' => "#{Attachment.table_name}.filename",
+ 'created_on' => "#{Attachment.table_name}.created_on",
+ 'size' => "#{Attachment.table_name}.filesize",
+ 'downloads' => "#{Attachment.table_name}.downloads"
+
+ @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
+ @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
render :layout => !request.xhr?
end
@@ -221,16 +232,19 @@ class ProjectsController < ApplicationController
@days = Setting.activity_days_default.to_i
if params[:from]
- begin; @date_to = params[:from].to_date; rescue; end
+ begin; @date_to = params[:from].to_date + 1; rescue; end
end
@date_to ||= Date.today + 1
@date_from = @date_to - @days
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
+ @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
- @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project, :with_subprojects => @with_subprojects)
+ @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
+ :with_subprojects => @with_subprojects,
+ :author => @author)
@activity.scope_select {|t| !params["show_#{t}"].nil?}
- @activity.default_scope! if @activity.scope.empty?
+ @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
events = @activity.events(@date_from, @date_to)
@@ -240,10 +254,18 @@ class ProjectsController < ApplicationController
render :layout => false if request.xhr?
}
format.atom {
- title = (@activity.scope.size == 1) ? l("label_#{@activity.scope.first.singularize}_plural") : l(:label_activity)
+ title = l(:label_activity)
+ if @author
+ title = @author.name
+ elsif @activity.scope.size == 1
+ title = l("label_#{@activity.scope.first.singularize}_plural")
+ end
render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
}
end
+
+ rescue ActiveRecord::RecordNotFound
+ render_404
end
private
diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb
index dd3ece930..bfe515778 100644
--- a/app/controllers/reports_controller.rb
+++ b/app/controllers/reports_controller.rb
@@ -61,7 +61,7 @@ class ReportsController < ApplicationController
render :template => "reports/issue_report_details"
when "subproject"
@field = "project_id"
- @rows = @project.active_children
+ @rows = @project.descendants.active
@data = issues_by_subproject
@report_title = l(:field_subproject)
render :template => "reports/issue_report_details"
@@ -72,7 +72,7 @@ class ReportsController < ApplicationController
@categories = @project.issue_categories
@assignees = @project.members.collect { |m| m.user }
@authors = @project.members.collect { |m| m.user }
- @subprojects = @project.active_children
+ @subprojects = @project.descendants.active
issues_by_tracker
issues_by_version
issues_by_priority
@@ -229,8 +229,8 @@ private
#{Issue.table_name} i, #{IssueStatus.table_name} s
where
i.status_id=s.id
- and i.project_id IN (#{@project.active_children.collect{|p| p.id}.join(',')})
- group by s.id, s.is_closed, i.project_id") if @project.active_children.any?
+ and i.project_id IN (#{@project.descendants.active.collect{|p| p.id}.join(',')})
+ group by s.id, s.is_closed, i.project_id") if @project.descendants.active.any?
@issues_by_subproject ||= []
end
end
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index 78576856d..a90b57c3d 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -44,6 +44,21 @@ class RepositoriesController < ApplicationController
render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
end
+ def committers
+ @committers = @repository.committers
+ @users = @project.users
+ additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
+ @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
+ @users.compact!
+ @users.sort!
+ if request.post? && params[:committers].is_a?(Hash)
+ # Build a hash with repository usernames as keys and corresponding user ids as values
+ @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'committers', :id => @project
+ end
+ end
+
def destroy
@repository.destroy
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
@@ -73,7 +88,7 @@ class RepositoriesController < ApplicationController
def changes
@entry = @repository.entry(@path, @rev)
show_error_not_found and return unless @entry
- @changesets = @repository.changesets_for_path(@path)
+ @changesets = @repository.changesets_for_path(@path, :limit => Setting.repository_log_display_limit.to_i)
@properties = @repository.properties(@path, @rev)
end
@@ -84,7 +99,8 @@ class RepositoriesController < ApplicationController
params['page']
@changesets = @repository.changesets.find(:all,
:limit => @changeset_pages.items_per_page,
- :offset => @changeset_pages.current.offset)
+ :offset => @changeset_pages.current.offset,
+ :include => :user)
respond_to do |format|
format.html { render :layout => false if request.xhr? }
@@ -111,6 +127,9 @@ class RepositoriesController < ApplicationController
end
def annotate
+ @entry = @repository.entry(@path, @rev)
+ show_error_not_found and return unless @entry
+
@annotate = @repository.scm.annotate(@path, @rev)
render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
end
diff --git a/app/controllers/roles_controller.rb b/app/controllers/roles_controller.rb
index 72555e5b0..ab70ebf41 100644
--- a/app/controllers/roles_controller.rb
+++ b/app/controllers/roles_controller.rb
@@ -79,27 +79,6 @@ class RolesController < ApplicationController
redirect_to :action => 'list'
end
- def workflow
- @role = Role.find_by_id(params[:role_id])
- @tracker = Tracker.find_by_id(params[:tracker_id])
-
- if request.post?
- Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
- (params[:issue_status] || []).each { |old, news|
- news.each { |new|
- @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new)
- }
- }
- if @role.save
- flash[:notice] = l(:notice_successful_update)
- redirect_to :action => 'workflow', :role_id => @role, :tracker_id => @tracker
- end
- end
- @roles = Role.find(:all, :order => 'builtin, position')
- @trackers = Tracker.find(:all, :order => 'position')
- @statuses = IssueStatus.find(:all, :order => 'position')
- end
-
def report
@roles = Role.find(:all, :order => 'builtin, position')
@permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index e6e66f05c..485d2349d 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -34,7 +34,7 @@ class SearchController < ApplicationController
when 'my_projects'
User.current.memberships.collect(&:project)
when 'subprojects'
- @project ? ([ @project ] + @project.active_children) : nil
+ @project ? (@project.self_and_descendants.active) : nil
else
@project
end
diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb
index 6482a3576..7d99a7266 100644
--- a/app/controllers/settings_controller.rb
+++ b/app/controllers/settings_controller.rb
@@ -5,19 +5,19 @@
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
-#
+#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
-#
+#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class SettingsController < ApplicationController
before_filter :require_admin
-
+
def index
edit
render :action => 'edit'
@@ -39,17 +39,21 @@ class SettingsController < ApplicationController
@options = {}
@options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] }
@deliveries = ActionMailer::Base.perform_deliveries
+
+ @guessed_host_and_path = request.host_with_port.dup
+ @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
end
-
+
def plugin
- plugin_id = params[:id].to_sym
- @plugin = Redmine::Plugin.registered_plugins[plugin_id]
+ @plugin = Redmine::Plugin.find(params[:id])
if request.post?
- Setting["plugin_#{plugin_id}"] = params[:settings]
+ Setting["plugin_#{@plugin.id}"] = params[:settings]
flash[:notice] = l(:notice_successful_update)
- redirect_to :action => 'plugin', :id => params[:id]
+ redirect_to :action => 'plugin', :id => @plugin.id
end
@partial = @plugin.settings[:partial]
- @settings = Setting["plugin_#{plugin_id}"]
+ @settings = Setting["plugin_#{@plugin.id}"]
+ rescue Redmine::PluginNotFound
+ render_404
end
end
diff --git a/app/controllers/sys_controller.rb b/app/controllers/sys_controller.rb
index 8aff3bd15..da4b119a7 100644
--- a/app/controllers/sys_controller.rb
+++ b/app/controllers/sys_controller.rb
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2009 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -16,31 +16,35 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class SysController < ActionController::Base
- wsdl_service_name 'Sys'
- web_service_api SysApi
- web_service_scaffold :invoke
+ before_filter :check_enabled
- before_invocation :check_enabled
-
- # Returns the projects list, with their repositories
- def projects_with_repository_enabled
- Project.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
+ def projects
+ p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
+ render :xml => p.to_xml(:include => :repository)
end
-
- # Registers a repository for the given project identifier
- def repository_created(identifier, vendor, url)
- project = Project.find_by_identifier(identifier)
- # Do not create the repository if the project has already one
- return 0 unless project && project.repository.nil?
- logger.debug "Repository for #{project.name} was created"
- repository = Repository.factory(vendor, :project => project, :url => url)
- repository.save
- repository.id || 0
+
+ def create_project_repository
+ project = Project.find(params[:id])
+ if project.repository
+ render :nothing => true, :status => 409
+ else
+ logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
+ project.repository = Repository.factory(params[:vendor], params[:repository])
+ if project.repository && project.repository.save
+ render :xml => project.repository, :status => 201
+ else
+ render :nothing => true, :status => 422
+ end
+ end
end
-protected
+ protected
- def check_enabled(name, args)
- Setting.sys_api_enabled?
+ def check_enabled
+ User.current = nil
+ unless Setting.sys_api_enabled?
+ render :nothing => 'Access denied. Repository management WS is disabled.', :status => 403
+ return false
+ end
end
end
diff --git a/app/controllers/timelog_controller.rb b/app/controllers/timelog_controller.rb
index c333c02bb..58df1f5bc 100644
--- a/app/controllers/timelog_controller.rb
+++ b/app/controllers/timelog_controller.rb
@@ -138,7 +138,12 @@ class TimelogController < ApplicationController
def details
sort_init 'spent_on', 'desc'
- sort_update
+ sort_update 'spent_on' => 'spent_on',
+ 'user' => 'user_id',
+ 'activity' => 'activity_id',
+ 'project' => "#{Project.table_name}.name",
+ 'issue' => 'issue_id',
+ 'hours' => 'hours'
cond = ARCondition.new
if @project.nil?
diff --git a/app/controllers/trackers_controller.rb b/app/controllers/trackers_controller.rb
index 8c02f9474..51e70ddf4 100644
--- a/app/controllers/trackers_controller.rb
+++ b/app/controllers/trackers_controller.rb
@@ -40,8 +40,10 @@ class TrackersController < ApplicationController
end
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list'
+ return
end
@trackers = Tracker.find :all, :order => 'position'
+ @projects = Project.find(:all)
end
def edit
@@ -49,7 +51,9 @@ class TrackersController < ApplicationController
if request.post? and @tracker.update_attributes(params[:tracker])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list'
+ return
end
+ @projects = Project.find(:all)
end
def move
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index d2564c2cb..ced17d667 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -30,18 +30,22 @@ class UsersController < ApplicationController
def list
sort_init 'login', 'asc'
- sort_update
+ sort_update %w(login firstname lastname mail admin created_on last_login_on)
@status = params[:status] ? params[:status].to_i : 1
- conditions = "status <> 0"
- conditions = ["status=?", @status] unless @status == 0
+ c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
+
+ unless params[:name].blank?
+ name = "%#{params[:name].strip.downcase}%"
+ c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ?", name, name, name]
+ end
- @user_count = User.count(:conditions => conditions)
+ @user_count = User.count(:conditions => c.conditions)
@user_pages = Paginator.new self, @user_count,
per_page_option,
params['page']
@users = User.find :all,:order => sort_clause,
- :conditions => conditions,
+ :conditions => c.conditions,
:limit => @user_pages.items_per_page,
:offset => @user_pages.current.offset
@@ -79,7 +83,7 @@ class UsersController < ApplicationController
end
@auth_sources = AuthSource.find(:all)
@roles = Role.find_all_givable
- @projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects
+ @projects = Project.active.find(:all, :order => 'lft')
@membership ||= Member.new
@memberships = @user.memberships
end
diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb
index 3a2221761..c269432f3 100644
--- a/app/controllers/versions_controller.rb
+++ b/app/controllers/versions_controller.rb
@@ -37,12 +37,6 @@ class VersionsController < ApplicationController
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end
- def destroy_file
- @version.attachments.find(params[:attachment_id]).destroy
- flash[:notice] = l(:notice_successful_delete)
- redirect_to :controller => 'projects', :action => 'list_files', :id => @project
- end
-
def status_by
respond_to do |format|
format.html { render :action => 'show' }
diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb
index b8108e8ac..c14ec4dbe 100644
--- a/app/controllers/welcome_controller.rb
+++ b/app/controllers/welcome_controller.rb
@@ -16,9 +16,15 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class WelcomeController < ApplicationController
+ caches_action :robots
def index
@news = News.latest User.current
@projects = Project.latest User.current
end
+
+ def robots
+ @projects = Project.public.active
+ render :layout => false, :content_type => 'text/plain'
+ end
end
diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb
index 114010dff..1a480e4bd 100644
--- a/app/controllers/wiki_controller.rb
+++ b/app/controllers/wiki_controller.rb
@@ -19,8 +19,9 @@ require 'diff'
class WikiController < ApplicationController
before_filter :find_wiki, :authorize
+ before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
- verify :method => :post, :only => [:destroy, :destroy_attachment, :protect], :redirect_to => { :action => :index }
+ verify :method => :post, :only => [:destroy, :protect], :redirect_to => { :action => :index }
helper :attachments
include AttachmentsHelper
@@ -44,11 +45,11 @@ class WikiController < ApplicationController
return
end
@content = @page.content_for_version(params[:version])
- if params[:export] == 'html'
+ if params[:format] == 'html'
export = render_to_string :action => 'export', :layout => false
send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
return
- elsif params[:export] == 'txt'
+ elsif params[:format] == 'txt'
send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
return
end
@@ -63,7 +64,7 @@ class WikiController < ApplicationController
@page.content = WikiContent.new(:page => @page) if @page.new_record?
@content = @page.content_for_version(params[:version])
- @content.text = "h1. #{@page.pretty_title}" if @content.text.blank?
+ @content.text = initial_page_content(@page) if @content.text.blank?
# don't keep previous comment
@content.comments = nil
if request.get?
@@ -91,8 +92,7 @@ class WikiController < ApplicationController
# rename a page
def rename
- @page = @wiki.find_page(params[:page])
- return render_403 unless editable?
+ 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
@@ -103,15 +103,12 @@ class WikiController < ApplicationController
end
def protect
- page = @wiki.find_page(params[:page])
- page.update_attribute :protected, params[:protected]
- redirect_to :action => 'index', :id => @project, :page => page.title
+ @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])
-
@version_count = @page.content.versions.count
@version_pages = Paginator.new self, @version_count, per_page_option, params['p']
# don't load text
@@ -125,21 +122,19 @@ class WikiController < ApplicationController
end
def diff
- @page = @wiki.find_page(params[:page])
@diff = @page.diff(params[:version], params[:version_from])
render_404 unless @diff
end
def annotate
- @page = @wiki.find_page(params[:page])
@annotate = @page.annotate(params[:version])
+ render_404 unless @annotate
end
# remove a wiki page and its history
def destroy
- @page = @wiki.find_page(params[:page])
- return render_403 unless editable?
- @page.destroy if @page
+ return render_403 unless editable?
+ @page.destroy
redirect_to :action => 'special', :id => @project, :page => 'Page_index'
end
@@ -181,19 +176,11 @@ class WikiController < ApplicationController
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
-
private
def find_wiki
@@ -204,8 +191,21 @@ private
render_404
end
+ # Finds the requested page and returns a 404 error if it doesn't exist
+ def find_existing_page
+ @page = @wiki.find_page(params[:page])
+ render_404 if @page.nil?
+ 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
+
+ # Returns the default content of a new wiki page
+ def initial_page_content(page)
+ helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
+ extend helper unless self.instance_of?(helper)
+ helper.instance_method(:initial_page_content).bind(self).call(page)
+ end
end
diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb
new file mode 100644
index 000000000..380d4e752
--- /dev/null
+++ b/app/controllers/workflows_controller.rb
@@ -0,0 +1,45 @@
+# 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 WorkflowsController < ApplicationController
+ before_filter :require_admin
+
+ def index
+ @workflow_counts = Workflow.count_by_tracker_and_role
+ end
+
+ def edit
+ @role = Role.find_by_id(params[:role_id])
+ @tracker = Tracker.find_by_id(params[:tracker_id])
+
+ if request.post?
+ Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
+ (params[:issue_status] || []).each { |old, news|
+ news.each { |new|
+ @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new)
+ }
+ }
+ if @role.save
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker
+ end
+ end
+ @roles = Role.find(:all, :order => 'builtin, position')
+ @trackers = Tracker.find(:all, :order => 'position')
+ @statuses = IssueStatus.find(:all, :order => 'position')
+ end
+end
diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb
index 1b41d8374..b49a5674c 100644
--- a/app/helpers/admin_helper.rb
+++ b/app/helpers/admin_helper.rb
@@ -17,7 +17,15 @@
module AdminHelper
def project_status_options_for_select(selected)
- options_for_select([[l(:label_all), "*"],
+ options_for_select([[l(:label_all), ''],
[l(:status_active), 1]], selected)
end
+
+ def css_project_classes(project)
+ s = 'project'
+ s << ' root' if project.root?
+ s << ' child' if project.child?
+ s << (project.leaf? ? ' leaf' : ' parent')
+ s
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index c3701b377..e7aa27033 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -5,26 +5,32 @@
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
-#
+#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
-#
+#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'coderay'
require 'coderay/helpers/file_type'
+require 'forwardable'
+require 'cgi'
module ApplicationHelper
include Redmine::WikiFormatting::Macros::Definitions
+ include GravatarHelper::PublicMethods
+
+ extend Forwardable
+ def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
def current_role
@current_role ||= User.current.role_for_project(@project)
end
-
+
# Return true if user is authorized for controller/action, otherwise false
def authorize_for(controller, action)
User.current.allowed_to?({:controller => controller, :action => action}, @project)
@@ -34,7 +40,7 @@ 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] || {}
@@ -42,17 +48,17 @@ module ApplicationHelper
end
# Display a link to user's account page
- def link_to_user(user)
- user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
+ def link_to_user(user, options={})
+ (user && !user.anonymous?) ? link_to(user.name(options[:format]), :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
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)
@@ -60,37 +66,37 @@ module ApplicationHelper
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(); ")
onclick << "return false;"
link_to(name, "#", :onclick => onclick)
end
-
+
def image_to_function(name, function, html_options = {})
html_options.symbolize_keys!
- tag(:input, html_options.merge({
- :type => "image", :src => image_path(name),
- :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
+ tag(:input, html_options.merge({
+ :type => "image", :src => image_path(name),
+ :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
}))
end
-
+
def prompt_to_remote(name, text, param, url, html_options = {})
html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
link_to name, {}, html_options
end
-
+
def format_date(date)
return nil unless date
# "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
@date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
date.strftime(@date_format)
end
-
+
def format_time(time, include_date = true)
return nil unless time
time = time.to_time if time.is_a?(String)
@@ -100,43 +106,147 @@ module ApplicationHelper
@time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
end
-
+
+ def format_activity_title(text)
+ h(truncate_single_line(text, 100))
+ 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.to_s, 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
+ end
+
def distance_of_date_in_words(from_date, to_date = 0)
from_date = from_date.to_date if from_date.respond_to?(:to_date)
to_date = to_date.to_date if to_date.respond_to?(:to_date)
distance_in_days = (to_date - from_date).abs
lwr(:actionview_datehelper_time_in_words_day, distance_in_days)
end
-
+
def due_date_distance_in_words(date)
if date
l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
end
end
+
+ def render_page_hierarchy(pages, node=nil)
+ content = ''
+ if pages[node]
+ content << "<ul class=\"pages-hierarchy\">\n"
+ pages[node].each do |page|
+ content << "<li>"
+ content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
+ :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
+ content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
+ content << "</li>\n"
+ end
+ content << "</ul>\n"
+ end
+ content
+ end
+ # Renders flash messages
+ def render_flash_messages
+ s = ''
+ flash.each do |k,v|
+ s << content_tag('div', v, :class => "flash #{k}")
+ end
+ s
+ end
+
+ # Renders the project quick-jump box
+ def render_project_jump_box
+ # Retrieve them now to avoid a COUNT query
+ projects = User.current.projects.all
+ if projects.any?
+ s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
+ "<option selected='selected'>#{ l(:label_jump_to_a_project) }</option>" +
+ '<option disabled="disabled">---</option>'
+ s << project_tree_options_for_select(projects) do |p|
+ { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
+ end
+ s << '</select>'
+ s
+ end
+ end
+
+ def project_tree_options_for_select(projects, options = {})
+ s = ''
+ project_tree(projects) do |project, level|
+ name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
+ tag_options = {:value => project.id, :selected => ((project == options[:selected]) ? 'selected' : nil)}
+ tag_options.merge!(yield(project)) if block_given?
+ s << content_tag('option', name_prefix + h(project), tag_options)
+ end
+ s
+ end
+
+ # Yields the given block for each project with its level in the tree
+ def project_tree(projects, &block)
+ ancestors = []
+ projects.sort_by(&:lft).each do |project|
+ while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
+ ancestors.pop
+ end
+ yield project, ancestors.size
+ ancestors << project
+ end
+ end
+
+ def project_nested_ul(projects, &block)
+ s = ''
+ if projects.any?
+ ancestors = []
+ projects.sort_by(&:lft).each do |project|
+ if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
+ s << "<ul>\n"
+ else
+ ancestors.pop
+ s << "</li>"
+ while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
+ ancestors.pop
+ s << "</ul></li>\n"
+ end
+ end
+ s << "<li>"
+ s << yield(project).to_s
+ ancestors << project
+ end
+ s << ("</li></ul>\n" * ancestors.size)
+ end
+ s
+ end
+
# 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+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
end
-
- def authoring(created, author)
- time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
+
+ def authoring(created, author, options={})
+ time_tag = @project.nil? ? content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created)) :
+ link_to(distance_of_time_in_words(Time.now, created),
+ {:controller => 'projects', :action => 'activity', :id => @project, :from => created.to_date},
+ :title => format_time(created))
author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
- l(:label_added_time_by, author_tag, time_tag)
+ l(options[:label] || :label_added_time_by, author_tag, time_tag)
end
-
- def l_or_humanize(s)
- l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
+
+ def l_or_humanize(s, options={})
+ k = "#{options[:prefix]}#{s}".to_sym
+ l_has_string?(k) ? l(k) : s.to_s.humanize
end
-
+
def day_name(day)
l(:general_day_names).split(',')[day-1]
end
-
+
def month_name(month)
l(:actionview_datehelper_select_month_names).split(',')[month-1]
end
@@ -145,7 +255,7 @@ module ApplicationHelper
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
@@ -153,53 +263,56 @@ module ApplicationHelper
def pagination_links_full(paginator, count=nil, options={})
page_param = options.delete(:page_param) || :page
url_param = params.dup
- # don't reuse params if filters are present
- url_param.clear if url_param.has_key?(:set_filter)
-
- html = ''
- html << link_to_remote(('&#171; ' + l(:label_previous)),
- {:update => 'content',
- :url => url_param.merge(page_param => paginator.current.previous),
- :complete => 'window.scrollTo(0,0)'},
- {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
-
+ # don't reuse query params if filters are present
+ url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
+
+ html = ''
+ if paginator.current.previous
+ html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
+ end
+
html << (pagination_links_each(paginator, options) do |n|
- link_to_remote(n.to_s,
- {:url => {:params => url_param.merge(page_param => n)},
- :update => 'content',
- :complete => 'window.scrollTo(0,0)'},
- {:href => url_for(:params => url_param.merge(page_param => n))})
+ link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
end || '')
- html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
- {:update => 'content',
- :url => url_param.merge(page_param => paginator.current.next),
- :complete => 'window.scrollTo(0,0)'},
- {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
-
+ if paginator.current.next
+ html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
+ end
+
unless count.nil?
- html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
+ html << [
+ " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
+ per_page_links(paginator.items_per_page)
+ ].compact.join(' | ')
end
-
- html
+
+ html
end
def per_page_links(selected=nil)
url_param = params.dup
url_param.clear if url_param.has_key?(:set_filter)
-
+
links = Setting.per_page_options_array.collect do |n|
- n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
+ n == selected ? n : link_to_remote(n, {:update => "content",
+ :url => params.dup.merge(:per_page => n),
+ :method => :get},
{:href => url_for(url_param.merge(:per_page => n))})
end
links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
end
-
+
def breadcrumb(*args)
elements = args.flatten
elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
end
+ def other_formats_links(&block)
+ concat('<p class="other-formats">' + l(:label_export_to), block.binding)
+ yield Redmine::Views::OtherFormatsBuilder.new(self)
+ concat('</p>', block.binding)
+ end
+
def html_title(*args)
if args.empty?
title = []
@@ -234,32 +347,30 @@ module ApplicationHelper
raise ArgumentError, 'invalid arguments to textilizable'
end
return '' if text.blank?
-
+
only_path = options.delete(:only_path) == false ? false : true
# when using an image link, try to use an attachment, if possible
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
-
+
if attachments
- text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
+ attachments = attachments.sort_by(&:created_on).reverse
+ text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
style = $1
- filename = $6
- rf = Regexp.new(filename, Regexp::IGNORECASE)
+ filename = $6.downcase
# search for the picture in attachments
- if found = attachments.detect { |att| att.filename =~ rf }
+ if found = attachments.detect { |att| att.filename.downcase == filename }
image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
alt = desc.blank? ? nil : "(#{desc})"
"!#{style}#{image_url}#{alt}!"
else
- "!#{style}#{filename}!"
+ m
end
end
end
-
- text = (Setting.text_formatting == 'textile') ?
- Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
- simple_format(auto_link(h(text)))
+
+ text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
# different methods for formatting wiki links
case options[:wiki_links]
@@ -272,11 +383,11 @@ module ApplicationHelper
else
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)
-
+
# Wiki links
- #
+ #
# Examples:
# [[mypage]]
# [[mypage|mytext]]
@@ -294,7 +405,7 @@ module ApplicationHelper
page = $2
title ||= $1 if page.blank?
end
-
+
if link_project && link_project.wiki
# extract anchor
anchor = nil
@@ -307,7 +418,7 @@ module ApplicationHelper
:class => ('wiki-page' + (wiki_page ? '' : ' new')))
else
# project or wiki doesn't exist
- title || page
+ all
end
else
all
@@ -315,7 +426,7 @@ module ApplicationHelper
end
# Redmine links
- #
+ #
# Examples:
# Issues:
# #52 -> Link to issue #52
@@ -354,7 +465,7 @@ module ApplicationHelper
oid = oid.to_i
case prefix
when nil
- if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
+ if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
:class => (issue.closed? ? 'issue closed' : 'issue'),
:title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
@@ -422,10 +533,10 @@ module ApplicationHelper
end
leading + (link || "#{prefix}#{sep}#{oid}")
end
-
+
text
end
-
+
# Same as Rails' simple_format helper without using paragraphs
def simple_format_without_paragraph(text)
text.to_s.
@@ -433,7 +544,7 @@ module ApplicationHelper
gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
end
-
+
def error_messages_for(object_name, options = {})
options = options.symbolize_keys
object = instance_variable_get("@#{object_name}")
@@ -451,14 +562,14 @@ module ApplicationHelper
end
# retrieve custom values error messages
if object.errors[:custom_values]
- object.custom_values.each do |v|
+ object.custom_values.each do |v|
v.errors.each do |attr, msg|
next if msg.nil?
msg = msg.first if msg.is_a? Array
full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
end
end
- end
+ end
content_tag("div",
content_tag(
options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
@@ -470,34 +581,35 @@ module ApplicationHelper
""
end
end
-
+
def lang_options_for_select(blank=true)
- (blank ? [["(auto)", ""]] : []) +
+ (blank ? [["(auto)", ""]] : []) +
GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
end
-
+
def label_tag_for(name, option_tags = nil, options = {})
label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
content_tag("label", label_text)
end
-
+
def labelled_tabular_form_for(name, object, options, &proc)
options[:html] ||= {}
options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
end
-
+
def back_url_hidden_field_tag
back_url = params[:back_url] || request.env['HTTP_REFERER']
- hidden_field_tag('back_url', back_url) unless back_url.blank?
+ back_url = CGI.unescape(back_url.to_s)
+ hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
end
-
+
def check_all_links(form_name)
link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
" | " +
- link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
+ link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
end
-
+
def progress_bar(pcts, options={})
pcts = [pcts, pcts] unless pcts.is_a?(Array)
pcts[1] = pcts[1] - pcts[0]
@@ -506,13 +618,13 @@ module ApplicationHelper
legend = options[:legend] || ''
content_tag('table',
content_tag('tr',
- (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
- (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
- (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
+ (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0].floor}%;", :class => 'closed') : '') +
+ (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1].floor}%;", :class => 'done') : '') +
+ (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2].floor}%;", :class => 'todo') : '')
), :class => 'progress', :style => "width: #{width};") +
content_tag('p', legend, :class => 'pourcent')
end
-
+
def context_menu_link(name, url, options={})
options[:class] ||= ''
if options.delete(:selected)
@@ -528,7 +640,7 @@ module ApplicationHelper
end
link_to name, url, options
end
-
+
def calendar_for(field_id)
include_calendar_headers_tags
image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
@@ -546,26 +658,44 @@ module ApplicationHelper
end
end
end
-
- def wikitoolbar_for(field_id)
- return '' unless Setting.text_formatting == 'textile'
-
- help_link = l(:setting_text_formatting) + ': ' +
- link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
- :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
- javascript_include_tag('jstoolbar/jstoolbar') +
- javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
- javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
- end
-
def content_for(name, content = nil, &block)
@has_content ||= {}
@has_content[name] = true
super(name, content, &block)
end
-
+
def has_content?(name)
(@has_content && @has_content[name]) || false
end
+
+ # Returns the avatar image tag for the given +user+ if avatars are enabled
+ # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
+ def avatar(user, options = { })
+ if Setting.gravatar_enabled?
+ email = nil
+ if user.respond_to?(:mail)
+ email = user.mail
+ elsif user.to_s =~ %r{<(.+?)>}
+ email = $1
+ end
+ return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
+ end
+ end
+
+ private
+
+ def wiki_helper
+ helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
+ extend helper
+ return self
+ end
+
+ def link_to_remote_content_update(text, url_params)
+ link_to_remote(text,
+ {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
+ {:href => url_for(:params => url_params)}
+ )
+ end
+
end
diff --git a/app/helpers/attachments_helper.rb b/app/helpers/attachments_helper.rb
index ebf417bab..29cdb9790 100644
--- a/app/helpers/attachments_helper.rb
+++ b/app/helpers/attachments_helper.rb
@@ -16,10 +16,15 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module AttachmentsHelper
- # displays the links to a collection of attachments
- def link_to_attachments(attachments, options = {})
- if attachments.any?
- render :partial => 'attachments/links', :locals => {:attachments => attachments, :options => options}
+ # Displays view/delete links to the attachments of the given object
+ # Options:
+ # :author -- author names are not displayed if set to false
+ def link_to_attachments(container, options = {})
+ options.assert_valid_keys(:author)
+
+ if container.attachments.any?
+ options = {:deletable => container.attachments_deletable?, :author => true}.merge(options)
+ render :partial => 'attachments/links', :locals => {:attachments => container.attachments, :options => options}
end
end
diff --git a/app/helpers/ifpdf_helper.rb b/app/helpers/ifpdf_helper.rb
deleted file mode 100644
index 2cfca1929..000000000
--- a/app/helpers/ifpdf_helper.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-# redMine - project management software
-# Copyright (C) 2006 Jean-Philippe Lang
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-require 'iconv'
-require 'rfpdf/chinese'
-
-module IfpdfHelper
-
- class IFPDF < FPDF
- include GLoc
- attr_accessor :footer_date
-
- def initialize(lang)
- super()
- set_language_if_valid lang
- case current_language.to_s
- when 'ja'
- extend(PDF_Japanese)
- AddSJISFont()
- @font_for_content = 'SJIS'
- @font_for_footer = 'SJIS'
- when 'zh'
- extend(PDF_Chinese)
- AddGBFont()
- @font_for_content = 'GB'
- @font_for_footer = 'GB'
- when 'zh-tw'
- extend(PDF_Chinese)
- AddBig5Font()
- @font_for_content = 'Big5'
- @font_for_footer = 'Big5'
- else
- @font_for_content = 'Arial'
- @font_for_footer = 'Helvetica'
- end
- SetCreator("redMine #{Redmine::VERSION}")
- SetFont(@font_for_content)
- end
-
- def SetFontStyle(style, size)
- SetFont(@font_for_content, style, size)
- end
-
- def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
- @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
- # these quotation marks are not correctly rendered in the pdf
- txt = txt.gsub(/[“â€]/, '"') if txt
- txt = begin
- # 0x5c char handling
- txtar = txt.split('\\')
- txtar << '' if txt[-1] == ?\\
- txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\")
- rescue
- txt
- end || ''
- super w,h,txt,border,ln,align,fill,link
- end
-
- def Footer
- SetFont(@font_for_footer, 'I', 8)
- SetY(-15)
- SetX(15)
- Cell(0, 5, @footer_date, 0, 0, 'L')
- SetY(-15)
- SetX(-30)
- Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
- end
-
- end
-
-end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 43acabd19..b2b85ee4c 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -33,6 +33,14 @@ module IssuesHelper
"<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
end
+ # Returns a string of css classes that apply to the given issue
+ def css_issue_classes(issue)
+ s = "issue status-#{issue.status.position} priority-#{issue.priority.position}"
+ s << ' closed' if issue.closed?
+ s << ' overdue' if issue.overdue?
+ s
+ end
+
def sidebar_queries
unless @sidebar_queries
# User can see public queries and his own queries
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index cd2e743fe..912450c1c 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -21,18 +21,6 @@ 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.to_s, 250).gsub(%r{<(pre|code)>.*$}m, '...'))
- end
-
def project_settings_tabs
tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
{:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
@@ -45,4 +33,39 @@ module ProjectsHelper
]
tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
end
+
+ def parent_project_select_tag(project)
+ options = '<option></option>' + project_tree_options_for_select(project.possible_parents, :selected => project.parent)
+ content_tag('select', options, :name => 'project[parent_id]')
+ end
+
+ # Renders a tree of projects as a nested set of unordered lists
+ # The given collection may be a subset of the whole project tree
+ # (eg. some intermediate nodes are private and can not be seen)
+ def render_project_hierarchy(projects)
+ s = ''
+ if projects.any?
+ ancestors = []
+ projects.each do |project|
+ if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
+ s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
+ else
+ ancestors.pop
+ s << "</li>"
+ while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
+ ancestors.pop
+ s << "</ul></li>\n"
+ end
+ end
+ classes = (ancestors.empty? ? 'root' : 'child')
+ s << "<li class='#{classes}'><div class='#{classes}'>" +
+ link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
+ s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
+ s << "</div>\n"
+ ancestors << project
+ end
+ s << ("</li></ul>\n" * ancestors.size)
+ end
+ s
+ end
end
diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb
index a58c5d0ea..63d6a5356 100644
--- a/app/helpers/queries_helper.rb
+++ b/app/helpers/queries_helper.rb
@@ -22,8 +22,8 @@ module QueriesHelper
end
def column_header(column)
- column.sortable ? sort_header_tag(column.sortable, :caption => column.caption,
- :default_order => column.default_order) :
+ column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
+ :default_order => column.default_order) :
content_tag('th', column.caption)
end
@@ -44,6 +44,8 @@ module QueriesHelper
link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
when :done_ratio
progress_bar(value, :width => '80px')
+ when :fixed_version
+ link_to(h(value), { :controller => 'versions', :action => 'show', :id => issue.fixed_version_id })
else
h(value)
end
diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb
index d798a2a5f..8310b83bb 100644
--- a/app/helpers/repositories_helper.rb
+++ b/app/helpers/repositories_helper.rb
@@ -22,6 +22,12 @@ module RepositoriesHelper
txt.to_s[0,8]
end
+ def truncate_at_line_break(text, length = 255)
+ if text
+ text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')
+ end
+ end
+
def render_properties(properties)
unless properties.nil? || properties.empty?
content = ''
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index cd96dbd3f..32ff16f67 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -44,7 +44,7 @@ module SearchHelper
def project_select_tag
options = [[l(:label_project_all), 'all']]
options << [l(:label_my_projects), 'my_projects'] unless User.current.memberships.empty?
- options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.active_children.empty?
+ options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.descendants.active.empty?
options << [@project.name, ''] unless @project.nil?
select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1
end
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index 47e691334..35442f615 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -18,6 +18,7 @@
module SettingsHelper
def administration_settings_tabs
tabs = [{:name => 'general', :partial => 'settings/general', :label => :label_general},
+ {:name => 'display', :partial => 'settings/display', :label => :label_display},
{:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication},
{:name => 'projects', :partial => 'settings/projects', :label => :label_project_plural},
{:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking},
diff --git a/app/helpers/sort_helper.rb b/app/helpers/sort_helper.rb
index 9ca5c11bd..f5e9bb494 100644
--- a/app/helpers/sort_helper.rb
+++ b/app/helpers/sort_helper.rb
@@ -67,23 +67,31 @@ module SortHelper
# Updates the sort state. Call this in the controller prior to calling
# sort_clause.
- #
- def sort_update()
- if params[:sort_key]
- sort = {:key => params[:sort_key], :order => params[:sort_order]}
+ # sort_keys can be either an array or a hash of allowed keys
+ def sort_update(sort_keys)
+ sort_key = params[:sort_key]
+ sort_key = nil unless (sort_keys.is_a?(Array) ? sort_keys.include?(sort_key) : sort_keys[sort_key])
+
+ sort_order = (params[:sort_order] == 'desc' ? 'DESC' : 'ASC')
+
+ if sort_key
+ sort = {:key => sort_key, :order => sort_order}
elsif session[@sort_name]
sort = session[@sort_name] # Previous sort.
else
sort = @sort_default
end
session[@sort_name] = sort
+
+ sort_column = (sort_keys.is_a?(Hash) ? sort_keys[sort[:key]] : sort[:key])
+ @sort_clause = (sort_column.blank? ? nil : [sort_column].flatten.collect {|s| "#{s} #{sort[:order]}"}.join(','))
end
# Returns an SQL sort clause corresponding to the current sort state.
# Use this to sort the controller's table items collection.
#
def sort_clause()
- session[@sort_name][:key] + ' ' + (session[@sort_name][:order] || 'ASC')
+ @sort_clause
end
# Returns a link which sorts by the named column.
@@ -112,8 +120,11 @@ module SortHelper
# don't reuse params if filters are present
url_options = params.has_key?(:set_filter) ? sort_options : params.merge(sort_options)
+ # Add project_id to url_options
+ url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id)
+
link_to_remote(caption,
- {:update => "content", :url => url_options},
+ {:update => "content", :url => url_options, :method => :get},
{:href => url_for(url_options)}) +
(icon ? nbsp(2) + image_tag(icon) : '')
end
diff --git a/app/helpers/timelog_helper.rb b/app/helpers/timelog_helper.rb
index f55a8ffe7..f068b4db8 100644
--- a/app/helpers/timelog_helper.rb
+++ b/app/helpers/timelog_helper.rb
@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module TimelogHelper
+ include ApplicationHelper
+
def render_timelog_breadcrumb
links = []
links << link_to(l(:label_project_all), {:project_id => nil, :issue_id => nil})
@@ -81,7 +83,7 @@ module TimelogHelper
csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
# csv lines
entries.each do |entry|
- fields = [l_date(entry.spent_on),
+ fields = [format_date(entry.spent_on),
entry.user,
entry.activity,
entry.project,
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 5b113e880..b9e990d6e 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -25,15 +25,10 @@ module UsersHelper
end
# Options for the new membership projects combo-box
- def projects_options_for_select(projects)
+ def options_for_membership_project_select(user, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
- projects_by_root = projects.group_by(&:root)
- projects_by_root.keys.sort.each do |root|
- options << content_tag('option', h(root.name), :value => root.id, :disabled => (!projects.include?(root)))
- projects_by_root[root].sort.each do |project|
- next if project == root
- options << content_tag('option', '&#187; ' + h(project.name), :value => project.id)
- end
+ options << project_tree_options_for_select(projects) do |p|
+ {:disabled => (user.projects.include?(p))}
end
options
end
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index 0a6b810de..c692c748b 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -16,22 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module WikiHelper
-
- def render_page_hierarchy(pages, node=nil)
- content = ''
- if pages[node]
- content << "<ul class=\"pages-hierarchy\">\n"
- pages[node].each do |page|
- content << "<li>"
- content << link_to(h(page.pretty_title), {:action => 'index', :page => page.title},
- :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
- content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
- content << "</li>\n"
- end
- content << "</ul>\n"
- end
- content
- end
def html_diff(wdiff)
words = wdiff.words.collect{|word| h(word)}
diff --git a/app/apis/sys_api.rb b/app/helpers/workflows_helper.rb
index fcee616b5..48ded305f 100644
--- a/app/apis/sys_api.rb
+++ b/app/helpers/workflows_helper.rb
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# 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
@@ -15,19 +15,5 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-class AWSProjectWithRepository < ActionWebService::Struct
- member :id, :int
- member :identifier, :string
- member :name, :string
- member :is_public, :bool
- member :repository, Repository
-end
-
-class SysApi < ActionWebService::API::Base
- api_method :projects_with_repository_enabled,
- :expects => [],
- :returns => [[AWSProjectWithRepository]]
- api_method :repository_created,
- :expects => [:string, :string, :string],
- :returns => [:int]
+module WorkflowsHelper
end
diff --git a/app/models/attachment.rb b/app/models/attachment.rb
index 95ba8491f..2ba75a3fd 100644
--- a/app/models/attachment.rb
+++ b/app/models/attachment.rb
@@ -30,12 +30,14 @@ class Attachment < ActiveRecord::Base
acts_as_activity_provider :type => 'files',
:permission => :view_files,
+ :author_key => :author_id,
: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,
+ :author_key => :author_id,
: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"}
@@ -70,7 +72,7 @@ class Attachment < ActiveRecord::Base
File.open(diskfile, "wb") do |f|
f.write(@temp_file.read)
end
- self.digest = Digest::MD5.hexdigest(File.read(diskfile))
+ self.digest = self.class.digest(diskfile)
end
# Don't save the content type if it's longer than the authorized length
if self.content_type && self.content_type.length > 255
@@ -96,6 +98,14 @@ class Attachment < ActiveRecord::Base
container.project
end
+ def visible?(user=User.current)
+ container.attachments_visible?(user)
+ end
+
+ def deletable?(user=User.current)
+ container.attachments_deletable?(user)
+ end
+
def image?
self.filename =~ /\.(jpe?g|gif|png)$/i
end
@@ -131,4 +141,11 @@ private
end
df
end
+
+ # Returns the MD5 digest of the file at given path
+ def self.digest(filename)
+ File.open(filename, 'rb') do |f|
+ Digest::MD5.hexdigest(f.read)
+ end
+ end
end
diff --git a/app/models/auth_source.rb b/app/models/auth_source.rb
index a0a2cdc5f..537ed2d43 100644
--- a/app/models/auth_source.rb
+++ b/app/models/auth_source.rb
@@ -38,7 +38,8 @@ class AuthSource < ActiveRecord::Base
begin
logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
attrs = source.authenticate(login, password)
- rescue
+ rescue => e
+ logger.error "Error during authentication: #{e.message}"
attrs = nil
end
return attrs if attrs
diff --git a/app/models/auth_source_ldap.rb b/app/models/auth_source_ldap.rb
index 6203a7c85..a619b2f85 100644
--- a/app/models/auth_source_ldap.rb
+++ b/app/models/auth_source_ldap.rb
@@ -91,6 +91,8 @@ class AuthSourceLdap < AuthSource
end
def self.get_attr(entry, attr_name)
- entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
+ if !attr_name.blank?
+ entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
+ end
end
end
diff --git a/app/models/changeset.rb b/app/models/changeset.rb
index c4258c88b..0824a980e 100644
--- a/app/models/changeset.rb
+++ b/app/models/changeset.rb
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# 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
@@ -19,13 +19,13 @@ require 'iconv'
class Changeset < ActiveRecord::Base
belongs_to :repository
+ belongs_to :user
has_many :changes, :dependent => :delete_all
has_and_belongs_to_many :issues
- acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.comments.blank? ? '' : (': ' + o.comments))},
- :description => :comments,
+ acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
+ :description => :long_comments,
:datetime => :committed_on,
- :author => :committer,
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
acts_as_searchable :columns => 'comments',
@@ -34,6 +34,7 @@ class Changeset < ActiveRecord::Base
:date_column => 'committed_on'
acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
+ :author_key => :user_id,
:find_options => {:include => {:repository => :project}}
validates_presence_of :repository_id, :revision, :committed_on, :commit_date
@@ -57,6 +58,14 @@ class Changeset < ActiveRecord::Base
repository.project
end
+ def author
+ user || committer.to_s.split('<').first
+ end
+
+ def before_create
+ self.user = repository.find_committer_user(committer)
+ end
+
def after_create
scan_comment_for_issue_ids
end
@@ -96,12 +105,11 @@ class Changeset < ActiveRecord::Base
issue.reload
# don't change the status is the issue is closed
next if issue.status.is_closed?
- user = committer_user || User.anonymous
csettext = "r#{self.revision}"
if self.scmid && (! (csettext =~ /^r[0-9]+$/))
csettext = "commit:\"#{self.scmid}\""
end
- journal = issue.init_journal(user, l(:text_status_changed_by_changeset, csettext))
+ journal = issue.init_journal(user || User.anonymous, l(:text_status_changed_by_changeset, csettext))
issue.status = fix_status
issue.done_ratio = done_ratio if done_ratio
issue.save
@@ -113,15 +121,13 @@ class Changeset < ActiveRecord::Base
self.issues = referenced_issues.uniq
end
-
- # Returns the Redmine User corresponding to the committer
- def committer_user
- if committer && committer.strip =~ /^([^<]+)(<(.*)>)?$/
- username, email = $1.strip, $3
- u = User.find_by_login(username)
- u ||= User.find_by_mail(email) unless email.blank?
- u
- end
+
+ def short_comments
+ @short_comments || split_comments.first
+ end
+
+ def long_comments
+ @long_comments || split_comments.last
end
# Returns the previous changeset
@@ -140,7 +146,14 @@ class Changeset < ActiveRecord::Base
end
private
-
+
+ def split_comments
+ comments =~ /\A(.+?)\r?\n(.*)$/m
+ @short_comments = $1 || comments
+ @long_comments = $2.to_s.strip
+ return @short_comments, @long_comments
+ end
+
def self.to_utf8(str)
return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
encoding = Setting.commit_logs_encoding.to_s.strip
diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb
index 4759b714b..f277dc349 100644
--- a/app/models/custom_field.rb
+++ b/app/models/custom_field.rb
@@ -41,8 +41,6 @@ class CustomField < ActiveRecord::Base
end
def before_validation
- # remove empty values
- self.possible_values = self.possible_values.collect{|v| v unless v.empty?}.compact
# make sure these fields are not searchable
self.searchable = false if %w(int float date bool).include?(field_format)
true
@@ -59,11 +57,49 @@ class CustomField < ActiveRecord::Base
v.custom_field.is_required = false
errors.add(:default_value, :activerecord_error_invalid) unless v.valid?
end
+
+ # Makes possible_values accept a multiline string
+ def possible_values=(arg)
+ if arg.is_a?(Array)
+ write_attribute(:possible_values, arg.compact.collect(&:strip).select {|v| !v.blank?})
+ else
+ self.possible_values = arg.to_s.split(/[\n\r]+/)
+ end
+ end
+
+ # Returns a ORDER BY clause that can used to sort customized
+ # objects by their value of the custom field.
+ # Returns false, if the custom field can not be used for sorting.
+ def order_statement
+ case field_format
+ when 'string', 'text', 'list', 'date', 'bool'
+ # COALESCE is here to make sure that blank and NULL values are sorted equally
+ "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
+ " WHERE cv_sort.customized_type='#{self.class.customized_class.name}'" +
+ " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
+ " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
+ when 'int', 'float'
+ # Make the database cast values into numeric
+ # Postgresql will raise an error if a value can not be casted!
+ # CustomValue validations should ensure that it doesn't occur
+ "(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" +
+ " WHERE cv_sort.customized_type='#{self.class.customized_class.name}'" +
+ " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
+ " AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)"
+ else
+ nil
+ end
+ end
def <=>(field)
position <=> field.position
end
+ def self.customized_class
+ self.name =~ /^(.+)CustomField$/
+ begin; $1.constantize; rescue nil; end
+ end
+
# to move in project_custom_field
def self.for_all
find(:all, :conditions => ["is_for_all=?", true], :order => 'position')
diff --git a/app/models/custom_value.rb b/app/models/custom_value.rb
index 1d453baf0..1f662baa7 100644
--- a/app/models/custom_value.rb
+++ b/app/models/custom_value.rb
@@ -30,6 +30,18 @@ class CustomValue < ActiveRecord::Base
self.value == '1'
end
+ def editable?
+ custom_field.editable?
+ end
+
+ def required?
+ custom_field.is_required?
+ end
+
+ def to_s
+ value.to_s
+ end
+
protected
def validate
if value.blank?
diff --git a/app/models/document.rb b/app/models/document.rb
index 627a2418f..2ec99fe07 100644
--- a/app/models/document.rb
+++ b/app/models/document.rb
@@ -18,7 +18,7 @@
class Document < ActiveRecord::Base
belongs_to :project
belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
- has_many :attachments, :as => :container, :dependent => :destroy
+ acts_as_attachable :delete_permission => :manage_documents
acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"},
@@ -28,4 +28,10 @@ class Document < ActiveRecord::Base
validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60
+
+ def after_initialize
+ if new_record?
+ self.category ||= Enumeration.default('DCAT')
+ end
+ end
end
diff --git a/app/models/enumeration.rb b/app/models/enumeration.rb
index d32a0c049..e4b080be1 100644
--- a/app/models/enumeration.rb
+++ b/app/models/enumeration.rb
@@ -44,7 +44,9 @@ class Enumeration < ActiveRecord::Base
end
def before_save
- Enumeration.update_all("is_default = #{connection.quoted_false}", {:opt => opt}) if is_default?
+ if is_default? && is_default_changed?
+ Enumeration.update_all("is_default = #{connection.quoted_false}", {:opt => opt})
+ end
end
def objects_count
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 4701e41f1..618c5597d 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -26,13 +26,13 @@ class Issue < ActiveRecord::Base
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
has_many :journals, :as => :journalized, :dependent => :destroy
- has_many :attachments, :as => :container, :dependent => :destroy
has_many :time_entries, :dependent => :delete_all
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_attachable :after_remove => :attachment_removed
acts_as_customizable
acts_as_watchable
acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
@@ -40,15 +40,25 @@ class Issue < ActiveRecord::Base
# 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}}
+ :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
+ :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
- acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]}
+ acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
+ :author_key => :author_id
- validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status
+ validates_presence_of :subject, :priority, :project, :tracker, :author, :status
validates_length_of :subject, :maximum => 255
validates_inclusion_of :done_ratio, :in => 0..100
validates_numericality_of :estimated_hours, :allow_nil => true
+ named_scope :visible, lambda {|*args| { :include => :project,
+ :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
+
+ # Returns true if usr or current user is allowed to view the issue
+ def visible?(usr=nil)
+ (usr || User.current).allowed_to?(:view_issues, self.project)
+ end
+
def after_initialize
if new_record?
# set default values for new records only
@@ -69,34 +79,43 @@ class Issue < ActiveRecord::Base
self
end
- # Move an issue to a new project and tracker
- def move_to(new_project, new_tracker = nil)
+ # Moves/copies an issue to a new project and tracker
+ # Returns the moved/copied issue on success, false on failure
+ def move_to(new_project, new_tracker = nil, options = {})
+ options ||= {}
+ issue = options[:copy] ? self.clone : self
transaction do
- if new_project && project_id != new_project.id
+ if new_project && issue.project_id != new_project.id
# delete issue relations
unless Setting.cross_project_issue_relations?
- self.relations_from.clear
- self.relations_to.clear
+ issue.relations_from.clear
+ issue.relations_to.clear
end
# issue is moved to another project
# reassign to the category with same name if any
- new_category = category.nil? ? nil : new_project.issue_categories.find_by_name(category.name)
- self.category = new_category
- self.fixed_version = nil
- self.project = new_project
+ new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
+ issue.category = new_category
+ issue.fixed_version = nil
+ issue.project = new_project
end
if new_tracker
- self.tracker = new_tracker
+ issue.tracker = new_tracker
+ end
+ if options[:copy]
+ issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
+ issue.status = self.status
end
- if save
- # Manually update project_id on related time entries
- TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
+ if issue.save
+ unless options[:copy]
+ # Manually update project_id on related time entries
+ TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
+ end
else
- rollback_db_transaction
+ Issue.connection.rollback_db_transaction
return false
end
end
- return true
+ return issue
end
def priority_id=(pid)
@@ -194,6 +213,11 @@ class Issue < ActiveRecord::Base
self.status.is_closed?
end
+ # Returns true if the issue is overdue
+ def overdue?
+ !due_date.nil? && (due_date < Date.today) && !status.is_closed?
+ end
+
# Users the issue can be assigned to
def assignable_users
project.assignable_users
@@ -251,13 +275,18 @@ class Issue < ActiveRecord::Base
@soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
end
- def self.visible_by(usr)
- with_scope(:find => { :conditions => Project.visible_by(usr) }) do
- yield
- end
- end
-
def to_s
"#{tracker} ##{id}: #{subject}"
end
+
+ private
+
+ # Callback on attachment deletion
+ def attachment_removed(obj)
+ journal = init_journal(User.current)
+ journal.details << JournalDetail.new(:property => 'attachment',
+ :prop_key => obj.id,
+ :old_value => obj.filename)
+ journal.save
+ end
end
diff --git a/app/models/issue_relation.rb b/app/models/issue_relation.rb
index 49329e0bb..13e14cccc 100644
--- a/app/models/issue_relation.rb
+++ b/app/models/issue_relation.rb
@@ -35,6 +35,8 @@ class IssueRelation < ActiveRecord::Base
validates_numericality_of :delay, :allow_nil => true
validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
+ attr_protected :issue_from_id, :issue_to_id
+
def validate
if issue_from && issue_to
errors.add :issue_to_id, :activerecord_error_invalid if issue_from_id == issue_to_id
diff --git a/app/models/issue_status.rb b/app/models/issue_status.rb
index ddff9c005..16c7bce91 100644
--- a/app/models/issue_status.rb
+++ b/app/models/issue_status.rb
@@ -25,8 +25,8 @@ class IssueStatus < ActiveRecord::Base
validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
- def before_save
- IssueStatus.update_all "is_default=#{connection.quoted_false}" if self.is_default?
+ def after_save
+ IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default?
end
# Returns the default status for new issues
diff --git a/app/models/journal.rb b/app/models/journal.rb
index 71a51290b..72e7eb91c 100644
--- a/app/models/journal.rb
+++ b/app/models/journal.rb
@@ -33,6 +33,7 @@ class Journal < ActiveRecord::Base
acts_as_activity_provider :type => 'issues',
:permission => :view_issues,
+ :author_key => :user_id,
: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 <> '')"}
diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb
index 2f1eba3e9..8e19bcdf4 100644
--- a/app/models/mail_handler.rb
+++ b/app/models/mail_handler.rb
@@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class MailHandler < ActionMailer::Base
+ include ActionView::Helpers::SanitizeHelper
class UnauthorizedAction < StandardError; end
class MissingInformation < StandardError; end
@@ -31,7 +32,7 @@ class MailHandler < ActionMailer::Base
@@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
+ # Status overridable by default
@@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
super email
end
@@ -39,7 +40,7 @@ class MailHandler < ActionMailer::Base
# Processes incoming emails
def receive(email)
@email = email
- @user = User.find_active(:first, :conditions => {:mail => email.from.first})
+ @user = User.active.find_by_mail(email.from.first.to_s.strip)
unless @user
# Unknown user => the email is ignored
# TODO: ability to create the user's account
@@ -52,11 +53,24 @@ class MailHandler < ActionMailer::Base
private
+ MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
+ MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]+msg(\d+)\]}
def dispatch
- if m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
- receive_issue_update(m[1].to_i)
+ headers = [email.in_reply_to, email.references].flatten.compact
+ if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
+ klass, object_id = $1, $2.to_i
+ method_name = "receive_#{klass}_reply"
+ if self.class.private_instance_methods.include?(method_name)
+ send method_name, object_id
+ else
+ # ignoring it
+ end
+ elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
+ receive_issue_reply(m[1].to_i)
+ elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
+ receive_message_reply(m[1].to_i)
else
receive_issue
end
@@ -78,16 +92,30 @@ class MailHandler < ActionMailer::Base
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
+ status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
# check permission
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 = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
+ # check workflow
+ if status && issue.new_statuses_allowed_to(user).include?(status)
+ issue.status = status
+ end
+ issue.subject = email.subject.chomp.toutf8
+ issue.description = plain_text_body
+ # custom fields
+ issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
+ if value = get_keyword(c.name, :override => true)
+ h[c.id] = value
+ end
+ h
+ end
issue.save!
add_attachments(issue)
logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
+ # add To and Cc as watchers
+ add_watchers(issue)
+ # send notification after adding watchers so that they can reply to Redmine
Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
issue
end
@@ -102,7 +130,7 @@ class MailHandler < ActionMailer::Base
end
# Adds a note to an existing issue
- def receive_issue_update(issue_id)
+ def receive_issue_reply(issue_id)
status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
issue = Issue.find_by_id(issue_id)
@@ -112,15 +140,45 @@ class MailHandler < ActionMailer::Base
raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
# add the note
- journal = issue.init_journal(user, email.plain_text_body.chomp)
+ journal = issue.init_journal(user, plain_text_body)
add_attachments(issue)
- issue.status = status unless status.nil?
+ # check workflow
+ if status && issue.new_statuses_allowed_to(user).include?(status)
+ issue.status = status
+ end
issue.save!
logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
journal
end
+ # Reply will be added to the issue
+ def receive_journal_reply(journal_id)
+ journal = Journal.find_by_id(journal_id)
+ if journal && journal.journalized_type == 'Issue'
+ receive_issue_reply(journal.journalized_id)
+ end
+ end
+
+ # Receives a reply to a forum message
+ def receive_message_reply(message_id)
+ message = Message.find_by_id(message_id)
+ if message
+ message = message.root
+ if user.allowed_to?(:add_messages, message.project) && !message.locked?
+ reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
+ :content => plain_text_body)
+ reply.author = user
+ reply.board = message.board
+ message.children << reply
+ add_attachments(reply)
+ reply
+ else
+ raise UnauthorizedAction
+ end
+ end
+ end
+
def add_attachments(obj)
if email.has_attachments?
email.attachments.each do |attachment|
@@ -132,22 +190,50 @@ class MailHandler < ActionMailer::Base
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]
+ # Adds To and Cc as watchers of the given object if the sender has the
+ # appropriate permission
+ def add_watchers(obj)
+ if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
+ addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
+ unless addresses.empty?
+ watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
+ watchers.each {|w| obj.add_watcher(w)}
+ end
end
end
-end
-
-class TMail::Mail
- # Returns body of the first plain text part found if any
+
+ def get_keyword(attr, options={})
+ @keywords ||= {}
+ if @keywords.has_key?(attr)
+ @keywords[attr]
+ else
+ @keywords[attr] = begin
+ if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}:[ \t]*(.+)\s*$/i, '')
+ $1.strip
+ elsif !@@handler_options[:issue][attr].blank?
+ @@handler_options[:issue][attr]
+ end
+ end
+ end
+ end
+
+ # Returns the text/plain part of the email
+ # If not found (eg. HTML-only email), returns the body with tags removed
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
+ parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
+ if parts.empty?
+ parts << @email
+ end
+ plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
+ if plain_text_part.nil?
+ # no text/plain part found, assuming html-only email
+ # strip html tags and remove doctype directive
+ @plain_text_body = strip_tags(@email.body.to_s)
+ @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
+ else
+ @plain_text_body = plain_text_part.body.to_s
+ end
+ @plain_text_body.strip!
end
end
-
diff --git a/app/models/mailer.rb b/app/models/mailer.rb
index a2d9ddba5..c0ff7a8c8 100644
--- a/app/models/mailer.rb
+++ b/app/models/mailer.rb
@@ -5,12 +5,12 @@
# 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.
@@ -19,15 +19,17 @@ class Mailer < ActionMailer::Base
helper :application
helper :issues
helper :custom_fields
-
+
include ActionController::UrlWriter
-
- def issue_add(issue)
+
+ def issue_add(issue)
redmine_headers 'Project' => issue.project.identifier,
'Issue-Id' => issue.id,
'Issue-Author' => issue.author.login
redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
- recipients issue.recipients
+ message_id issue
+ recipients issue.recipients
+ cc(issue.watcher_recipients - @recipients)
subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
body :issue => issue,
:issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
@@ -39,6 +41,9 @@ class Mailer < ActionMailer::Base
'Issue-Id' => issue.id,
'Issue-Author' => issue.author.login
redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
+ message_id journal
+ references issue
+ @author = journal.user
recipients issue.recipients
# Watchers in cc
cc(issue.watcher_recipients - @recipients)
@@ -50,16 +55,16 @@ class Mailer < ActionMailer::Base
:journal => journal,
: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')
+ :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
end
-
+
def document_added(document)
redmine_headers 'Project' => document.project.identifier
recipients document.project.recipients
@@ -67,12 +72,15 @@ class Mailer < ActionMailer::Base
body :document => document,
:document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
end
-
+
def attachments_added(attachments)
container = attachments.first.container
added_to = ''
added_to_url = ''
case container.class.name
+ when 'Project'
+ added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
+ added_to = "#{l(:label_project)}: #{container}"
when 'Version'
added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
added_to = "#{l(:label_version)}: #{container.name}"
@@ -90,6 +98,7 @@ class Mailer < ActionMailer::Base
def news_added(news)
redmine_headers 'Project' => news.project.identifier
+ message_id news
recipients news.project.recipients
subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
body :news => news,
@@ -99,12 +108,14 @@ class Mailer < ActionMailer::Base
def message_posted(message, recipients)
redmine_headers 'Project' => message.project.identifier,
'Topic-Id' => (message.parent_id || message.id)
+ message_id message
+ references message.parent unless message.parent.nil?
recipients(recipients)
- subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}"
+ subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
body :message => message,
:message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
end
-
+
def account_information(user, password)
set_language_if_valid user.language
recipients user.mail
@@ -113,10 +124,10 @@ class Mailer < ActionMailer::Base
:password => password,
:login_url => url_for(:controller => 'account', :action => 'login')
end
-
+
def account_activation_request(user)
# Send the email to all active administrators
- recipients User.find_active(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
+ recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
subject l(:mail_subject_account_activation_request, Setting.app_title)
body :user => user,
:url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
@@ -128,7 +139,7 @@ class Mailer < ActionMailer::Base
subject l(:mail_subject_lost_password, Setting.app_title)
body :token => token,
:url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
- end
+ end
def register(token)
set_language_if_valid(token.user.language)
@@ -137,7 +148,7 @@ class Mailer < ActionMailer::Base
body :token => token,
:url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
end
-
+
def test(user)
set_language_if_valid(user.language)
recipients user.mail
@@ -148,12 +159,20 @@ class Mailer < ActionMailer::Base
# Overrides default deliver! method to prevent from sending an email
# with no recipient, cc or bcc
def deliver!(mail = @mail)
- return false if (recipients.nil? || recipients.empty?) &&
+ return false if (recipients.nil? || recipients.empty?) &&
(cc.nil? || cc.empty?) &&
(bcc.nil? || bcc.empty?)
- super
+
+ # Set Message-Id and References
+ if @message_id_object
+ mail.message_id = self.class.message_id_for(@message_id_object)
+ end
+ if @references_objects
+ mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
+ end
+ super(mail)
end
-
+
# Sends reminders to issue assignees
# Available options:
# * :days => how many days in the future to remind about (defaults to 7)
@@ -163,13 +182,13 @@ class Mailer < ActionMailer::Base
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)
@@ -183,45 +202,96 @@ class Mailer < ActionMailer::Base
super
set_language_if_valid Setting.default_language
from Setting.mail_from
- default_url_options[:host] = Setting.host_name
+
+ # URL options
+ h = Setting.host_name
+ h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
+ default_url_options[:host] = h
default_url_options[:protocol] = Setting.protocol
+
# Common headers
headers 'X-Mailer' => 'Redmine',
'X-Redmine-Host' => Setting.host_name,
'X-Redmine-Site' => Setting.app_title
end
-
+
# Appends a Redmine header field (name is prepended with 'X-Redmine-')
def redmine_headers(h)
h.each { |k,v| headers["X-Redmine-#{k}"] = v }
end
-
+
# Overrides the create_mail method
def create_mail
# Removes the current user from the recipients and cc
# if he doesn't want to receive notifications about what he does
- if User.current.pref[:no_self_notified]
- recipients.delete(User.current.mail) if recipients
- cc.delete(User.current.mail) if cc
+ @author ||= User.current
+ if @author.pref[:no_self_notified]
+ recipients.delete(@author.mail) if recipients
+ cc.delete(@author.mail) if cc
end
# Blind carbon copy recipients
if Setting.bcc_recipients?
bcc([recipients, cc].flatten.compact.uniq)
recipients []
cc []
- end
+ end
super
end
-
+
# Renders a message with the corresponding layout
def render_message(method_name, body)
- layout = method_name.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
+ layout = method_name.to_s.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
body[:content_for_layout] = render(:file => method_name, :body => body)
ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true)
end
-
+
+ # for the case of plain text only
+ def body(*params)
+ value = super(*params)
+ if Setting.plain_text_mail?
+ templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}")
+ unless String === @body or templates.empty?
+ template = File.basename(templates.first)
+ @body[:content_for_layout] = render(:file => template, :body => @body)
+ @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true)
+ return @body
+ end
+ end
+ return value
+ end
+
# Makes partial rendering work with Rails 1.2 (retro-compatibility)
def self.controller_path
''
end unless respond_to?('controller_path')
+
+ # Returns a predictable Message-Id for the given object
+ def self.message_id_for(object)
+ # id + timestamp should reduce the odds of a collision
+ # as far as we don't send multiple emails for the same object
+ hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{object.created_on.strftime("%Y%m%d%H%M%S")}"
+ host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
+ host = "#{::Socket.gethostname}.redmine" if host.empty?
+ "<#{hash}@#{host}>"
+ end
+
+ private
+
+ def message_id(object)
+ @message_id_object = object
+ end
+
+ def references(object)
+ @references_objects ||= []
+ @references_objects << object
+ end
+end
+
+# Patch TMail so that message_id is not overwritten
+module TMail
+ class Mail
+ def add_message_id( fqdn = nil )
+ self.message_id ||= ::TMail::new_message_id(fqdn)
+ end
+ end
end
diff --git a/app/models/message.rb b/app/models/message.rb
index f1cb2d0ba..716c53b0b 100644
--- a/app/models/message.rb
+++ b/app/models/message.rb
@@ -19,11 +19,11 @@ class Message < ActiveRecord::Base
belongs_to :board
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC"
- has_many :attachments, :as => :container, :dependent => :destroy
+ acts_as_attachable
belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id'
acts_as_searchable :columns => ['subject', 'content'],
- :include => {:board, :project},
+ :include => {:board => :project},
:project_key => 'project_id',
:date_column => "#{table_name}.created_on"
acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
@@ -32,7 +32,8 @@ class Message < ActiveRecord::Base
: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]}
+ acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]},
+ :author_key => :author_id
acts_as_watchable
attr_protected :locked, :sticky
@@ -71,6 +72,14 @@ class Message < ActiveRecord::Base
def project
board.project
end
+
+ def editable_by?(usr)
+ usr && usr.logged? && (usr.allowed_to?(:edit_messages, project) || (self.author == usr && usr.allowed_to?(:edit_own_messages, project)))
+ end
+
+ def destroyable_by?(usr)
+ usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project)))
+ end
private
diff --git a/app/models/news.rb b/app/models/news.rb
index 4c4943b78..5949a731b 100644
--- a/app/models/news.rb
+++ b/app/models/news.rb
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006 Jean-Philippe Lang
+# 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
@@ -26,10 +26,11 @@ class News < ActiveRecord::Base
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]}
+ acts_as_activity_provider :find_options => {:include => [:project, :author]},
+ :author_key => :author_id
# returns latest news for projects visible by user
- def self.latest(user=nil, count=5)
- find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
+ def self.latest(user = User.current, count = 5)
+ find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index e40af9967..7ce0051ed 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -43,7 +43,9 @@ class Project < ActiveRecord::Base
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
:association_foreign_key => 'custom_field_id'
- acts_as_tree :order => "name", :counter_cache => true
+ acts_as_nested_set :order => 'name', :dependent => :destroy
+ acts_as_attachable :view_permission => :view_files,
+ :delete_permission => :manage_files
acts_as_customizable
acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
@@ -58,12 +60,15 @@ class Project < ActiveRecord::Base
validates_associated :repository, :wiki
validates_length_of :name, :maximum => 30
validates_length_of :homepage, :maximum => 255
- validates_length_of :identifier, :in => 3..20
+ validates_length_of :identifier, :in => 2..20
validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
before_destroy :delete_all_members
named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
+ named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
+ named_scope :public, { :conditions => { :is_public => true } }
+ named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
def identifier=(identifier)
super unless identifier_frozen?
@@ -76,7 +81,7 @@ class Project < ActiveRecord::Base
def issues_with_subprojects(include_subprojects=false)
conditions = nil
if include_subprojects
- ids = [id] + child_ids
+ ids = [id] + descendants.collect(&:id)
conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
end
conditions ||= ["#{Project.table_name}.id = ?", id]
@@ -108,9 +113,15 @@ class Project < ActiveRecord::Base
def self.allowed_to_condition(user, permission, options={})
statements = []
base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
+ if perm = Redmine::AccessControl.permission(permission)
+ unless perm.project_module.nil?
+ # If the permission belongs to a project module, make sure the module is enabled
+ base_statement << " AND EXISTS (SELECT em.id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}' AND em.project_id=#{Project.table_name}.id)"
+ end
+ end
if options[:project]
project_statement = "#{Project.table_name}.id = #{options[:project].id}"
- project_statement << " OR #{Project.table_name}.parent_id = #{options[:project].id}" if options[:with_subprojects]
+ project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
base_statement = "(#{project_statement}) AND (#{base_statement})"
end
if user.admin?
@@ -133,7 +144,7 @@ class Project < ActiveRecord::Base
def project_condition(with_subprojects)
cond = "#{Project.table_name}.id = #{id}"
- cond = "(#{cond} OR #{Project.table_name}.parent_id = #{id})" if with_subprojects
+ cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
cond
end
@@ -156,6 +167,7 @@ class Project < ActiveRecord::Base
self.status == STATUS_ACTIVE
end
+ # Archives the project and its descendants recursively
def archive
# Archive subprojects if any
children.each do |subproject|
@@ -164,21 +176,62 @@ class Project < ActiveRecord::Base
update_attribute :status, STATUS_ARCHIVED
end
+ # Unarchives the project
+ # All its ancestors must be active
def unarchive
- return false if parent && !parent.active?
+ return false if ancestors.detect {|a| !a.active?}
update_attribute :status, STATUS_ACTIVE
end
- def active_children
- children.select {|child| child.active?}
+ # Returns an array of projects the project can be moved to
+ def possible_parents
+ @possible_parents ||= (Project.active.find(:all) - self_and_descendants)
+ end
+
+ # Sets the parent of the project
+ # Argument can be either a Project, a String, a Fixnum or nil
+ def set_parent!(p)
+ unless p.nil? || p.is_a?(Project)
+ if p.to_s.blank?
+ p = nil
+ else
+ p = Project.find_by_id(p)
+ return false unless p
+ end
+ end
+ if p == parent && !p.nil?
+ # Nothing to do
+ true
+ elsif p.nil? || (p.active? && move_possible?(p))
+ # Insert the project so that target's children or root projects stay alphabetically sorted
+ sibs = (p.nil? ? self.class.roots : p.children)
+ to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
+ if to_be_inserted_before
+ move_to_left_of(to_be_inserted_before)
+ elsif p.nil?
+ if sibs.empty?
+ # move_to_root adds the project in first (ie. left) position
+ move_to_root
+ else
+ move_to_right_of(sibs.last) unless self == sibs.last
+ end
+ else
+ # move_to_child_of adds the project in last (ie.right) position
+ move_to_child_of(p)
+ end
+ true
+ else
+ # Can not move to the given target
+ false
+ end
end
- # Returns an array of the trackers used by the project and its sub projects
+ # Returns an array of the trackers used by the project and its active sub projects
def rolled_up_trackers
@rolled_up_trackers ||=
Tracker.find(:all, :include => :projects,
:select => "DISTINCT #{Tracker.table_name}.*",
- :conditions => ["#{Project.table_name}.id = ? OR #{Project.table_name}.parent_id = ?", id, id],
+ :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
:order => "#{Tracker.table_name}.position")
end
@@ -217,7 +270,7 @@ class Project < ActiveRecord::Base
# Returns a short description of the projects (first lines)
def short_description(length = 255)
- description.gsub(/^(.{#{length}}[^\n]*).*$/m, '\1').strip if description
+ description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
end
def allows_to?(action)
@@ -249,8 +302,6 @@ class Project < ActiveRecord::Base
protected
def validate
- errors.add(parent_id, " must be a root project") if parent and parent.parent
- errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
errors.add(:identifier, :activerecord_error_invalid) if !identifier.blank? && identifier.match(/^\d*$/)
end
diff --git a/app/models/query.rb b/app/models/query.rb
index f8c236145..23742cfa5 100644
--- a/app/models/query.rb
+++ b/app/models/query.rb
@@ -35,7 +35,7 @@ class QueryCustomFieldColumn < QueryColumn
def initialize(custom_field)
self.name = "cf_#{custom_field.id}".to_sym
- self.sortable = false
+ self.sortable = custom_field.order_statement || false
@cf = custom_field
end
@@ -98,10 +98,10 @@ class Query < ActiveRecord::Base
QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc'),
QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
QueryColumn.new(:author),
- QueryColumn.new(:assigned_to, :sortable => "#{User.table_name}.lastname"),
+ QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname"]),
QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
- QueryColumn.new(:fixed_version, :sortable => "#{Version.table_name}.effective_date", :default_order => 'desc'),
+ QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc'),
QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
@@ -165,6 +165,10 @@ class Query < ActiveRecord::Base
end
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
@available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
+
+ if User.current.logged?
+ @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
+ end
if project
# project specific filters
@@ -174,8 +178,8 @@ class Query < ActiveRecord::Base
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] } }
+ unless @project.descendants.active.empty?
+ @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
end
add_custom_fields_filters(@project.all_issue_custom_fields)
else
@@ -257,7 +261,7 @@ class Query < ActiveRecord::Base
def project_statement
project_clauses = []
- if project && !@project.active_children.empty?
+ if project && !@project.descendants.active.empty?
ids = [project.id]
if has_filter?("subproject_id")
case operator_for("subproject_id")
@@ -268,16 +272,16 @@ class Query < ActiveRecord::Base
# main project only
else
# all subprojects
- ids += project.child_ids
+ ids += project.descendants.collect(&:id)
end
elsif Setting.display_subprojects_issues?
- ids += project.child_ids
+ ids += project.descendants.collect(&:id)
end
project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
elsif project
project_clauses << "#{Project.table_name}.id = %d" % project.id
end
- project_clauses << Project.visible_by(User.current)
+ project_clauses << Project.allowed_to_condition(User.current, :view_issues)
project_clauses.join(' AND ')
end
@@ -288,74 +292,34 @@ class Query < ActiveRecord::Base
next if field == "subproject_id"
v = values_for(field).clone
next unless v and !v.empty?
-
+ operator = operator_for(field)
+
+ # "me" value subsitution
+ if %w(assigned_to_id author_id watcher_id).include?(field)
+ v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
+ end
+
sql = ''
- is_custom_filter = false
if field =~ /^cf_(\d+)$/
# custom field
db_table = CustomValue.table_name
db_field = 'value'
is_custom_filter = true
sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
+ sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
+ elsif field == 'watcher_id'
+ db_table = Watcher.table_name
+ db_field = 'user_id'
+ sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
+ sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
else
# regular field
db_table = Issue.table_name
db_field = field
- sql << '('
- end
-
- # "me" value subsitution
- if %w(assigned_to_id author_id).include?(field)
- v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
+ sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
end
-
- case operator_for field
- when "="
- sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
- when "!"
- sql = sql + "(#{db_table}.#{db_field} 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"
- sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
- when "*"
- 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 "<="
- sql = sql + "#{db_table}.#{db_field} <= #{v.first.to_i}"
- when "o"
- sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
- when "c"
- sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
- when ">t-"
- sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)]
- when "<t-"
- sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time)
- when "t-"
- sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)]
- when ">t+"
- sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
- when "<t+"
- sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
- when "t+"
- sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
- when "t"
- sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)]
- when "w"
- from = l(:general_first_day_of_week) == '7' ?
- # week starts on sunday
- ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
- # week starts on monday (Rails default)
- Time.now.at_beginning_of_week
- sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
- when "~"
- sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
- when "!~"
- sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
- end
- sql << ')'
filters_clauses << sql
+
end if filters and valid?
(filters_clauses << project_statement).join(' AND ')
@@ -363,6 +327,58 @@ class Query < ActiveRecord::Base
private
+ # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
+ def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
+ sql = ''
+ case operator
+ when "="
+ sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
+ when "!"
+ sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
+ when "!*"
+ sql = "#{db_table}.#{db_field} IS NULL"
+ sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
+ when "*"
+ sql = "#{db_table}.#{db_field} IS NOT NULL"
+ sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
+ when ">="
+ sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
+ when "<="
+ sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
+ when "o"
+ sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
+ when "c"
+ sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
+ when ">t-"
+ sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
+ when "<t-"
+ sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
+ when "t-"
+ sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
+ when ">t+"
+ sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
+ when "<t+"
+ sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
+ when "t+"
+ sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
+ when "t"
+ sql = date_range_clause(db_table, db_field, 0, 0)
+ when "w"
+ from = l(:general_first_day_of_week) == '7' ?
+ # week starts on sunday
+ ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
+ # week starts on monday (Rails default)
+ Time.now.at_beginning_of_week
+ sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
+ when "~"
+ sql = "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(value.first)}%'"
+ when "!~"
+ sql = "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(value.first)}%'"
+ end
+
+ return sql
+ end
+
def add_custom_fields_filters(custom_fields)
@available_filters ||= {}
@@ -382,4 +398,16 @@ class Query < ActiveRecord::Base
@available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
end
end
+
+ # Returns a SQL clause for a date or datetime field.
+ def date_range_clause(table, field, from, to)
+ s = []
+ if from
+ s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
+ end
+ if to
+ s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
+ end
+ s.join(' AND ')
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index b0a7a0a16..a62388bb6 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -78,11 +78,12 @@ class Repository < ActiveRecord::Base
end
# Default behaviour: we search in cached changesets
- def changesets_for_path(path)
+ def changesets_for_path(path, options={})
path = "/#{path}" unless path.starts_with?('/')
- Change.find(:all, :include => :changeset,
- :conditions => ["repository_id = ? AND path = ?", id, path],
- :order => "committed_on DESC, #{Changeset.table_name}.id DESC").collect(&:changeset)
+ Change.find(:all, :include => {:changeset => :user},
+ :conditions => ["repository_id = ? AND path = ?", id, path],
+ :order => "committed_on DESC, #{Changeset.table_name}.id DESC",
+ :limit => options[:limit]).collect(&:changeset)
end
# Returns a path relative to the url of the repository
@@ -98,6 +99,45 @@ class Repository < ActiveRecord::Base
self.changesets.each(&:scan_comment_for_issue_ids)
end
+ # Returns an array of committers usernames and associated user_id
+ def committers
+ @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
+ end
+
+ # Maps committers username to a user ids
+ def committer_ids=(h)
+ if h.is_a?(Hash)
+ committers.each do |committer, user_id|
+ new_user_id = h[committer]
+ if new_user_id && (new_user_id.to_i != user_id.to_i)
+ new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
+ Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
+ end
+ end
+ @committers = nil
+ true
+ else
+ false
+ end
+ end
+
+ # Returns the Redmine User corresponding to the given +committer+
+ # It will return nil if the committer is not yet mapped and if no User
+ # with the same username or email was found
+ def find_committer_user(committer)
+ if committer
+ c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
+ if c && c.user
+ c.user
+ elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
+ username, email = $1.strip, $3
+ u = User.find_by_login(username)
+ u ||= User.find_by_mail(email) unless email.blank?
+ u
+ end
+ end
+ end
+
# fetch new changesets for all repositories
# can be called periodically by an external script
# eg. ruby script/runner "Repository.fetch_changesets"
diff --git a/app/models/repository/darcs.rb b/app/models/repository/darcs.rb
index 855a403fc..5c8d3871a 100644
--- a/app/models/repository/darcs.rb
+++ b/app/models/repository/darcs.rb
@@ -52,7 +52,7 @@ class Repository::Darcs < Repository
end
def cat(path, identifier=nil)
- patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
+ patch = identifier.nil? ? nil : changesets.find_by_revision(identifier.to_s)
scm.cat(path, patch.nil? ? nil : patch.scmid)
end
diff --git a/app/models/repository/git.rb b/app/models/repository/git.rb
index f6b9c2fef..0538e11cc 100644
--- a/app/models/repository/git.rb
+++ b/app/models/repository/git.rb
@@ -40,10 +40,11 @@ class Repository::Git < Repository
'Git'
end
- def changesets_for_path(path)
- Change.find(:all, :include => :changeset,
+ def changesets_for_path(path, options={})
+ Change.find(:all, :include => {:changeset => :user},
:conditions => ["repository_id = ? AND path = ?", id, path],
- :order => "committed_on DESC, #{Changeset.table_name}.revision DESC").collect(&:changeset)
+ :order => "committed_on DESC, #{Changeset.table_name}.revision DESC",
+ :limit => options[:limit]).collect(&:changeset)
end
def fetch_changesets
@@ -58,20 +59,22 @@ class Repository::Git < Repository
unless changesets.find_by_scmid(scm_revision)
scm.revisions('', db_revision, nil, :reverse => true) do |revision|
- transaction do
- changeset = Changeset.create(:repository => self,
- :revision => revision.identifier,
- :scmid => revision.scmid,
- :committer => revision.author,
- :committed_on => revision.time,
- :comments => revision.message)
-
- revision.paths.each do |change|
- Change.create(:changeset => changeset,
- :action => change[:action],
- :path => change[:path],
- :from_path => change[:from_path],
- :from_revision => change[:from_revision])
+ if changesets.find_by_scmid(revision.scmid.to_s).nil?
+ transaction do
+ changeset = Changeset.create!(:repository => self,
+ :revision => revision.identifier,
+ :scmid => revision.scmid,
+ :committer => revision.author,
+ :committed_on => revision.time,
+ :comments => revision.message)
+
+ revision.paths.each do |change|
+ Change.create!(:changeset => changeset,
+ :action => change[:action],
+ :path => change[:path],
+ :from_path => change[:from_path],
+ :from_revision => change[:from_revision])
+ end
end
end
end
diff --git a/app/models/repository/subversion.rb b/app/models/repository/subversion.rb
index e29c42bb9..055b00876 100644
--- a/app/models/repository/subversion.rb
+++ b/app/models/repository/subversion.rb
@@ -40,9 +40,9 @@ class Repository::Subversion < Repository
'Subversion'
end
- def changesets_for_path(path)
- revisions = scm.revisions(path)
- revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC") : []
+ def changesets_for_path(path, options={})
+ revisions = scm.revisions(path, nil, nil, :limit => options[:limit])
+ revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : []
end
# Returns a path relative to the url of the repository
diff --git a/app/models/role.rb b/app/models/role.rb
index beb13c03b..b07e7a039 100644
--- a/app/models/role.rb
+++ b/app/models/role.rb
@@ -31,9 +31,9 @@ class Role < ActiveRecord::Base
raise "Can not copy workflow from a #{role.class}" unless role.is_a?(Role)
raise "Can not copy workflow from/to an unsaved role" if proxy_owner.new_record? || role.new_record?
clear
- connection.insert "INSERT INTO workflows (tracker_id, old_status_id, new_status_id, role_id)" +
+ connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, old_status_id, new_status_id, role_id)" +
" SELECT tracker_id, old_status_id, new_status_id, #{proxy_owner.id}" +
- " FROM workflows" +
+ " FROM #{Workflow.table_name}" +
" WHERE role_id = #{role.id}"
end
end
diff --git a/app/models/setting.rb b/app/models/setting.rb
index c59925a9f..64d6c1401 100644
--- a/app/models/setting.rb
+++ b/app/models/setting.rb
@@ -75,9 +75,9 @@ class Setting < ActiveRecord::Base
cattr_accessor :available_settings
@@available_settings = YAML::load(File.open("#{RAILS_ROOT}/config/settings.yml"))
- Redmine::Plugin.registered_plugins.each do |id, plugin|
+ Redmine::Plugin.all.each do |plugin|
next unless plugin.settings
- @@available_settings["plugin_#{id}"] = {'default' => plugin.settings[:default], 'serialized' => true}
+ @@available_settings["plugin_#{plugin.id}"] = {'default' => plugin.settings[:default], 'serialized' => true}
end
validates_uniqueness_of :name
@@ -140,6 +140,10 @@ class Setting < ActiveRecord::Base
per_page_options.split(%r{[\s,]}).collect(&:to_i).select {|n| n > 0}.sort
end
+ def self.openid?
+ Object.const_defined?(:OpenID) && self['openid'].to_s == '1'
+ end
+
# Checks if settings have changed since the values were read
# and clears the cache hash if it's the case
# Called once per request
diff --git a/app/models/time_entry.rb b/app/models/time_entry.rb
index 57a75604d..f10b179d1 100644
--- a/app/models/time_entry.rb
+++ b/app/models/time_entry.rb
@@ -32,7 +32,7 @@ class TimeEntry < ActiveRecord::Base
:description => :comments
validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
- validates_numericality_of :hours, :allow_nil => true
+ validates_numericality_of :hours, :allow_nil => true, :message => :activerecord_error_invalid
validates_length_of :comments, :maximum => 255, :allow_nil => true
def after_initialize
@@ -54,7 +54,7 @@ class TimeEntry < ActiveRecord::Base
end
def hours=(h)
- write_attribute :hours, (h.is_a?(String) ? h.to_hours : h)
+ write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
end
# tyear, tmonth, tweek assigned where setting spent_on attributes
diff --git a/app/models/tracker.rb b/app/models/tracker.rb
index ecee908eb..7c5bae250 100644
--- a/app/models/tracker.rb
+++ b/app/models/tracker.rb
@@ -23,9 +23,9 @@ class Tracker < ActiveRecord::Base
raise "Can not copy workflow from a #{tracker.class}" unless tracker.is_a?(Tracker)
raise "Can not copy workflow from/to an unsaved tracker" if proxy_owner.new_record? || tracker.new_record?
clear
- connection.insert "INSERT INTO workflows (tracker_id, old_status_id, new_status_id, role_id)" +
+ connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, old_status_id, new_status_id, role_id)" +
" SELECT #{proxy_owner.id}, old_status_id, new_status_id, role_id" +
- " FROM workflows" +
+ " FROM #{Workflow.table_name}" +
" WHERE tracker_id = #{tracker.id}"
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 132896ad9..4f8223dc0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -37,10 +37,14 @@ class User < ActiveRecord::Base
has_many :members, :dependent => :delete_all
has_many :projects, :through => :memberships
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
+ has_many :changesets, :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
belongs_to :auth_source
+ # Active non-anonymous users scope
+ named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
+
acts_as_customizable
attr_accessor :password, :password_confirmation
@@ -50,7 +54,7 @@ class User < ActiveRecord::Base
validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
- validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }
+ validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
# Login must contain lettres, numbers, underscores only
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
validates_length_of :login, :maximum => 30
@@ -70,17 +74,19 @@ class User < ActiveRecord::Base
# update hashed_password if password was set
self.hashed_password = User.hash_password(self.password) if self.password
end
-
- def self.active
- with_scope :find => { :conditions => [ "status = ?", STATUS_ACTIVE ] } do
- yield
- end
+
+ def reload(*args)
+ @name = nil
+ super
end
- def self.find_active(*args)
- active do
- find(*args)
+ def identity_url=(url)
+ begin
+ self.write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
+ rescue InvalidOpenId
+ # Invlaid url, don't save
end
+ self.read_attribute(:identity_url)
end
# Returns the user that matches provided login and password, or nil
@@ -119,8 +125,11 @@ class User < ActiveRecord::Base
# Return user's full name for display
def name(formatter = nil)
- f = USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
- eval '"' + f + '"'
+ if formatter
+ eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
+ else
+ @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
+ end
end
def active?
@@ -138,13 +147,25 @@ class User < ActiveRecord::Base
def check_password?(clear_password)
User.hash_password(clear_password) == self.hashed_password
end
+
+ # Generate and set a random password. Useful for automated user creation
+ # Based on Token#generate_token_value
+ #
+ def random_password
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+ password = ''
+ 40.times { |i| password << chars[rand(chars.size-1)] }
+ self.password = password
+ self.password_confirmation = password
+ self
+ end
def pref
self.preference ||= UserPreference.new(:user => self)
end
def time_zone
- @time_zone ||= (self.pref.time_zone.blank? ? nil : TimeZone[self.pref.time_zone])
+ @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
end
def wants_comments_in_reverse_order?
@@ -178,15 +199,15 @@ class User < ActiveRecord::Base
token = Token.find_by_action_and_value('autologin', key)
token && (token.created_on > Setting.autologin.to_i.day.ago) && token.user.active? ? token.user : nil
end
+
+ # Makes find_by_mail case-insensitive
+ def self.find_by_mail(mail)
+ find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
+ end
+ # Sort users by their display names
def <=>(user)
- if user.nil?
- -1
- elsif lastname.to_s.downcase == user.lastname.to_s.downcase
- firstname.to_s.downcase <=> user.firstname.to_s.downcase
- else
- lastname.to_s.downcase <=> user.lastname.to_s.downcase
- end
+ self.to_s.downcase <=> user.to_s.downcase
end
def to_s
diff --git a/app/models/version.rb b/app/models/version.rb
index e379f4b05..7f96cea6b 100644
--- a/app/models/version.rb
+++ b/app/models/version.rb
@@ -19,7 +19,8 @@ class Version < ActiveRecord::Base
before_destroy :check_integrity
belongs_to :project
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
- has_many :attachments, :as => :container, :dependent => :destroy
+ acts_as_attachable :view_permission => :view_files,
+ :delete_permission => :manage_files
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id]
@@ -50,20 +51,20 @@ class Version < ActiveRecord::Base
end
def completed_pourcent
- if fixed_issues.count == 0
+ if issues_count == 0
0
elsif open_issues_count == 0
100
else
- (closed_issues_count * 100 + Issue.sum('done_ratio', :include => 'status', :conditions => ["fixed_version_id = ? AND is_closed = ?", id, false]).to_f) / fixed_issues.count
+ issues_progress(false) + issues_progress(true)
end
end
def closed_pourcent
- if fixed_issues.count == 0
+ if issues_count == 0
0
else
- closed_issues_count * 100.0 / fixed_issues.count
+ issues_progress(false)
end
end
@@ -72,6 +73,11 @@ class Version < ActiveRecord::Base
effective_date && (effective_date < Date.today) && (open_issues_count > 0)
end
+ # Returns assigned issues count
+ def issues_count
+ @issue_count ||= fixed_issues.count
+ end
+
def open_issues_count
@open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
end
@@ -103,4 +109,35 @@ private
def check_integrity
raise "Can't delete version" if self.fixed_issues.find(:first)
end
+
+ # Returns the average estimated time of assigned issues
+ # or 1 if no issue has an estimated time
+ # Used to weigth unestimated issues in progress calculation
+ def estimated_average
+ if @estimated_average.nil?
+ average = fixed_issues.average(:estimated_hours).to_f
+ if average == 0
+ average = 1
+ end
+ @estimated_average = average
+ end
+ @estimated_average
+ end
+
+ # Returns the total progress of open or closed issues
+ def issues_progress(open)
+ @issues_progress ||= {}
+ @issues_progress[open] ||= begin
+ progress = 0
+ if issues_count > 0
+ ratio = open ? 'done_ratio' : 100
+ done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
+ :include => :status,
+ :conditions => ["is_closed = ?", !open]).to_f
+
+ progress = done / (estimated_average * issues_count)
+ end
+ progress
+ end
+ end
end
diff --git a/app/models/wiki.rb b/app/models/wiki.rb
index 3432a2bc7..be048775a 100644
--- a/app/models/wiki.rb
+++ b/app/models/wiki.rb
@@ -43,6 +43,25 @@ class Wiki < ActiveRecord::Base
page
end
+ # Finds a page by title
+ # The given string can be of one of the forms: "title" or "project:title"
+ # Examples:
+ # Wiki.find_page("bar", project => foo)
+ # Wiki.find_page("foo:bar")
+ def self.find_page(title, options = {})
+ project = options[:project]
+ if title.to_s =~ %r{^([^\:]+)\:(.*)$}
+ project_identifier, title = $1, $2
+ project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier)
+ end
+ if project && project.wiki
+ page = project.wiki.find_page(title)
+ if page && page.content
+ page
+ end
+ end
+ end
+
# turn a string into a valid page title
def self.titleize(title)
# replace spaces with _ and remove unwanted caracters
diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb
index 4a4c5c270..e958e7b24 100644
--- a/app/models/wiki_content.rb
+++ b/app/models/wiki_content.rb
@@ -37,6 +37,7 @@ class WikiContent < ActiveRecord::Base
acts_as_activity_provider :type => 'wiki_edits',
:timestamp => "#{WikiContent.versioned_table_name}.updated_on",
+ :author_key => "#{WikiContent.versioned_table_name}.author_id",
:permission => :view_wiki_edits,
: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, " +
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 2416fab74..0d96cc047 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -21,7 +21,7 @@ require 'enumerator'
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_attachable :delete_permission => :delete_wiki_pages_attachments
acts_as_tree :order => 'title'
acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
@@ -111,6 +111,10 @@ class WikiPage < ActiveRecord::Base
def editable_by?(usr)
!protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
end
+
+ def attachments_deletable?(usr=User.current)
+ editable_by?(usr) && super(usr)
+ end
def parent_title
@parent_title || (self.parent && self.parent.pretty_title)
diff --git a/app/models/workflow.rb b/app/models/workflow.rb
index 89322aa58..4168fdea0 100644
--- a/app/models/workflow.rb
+++ b/app/models/workflow.rb
@@ -21,4 +21,23 @@ class Workflow < ActiveRecord::Base
belongs_to :new_status, :class_name => 'IssueStatus', :foreign_key => 'new_status_id'
validates_presence_of :role, :old_status, :new_status
+
+ # Returns workflow transitions count by tracker and role
+ def self.count_by_tracker_and_role
+ counts = connection.select_all("SELECT role_id, tracker_id, count(id) AS c FROM #{Workflow.table_name} GROUP BY role_id, tracker_id")
+ roles = Role.find(:all, :order => 'builtin, position')
+ trackers = Tracker.find(:all, :order => 'position')
+
+ result = []
+ trackers.each do |tracker|
+ t = []
+ roles.each do |role|
+ row = counts.detect {|c| c['role_id'] == role.id.to_s && c['tracker_id'] == tracker.id.to_s}
+ t << [role, (row.nil? ? 0 : row['c'].to_i)]
+ end
+ result << [tracker, t]
+ end
+
+ result
+ end
end
diff --git a/app/views/account/login.rhtml b/app/views/account/login.rhtml
index d8c1f313f..c55419d8e 100644
--- a/app/views/account/login.rhtml
+++ b/app/views/account/login.rhtml
@@ -10,6 +10,12 @@
<td align="right"><label for="password"><%=l(:field_password)%>:</label></td>
<td align="left"><%= password_field_tag 'password', nil, :size => 40 %></td>
</tr>
+<% if Setting.openid? %>
+<tr>
+ <td align="right"><label for="openid_url"><%=l(:field_identity_url)%></label></td>
+ <td align="left"><%= text_field_tag "openid_url" %></td>
+</tr>
+<% end %>
<tr>
<td></td>
<td align="left">
diff --git a/app/views/account/register.rhtml b/app/views/account/register.rhtml
index 755a7ad4b..f9f388ae4 100644
--- a/app/views/account/register.rhtml
+++ b/app/views/account/register.rhtml
@@ -1,4 +1,4 @@
-<h2><%=l(:label_register)%></h2>
+<h2><%=l(:label_register)%> <%=link_to l(:label_login_with_open_id_option), signin_url if Setting.openid? %></h2>
<% form_tag({:action => 'register'}, :class => "tabular") do %>
<%= error_messages_for 'user' %>
@@ -29,7 +29,12 @@
<p><label for="user_language"><%=l(:field_language)%></label>
<%= select("user", "language", lang_options_for_select) %></p>
-<% @user.custom_field_values.each do |value| %>
+<% if Setting.openid? %>
+<p><label for="user_identity_url"><%=l(:field_identity_url)%></label>
+<%= text_field 'user', 'identity_url' %></p>
+<% end %>
+
+<% @user.custom_field_values.select {|v| v.editable? || v.required?}.each do |value| %>
<p><%= custom_field_tag_with_label :user, value %></p>
<% end %>
<!--[eoform:user]-->
diff --git a/app/views/account/show.rhtml b/app/views/account/show.rhtml
index 1160a5d8c..649b4b45c 100644
--- a/app/views/account/show.rhtml
+++ b/app/views/account/show.rhtml
@@ -2,19 +2,23 @@
<%= link_to(l(:button_edit), {:controller => 'users', :action => 'edit', :id => @user}, :class => 'icon icon-edit') if User.current.admin? %>
</div>
-<h2><%=h @user.name %></h2>
+<h2><%= avatar @user %> <%=h @user.name %></h2>
-<p>
-<%= mail_to(h(@user.mail)) unless @user.pref.hide_mail %>
+<div class="splitcontentleft">
<ul>
- <li><%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %></li>
-<% for custom_value in @custom_values %>
-<% if !custom_value.value.empty? %>
+ <% unless @user.pref.hide_mail %>
+ <li><%=l(:field_mail)%>: <%= mail_to(h(@user.mail), nil, :encode => 'javascript') %></li>
+ <% end %>
+ <% for custom_value in @custom_values %>
+ <% if !custom_value.value.empty? %>
<li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
-<% end %>
-<% end %>
+ <% end %>
+ <% end %>
+ <li><%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %></li>
+ <% unless @user.last_login_on.nil? %>
+ <li><%=l(:field_last_login_on)%>: <%= format_date(@user.last_login_on) %></li>
+ <% end %>
</ul>
-</p>
<% unless @memberships.empty? %>
<h3><%=l(:label_project_plural)%></h3>
@@ -25,8 +29,40 @@
<% end %>
</ul>
<% end %>
+</div>
+
+<div class="splitcontentright">
+
+<% unless @events_by_day.empty? %>
+<h3><%= link_to l(:label_activity), :controller => 'projects', :action => 'activity', :user_id => @user, :from => @events_by_day.keys.first %></h3>
-<h3><%=l(:label_activity)%></h3>
<p>
<%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %>
-</p> \ No newline at end of file
+</p>
+
+<div id="activity">
+<% @events_by_day.keys.sort.reverse.each do |day| %>
+<h4><%= format_activity_day(day) %></h4>
+<dl>
+<% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%>
+ <dt class="<%= e.event_type %>">
+ <span class="time"><%= format_time(e.event_datetime, false) %></span>
+ <%= content_tag('span', h(e.project), :class => 'project') %>
+ <%= link_to format_activity_title(e.event_title), e.event_url %></dt>
+ <dd><span class="description"><%= format_activity_description(e.event_description) %></span></dd>
+<% end -%>
+</dl>
+<% end -%>
+</div>
+
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => nil, :user_id => @user, :key => User.current.rss_key} %>
+<% end %>
+
+<% content_for :header_tags do %>
+ <%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :user_id => @user, :format => :atom, :key => User.current.rss_key) %>
+<% end %>
+<% end %>
+</div>
+
+<% html_title @user.name %>
diff --git a/app/views/admin/index.rhtml b/app/views/admin/index.rhtml
index 18bee34cb..323641744 100644
--- a/app/views/admin/index.rhtml
+++ b/app/views/admin/index.rhtml
@@ -19,7 +19,7 @@
<p class="icon22 icon22-tracker">
<%= link_to l(:label_tracker_plural), :controller => 'trackers' %> |
<%= link_to l(:label_issue_status_plural), :controller => 'issue_statuses' %> |
-<%= link_to l(:label_workflow), :controller => 'roles', :action => 'workflow' %>
+<%= link_to l(:label_workflow), :controller => 'workflows', :action => 'edit' %>
</p>
<p class="icon22 icon22-workflow">
@@ -34,6 +34,16 @@
<%= link_to l(:label_settings), :controller => 'settings' %>
</p>
+<% menu_items_for(:admin_menu) do |item, caption, url, selected| -%>
+ <%= content_tag 'p',
+ link_to(h(caption), item.url, item.html_options),
+ :class => ["icon22", "icon22-#{item.name}"].join(' ') %>
+<% end -%>
+
+<p class="icon22 icon22-plugin">
+<%= link_to l(:label_plugins), :controller => 'admin', :action => 'plugins' %>
+</p>
+
<p class="icon22 icon22-info">
<%= link_to l(:label_information_plural), :controller => 'admin', :action => 'info' %>
</p>
diff --git a/app/views/admin/info.rhtml b/app/views/admin/info.rhtml
index 05c27f5ac..8c126b50a 100644
--- a/app/views/admin/info.rhtml
+++ b/app/views/admin/info.rhtml
@@ -4,24 +4,9 @@
<table class="list">
<tr class="odd"><td><%= l(:text_default_administrator_account_changed) %></td><td><%= image_tag (@flags[:default_admin_changed] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
-<tr class="even"><td><%= l(:text_file_repository_writable) %></td><td><%= image_tag (@flags[:file_repository_writable] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
+<tr class="even"><td><%= l(:text_file_repository_writable) %> (<%= Attachment.storage_path %>)</td><td><%= image_tag (@flags[:file_repository_writable] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
+<tr class="even"><td><%= l(:text_plugin_assets_writable) %> (<%= Engines.public_directory %>)</td><td><%= image_tag (@flags[:plugin_assets_writable] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
<tr class="odd"><td><%= l(:text_rmagick_available) %></td><td><%= image_tag (@flags[:rmagick_available] ? 'true.png' : 'false.png'), :style => "vertical-align:bottom;" %></td></tr>
</table>
-<% if @plugins.any? %>
-&nbsp;
-<h3 class="icon22 icon22-plugin"><%= l(:label_plugins) %></h3>
-<table class="list">
- <% @plugins.keys.sort {|x,y| x.to_s <=> y.to_s}.each do |plugin| %>
- <tr class="<%= cycle('odd', 'even') %>">
- <td><%=h @plugins[plugin].name %></td>
- <td><%=h @plugins[plugin].description %></td>
- <td><%=h @plugins[plugin].author %></td>
- <td><%=h @plugins[plugin].version %></td>
- <td><%= link_to(l(:button_configure), :controller => 'settings', :action => 'plugin', :id => plugin.to_s) if @plugins[plugin].configurable? %></td>
- </tr>
- <% end %>
-</table>
-<% end %>
-
<% html_title(l(:label_information_plural)) -%>
diff --git a/app/views/admin/plugins.rhtml b/app/views/admin/plugins.rhtml
new file mode 100644
index 000000000..4ee6c142c
--- /dev/null
+++ b/app/views/admin/plugins.rhtml
@@ -0,0 +1,19 @@
+<h2><%= l(:label_plugins) %></h2>
+
+<% if @plugins.any? %>
+<table class="list plugins">
+ <% @plugins.each do |plugin| %>
+ <tr class="<%= cycle('odd', 'even') %>">
+ <td><span class="name"><%=h plugin.name %></span>
+ <%= content_tag('span', h(plugin.description), :class => 'description') unless plugin.description.blank? %>
+ <%= content_tag('span', link_to(h(plugin.url), plugin.url), :class => 'url') unless plugin.url.blank? %>
+ </td>
+ <td class="author"><%= plugin.author_url.blank? ? h(plugin.author) : link_to(h(plugin.author), plugin.author_url) %></td>
+ <td class="version"><%=h plugin.version %></td>
+ <td class="configure"><%= link_to(l(:button_configure), :controller => 'settings', :action => 'plugin', :id => plugin.id) if plugin.configurable? %></td>
+ </tr>
+ <% end %>
+</table>
+<% else %>
+<p class="nodata"><%= l(:label_no_data) %></p>
+<% end %>
diff --git a/app/views/admin/projects.rhtml b/app/views/admin/projects.rhtml
index c42845622..40177a63b 100644
--- a/app/views/admin/projects.rhtml
+++ b/app/views/admin/projects.rhtml
@@ -4,33 +4,33 @@
<h2><%=l(:label_project_plural)%></h2>
-<% form_tag() do %>
+<% form_tag({}, :method => :get) do %>
<fieldset><legend><%= l(:label_filter_plural) %></legend>
<label><%= l(:field_status) %> :</label>
<%= select_tag 'status', project_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %>
-<%= submit_tag l(:button_apply), :class => "small" %>
+<label><%= l(:label_project) %>:</label>
+<%= text_field_tag 'name', params[:name], :size => 30 %>
+<%= submit_tag l(:button_apply), :class => "small", :name => nil %>
</fieldset>
<% end %>
&nbsp;
<table class="list">
<thead><tr>
- <%= sort_header_tag('name', :caption => l(:label_project)) %>
+ <th><%=l(:label_project)%></th>
<th><%=l(:field_description)%></th>
- <th><%=l(:label_subproject_plural)%></th>
- <%= sort_header_tag('is_public', :caption => l(:field_is_public), :default_order => 'desc') %>
- <%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %>
+ <th><%=l(:field_is_public)%></th>
+ <th><%=l(:field_created_on)%></th>
<th></th>
<th></th>
</tr></thead>
<tbody>
<% for project in @projects %>
- <tr class="<%= cycle("odd", "even") %>">
- <td><%= project.active? ? link_to(h(project.name), :controller => 'projects', :action => 'settings', :id => project) : h(project.name) %>
- <td><%= textilizable project.short_description, :project => project %>
- <td align="center"><%= project.children.size %>
- <td align="center"><%= image_tag 'true.png' if project.is_public? %>
- <td align="center"><%= format_date(project.created_on) %>
+ <tr class="<%= cycle("odd", "even") %> <%= css_project_classes(project) %>">
+ <td class="name" style="padding-left: <%= project.level %>em;"><%= project.active? ? link_to(h(project.name), :controller => 'projects', :action => 'settings', :id => project) : h(project.name) %></td>
+ <td><%= textilizable project.short_description, :project => project %></td>
+ <td align="center"><%= image_tag 'true.png' if project.is_public? %></td>
+ <td align="center"><%= format_date(project.created_on) %></td>
<td align="center" style="width:10%">
<small>
<%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %>
@@ -45,6 +45,4 @@
</tbody>
</table>
-<p class="pagination"><%= pagination_links_full @project_pages, @project_count %></p>
-
<% html_title(l(:label_project_plural)) -%>
diff --git a/app/views/attachments/_links.rhtml b/app/views/attachments/_links.rhtml
index 9aae909fe..19ab6734a 100644
--- a/app/views/attachments/_links.rhtml
+++ b/app/views/attachments/_links.rhtml
@@ -3,14 +3,14 @@
<p><%= link_to_attachment attachment, :class => 'icon icon-attachment' -%>
<%= h(" - #{attachment.description}") unless attachment.description.blank? %>
<span class="size">(<%= number_to_human_size attachment.filesize %>)</span>
- <% if options[:delete_url] %>
- <%= link_to image_tag('delete.png'), options[:delete_url].update({:attachment_id => attachment}),
+ <% if options[:deletable] %>
+ <%= link_to image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => attachment},
:confirm => l(:text_are_you_sure),
:method => :post,
:class => 'delete',
:title => l(:button_delete) %>
<% end %>
- <% unless options[:no_author] %>
+ <% if options[:author] %>
<span class="author"><%= attachment.author %>, <%= format_time(attachment.created_on) %></span>
<% end %>
</p>
diff --git a/app/views/auth_sources/list.rhtml b/app/views/auth_sources/list.rhtml
index 6836e6c67..5729ec77c 100644
--- a/app/views/auth_sources/list.rhtml
+++ b/app/views/auth_sources/list.rhtml
@@ -9,6 +9,7 @@
<th><%=l(:field_name)%></th>
<th><%=l(:field_type)%></th>
<th><%=l(:field_host)%></th>
+ <th><%=l(:label_user_plural)%></th>
<th></th>
<th></th>
</tr></thead>
@@ -18,8 +19,12 @@
<td><%= link_to source.name, :action => 'edit', :id => source%></td>
<td align="center"><%= source.auth_method_name %></td>
<td align="center"><%= source.host %></td>
+ <td align="center"><%= source.users.count %></td>
<td align="center"><%= link_to l(:button_test), :action => 'test_connection', :id => source %></td>
- <td align="center"><%= button_to l(:button_delete), { :action => 'destroy', :id => source }, :confirm => l(:text_are_you_sure), :class => "button-small" %></td>
+ <td align="center"><%= button_to l(:button_delete), { :action => 'destroy', :id => source },
+ :confirm => l(:text_are_you_sure),
+ :class => "button-small",
+ :disabled => source.users.any? %></td>
</tr>
<% end %>
</tbody>
diff --git a/app/views/boards/index.rhtml b/app/views/boards/index.rhtml
index 655352a96..440a77412 100644
--- a/app/views/boards/index.rhtml
+++ b/app/views/boards/index.rhtml
@@ -29,11 +29,9 @@
</tbody>
</table>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'Atom', {:controller => 'projects', :action => 'activity', :id => @project, :format => 'atom', :show_messages => 1, :key => User.current.rss_key},
- :class => 'feed' %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => @project, :show_messages => 1, :key => User.current.rss_key} %>
+<% end %>
<% 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}) %>
diff --git a/app/views/boards/show.rhtml b/app/views/boards/show.rhtml
index 96818df34..011a25e06 100644
--- a/app/views/boards/show.rhtml
+++ b/app/views/boards/show.rhtml
@@ -4,7 +4,7 @@
<%= link_to_if_authorized l(:label_message_new),
{:controller => 'messages', :action => 'new', :board_id => @board},
:class => 'icon icon-add',
- :onclick => 'Element.show("add-message"); return false;' %>
+ :onclick => 'Element.show("add-message"); Form.Element.focus("message_subject"); return false;' %>
<%= watcher_tag(@board, User.current) %>
</div>
@@ -26,15 +26,16 @@
</div>
<h2><%=h @board.name %></h2>
+<p class="subtitle"><%=h @board.description %></p>
<% if @topics.any? %>
<table class="list messages">
<thead><tr>
<th><%= l(:field_subject) %></th>
<th><%= l(:field_author) %></th>
- <%= 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)) %>
+ <%= sort_header_tag('created_on', :caption => l(:field_created_on)) %>
+ <%= sort_header_tag('replies', :caption => l(:label_reply_plural)) %>
+ <%= sort_header_tag('updated_on', :caption => l(:label_message_last)) %>
</tr></thead>
<tbody>
<% @topics.each do |topic| %>
diff --git a/app/views/common/_diff.rhtml b/app/views/common/_diff.rhtml
index 0b28101b7..104845b4d 100644
--- a/app/views/common/_diff.rhtml
+++ b/app/views/common/_diff.rhtml
@@ -1,4 +1,5 @@
-<% Redmine::UnifiedDiff.new(diff, diff_type).each do |table_file| -%>
+<% diff = Redmine::UnifiedDiff.new(diff, :type => diff_type, :max_lines => Setting.diff_max_lines_displayed.to_i) -%>
+<% diff.each do |table_file| -%>
<div class="autoscroll">
<% if diff_type == 'sbs' -%>
<table class="filecontent CodeRay">
@@ -62,3 +63,5 @@
</div>
<% end -%>
+
+<%= l(:text_diff_truncated) if diff.truncated? %>
diff --git a/app/views/common/feed.atom.rxml b/app/views/common/feed.atom.rxml
index c1b88a28e..8d07cf208 100644
--- a/app/views/common/feed.atom.rxml
+++ b/app/views/common/feed.atom.rxml
@@ -6,7 +6,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
xml.id url_for(:controller => 'welcome', :only_path => false)
xml.updated((@items.first ? @items.first.event_datetime : Time.now).xmlschema)
xml.author { xml.name "#{Setting.app_title}" }
- xml.generator(:uri => Redmine::Info.url, :version => Redmine::VERSION) { xml.text! Redmine::Info.versioned_name; }
+ xml.generator(:uri => Redmine::Info.url) { xml.text! Redmine::Info.app_name; }
@items.each do |item|
xml.entry do
url = url_for(item.event_url(:only_path => false))
diff --git a/app/views/custom_fields/_form.rhtml b/app/views/custom_fields/_form.rhtml
index f4aee6870..874c571bf 100644
--- a/app/views/custom_fields/_form.rhtml
+++ b/app/views/custom_fields/_form.rhtml
@@ -49,23 +49,6 @@ function toggle_custom_field_format() {
}
}
-function addValueField() {
- var f = $$('p#custom_field_possible_values span');
- p = document.getElementById("custom_field_possible_values");
- var v = f[0].cloneNode(true);
- v.childNodes[0].value = "";
- p.appendChild(v);
-}
-
-function deleteValueField(e) {
- var f = $$('p#custom_field_possible_values span');
- if (f.length == 1) {
- e.parentNode.childNodes[0].value = "";
- } else {
- Element.remove(e.parentNode);
- }
-}
-
//]]>
</script>
@@ -76,22 +59,22 @@ function deleteValueField(e) {
<%= f.text_field :min_length, :size => 5, :no_label => true %> -
<%= f.text_field :max_length, :size => 5, :no_label => true %><br>(<%=l(:text_min_max_length_info)%>)</p>
<p><%= f.text_field :regexp, :size => 50 %><br>(<%=l(:text_regexp_info)%>)</p>
-<p id="custom_field_possible_values"><label><%= l(:field_possible_values) %> <%= image_to_function "add.png", "addValueField();return false" %></label>
-<% (@custom_field.possible_values.to_a + [""]).each do |value| %>
-<span><%= text_field_tag 'custom_field[possible_values][]', value, :size => 30 %> <%= image_to_function "delete.png", "deleteValueField(this);return false" %><br /></span>
-<% end %>
-</p>
+<p id="custom_field_possible_values"><%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"),
+ :cols => 20,
+ :rows => 15 %>
+<br /><em><%= l(:text_custom_field_possible_values_info) %></em></p>
<p><%= @custom_field.field_format == 'bool' ? f.check_box(:default_value) : f.text_field(:default_value) %></p>
</div>
<div class="box">
-<% case @custom_field.type.to_s
+<% case @custom_field.class.name
when "IssueCustomField" %>
<fieldset><legend><%=l(:label_tracker_plural)%></legend>
<% for tracker in @trackers %>
- <%= check_box_tag "tracker_ids[]", tracker.id, (@custom_field.trackers.include? tracker) %> <%= tracker.name %>
+ <%= check_box_tag "custom_field[tracker_ids][]", tracker.id, (@custom_field.trackers.include? tracker) %> <%= tracker.name %>
<% end %>
+ <%= hidden_field_tag "custom_field[tracker_ids][]", '' %>
</fieldset>
&nbsp;
<p><%= f.check_box :is_required %></p>
@@ -101,6 +84,7 @@ when "IssueCustomField" %>
<% when "UserCustomField" %>
<p><%= f.check_box :is_required %></p>
+ <p><%= f.check_box :editable %></p>
<% when "ProjectCustomField" %>
<p><%= f.check_box :is_required %></p>
diff --git a/app/views/custom_fields/list.rhtml b/app/views/custom_fields/index.rhtml
index 43ddd99c8..43ddd99c8 100644
--- a/app/views/custom_fields/list.rhtml
+++ b/app/views/custom_fields/index.rhtml
diff --git a/app/views/documents/index.rhtml b/app/views/documents/index.rhtml
index 14d997360..7449b67c6 100644
--- a/app/views/documents/index.rhtml
+++ b/app/views/documents/index.rhtml
@@ -2,7 +2,7 @@
<%= link_to_if_authorized l(:label_document_new),
{:controller => 'documents', :action => 'new', :project_id => @project},
:class => 'icon icon-add',
- :onclick => 'Element.show("add-document"); return false;' %>
+ :onclick => 'Element.show("add-document"); Form.Element.focus("document_title"); return false;' %>
</div>
<div id="add-document" style="display:none;">
diff --git a/app/views/documents/show.rhtml b/app/views/documents/show.rhtml
index aa90c5518..4d18a7791 100644
--- a/app/views/documents/show.rhtml
+++ b/app/views/documents/show.rhtml
@@ -12,7 +12,7 @@
</div>
<h3><%= l(:label_attachment_plural) %></h3>
-<%= link_to_attachments @attachments, :delete_url => (authorize_for('documents', 'destroy_attachment') ? {:controller => 'documents', :action => 'destroy_attachment', :id => @document} : nil) %>
+<%= link_to_attachments @document %>
<% if authorize_for('documents', 'add_attachment') %>
<p><%= link_to l(:label_attachment_new), {}, :onclick => "Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;",
diff --git a/app/views/issues/_changesets.rhtml b/app/views/issues/_changesets.rhtml
index caa983cbf..15b75c61d 100644
--- a/app/views/issues/_changesets.rhtml
+++ b/app/views/issues/_changesets.rhtml
@@ -2,7 +2,7 @@
<div class="changeset <%= cycle('odd', 'even') %>">
<p><%= link_to("#{l(:label_revision)} #{changeset.revision}",
:controller => 'repositories', :action => 'revision', :id => @project, :rev => changeset.revision) %><br />
- <span class="author"><%= authoring(changeset.committed_on, changeset.committer) %></span></p>
+ <span class="author"><%= authoring(changeset.committed_on, changeset.author) %></span></p>
<%= textilizable(changeset, :comments) %>
</div>
<% end %>
diff --git a/app/views/issues/_edit.rhtml b/app/views/issues/_edit.rhtml
index 2c7a4286e..413f21729 100644
--- a/app/views/issues/_edit.rhtml
+++ b/app/views/issues/_edit.rhtml
@@ -35,6 +35,7 @@
<fieldset><legend><%= l(:field_notes) %></legend>
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
<%= wikitoolbar_for 'notes' %>
+ <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
<p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form' %></p>
</fieldset>
diff --git a/app/views/issues/_form.rhtml b/app/views/issues/_form.rhtml
index 419536fee..fac2d6a36 100644
--- a/app/views/issues/_form.rhtml
+++ b/app/views/issues/_form.rhtml
@@ -8,13 +8,14 @@
<div id="issue_descr_fields" <%= 'style="display:none"' unless @issue.new_record? || @issue.errors.any? %>>
<p><%= f.text_field :subject, :size => 80, :required => true %></p>
-<p><%= f.text_area :description, :required => true,
+<p><%= f.text_area :description,
:cols => 60,
:rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
:accesskey => accesskey(:edit),
:class => 'wiki-edit' %></p>
</div>
+<div class="attributes">
<div class="splitcontentleft">
<% if @issue.new_record? || @allowed_statuses.any? %>
<p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
@@ -24,11 +25,13 @@
<p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p>
<p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
+<% unless @project.issue_categories.empty? %>
<p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
<%= prompt_to_remote(l(:label_issue_category_new),
l(:label_issue_category_new), 'category[name]',
{:controller => 'projects', :action => 'add_issue_category', :id => @project},
:class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
+<% end %>
<%= content_tag('p', f.select(:fixed_version_id,
(@project.versions.sort.collect {|v| [v.name, v.id]}),
{ :include_blank => true })) unless @project.versions.empty? %>
@@ -43,11 +46,20 @@
<div style="clear:both;"> </div>
<%= render :partial => 'form_custom_fields' %>
+</div>
<% if @issue.new_record? %>
<p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
<% end %>
+<% if @issue.new_record? && User.current.allowed_to?(:add_issue_watchers, @project) -%>
+<p><label><%= l(:label_issue_watchers) %></label>
+<% @issue.project.users.sort.each do |user| -%>
+<label class="floating"><%= check_box_tag 'issue[watcher_user_ids][]', user.id, @issue.watcher_user_ids.include?(user.id) %> <%=h user %></label>
+<% end -%>
+</p>
+<% end %>
+
<%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
<%= wikitoolbar_for 'issue_description' %>
diff --git a/app/views/issues/_form_update.rhtml b/app/views/issues/_form_update.rhtml
index 25e81a7fd..3f17a0300 100644
--- a/app/views/issues/_form_update.rhtml
+++ b/app/views/issues/_form_update.rhtml
@@ -1,3 +1,4 @@
+<div class="attributes">
<div class="splitcontentleft">
<p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
<p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
@@ -8,3 +9,4 @@
(@project.versions.sort.collect {|v| [v.name, v.id]}),
{ :include_blank => true })) unless @project.versions.empty? %>
</div>
+</div>
diff --git a/app/views/issues/_history.rhtml b/app/views/issues/_history.rhtml
index b8efdb400..267263b72 100644
--- a/app/views/issues/_history.rhtml
+++ b/app/views/issues/_history.rhtml
@@ -1,14 +1,16 @@
<% reply_links = authorize_for('issues', 'edit') -%>
<% for journal in journals %>
- <div id="change-<%= journal.id %>" class="journal">
- <h4><div style="float:right;"><%= link_to "##{journal.indice}", :anchor => "note-#{journal.indice}" %></div>
- <%= content_tag('a', '', :name => "note-#{journal.indice}")%>
- <%= format_time(journal.created_on) %> - <%= journal.user.name %></h4>
- <ul>
- <% for detail in journal.details %>
- <li><%= show_detail(detail) %></li>
- <% end %>
- </ul>
- <%= render_notes(journal, :reply_links => reply_links) unless journal.notes.blank? %>
- </div>
+ <div id="change-<%= journal.id %>" class="journal">
+ <h4><div style="float:right;"><%= link_to "##{journal.indice}", :anchor => "note-#{journal.indice}" %></div>
+ <%= content_tag('a', '', :name => "note-#{journal.indice}")%>
+ <%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %></h4>
+ <%= avatar(journal.user, :size => "32") %>
+ <ul>
+ <% for detail in journal.details %>
+ <li><%= show_detail(detail) %></li>
+ <% end %>
+ </ul>
+ <%= render_notes(journal, :reply_links => reply_links) unless journal.notes.blank? %>
+ </div>
+ <%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %>
<% end %>
diff --git a/app/views/issues/_list.rhtml b/app/views/issues/_list.rhtml
index b42357894..932676015 100644
--- a/app/views/issues/_list.rhtml
+++ b/app/views/issues/_list.rhtml
@@ -4,14 +4,14 @@
<th><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;',
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
</th>
- <%= sort_header_tag("#{Issue.table_name}.id", :caption => '#', :default_order => 'desc') %>
+ <%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
<% query.columns.each do |column| %>
<%= column_header(column) %>
<% end %>
</tr></thead>
<tbody>
<% issues.each do |issue| -%>
- <tr id="issue-<%= issue.id %>" class="issue hascontextmenu <%= cycle('odd', 'even') %> <%= "status-#{issue.status.position} priority-#{issue.priority.position}" %>">
+ <tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= css_issue_classes(issue) %>">
<td class="checkbox"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
<td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
<% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %>
diff --git a/app/views/issues/_list_simple.rhtml b/app/views/issues/_list_simple.rhtml
index 8900b7359..64c00e5a7 100644
--- a/app/views/issues/_list_simple.rhtml
+++ b/app/views/issues/_list_simple.rhtml
@@ -3,21 +3,22 @@
<table class="list issues">
<thead><tr>
<th>#</th>
+ <th><%=l(:field_project)%></th>
<th><%=l(:field_tracker)%></th>
<th><%=l(:field_subject)%></th>
</tr></thead>
<tbody>
<% for issue in issues %>
- <tr id="issue-<%= issue.id %>" class="issue hascontextmenu <%= cycle('odd', 'even') %> <%= "status-#{issue.status.position} priority-#{issue.priority.position}" %>">
+ <tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= css_issue_classes(issue) %>">
<td class="id">
- <%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;') %>
+ <%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;') %>
<%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %>
</td>
- <td><%=h issue.project.name %> - <%= issue.tracker.name %><br />
- <%= issue.status.name %> - <%= format_time(issue.updated_on) %></td>
+ <td class="project"><%=h issue.project %></td>
+ <td class="tracker"><%=h issue.tracker %></td>
<td class="subject">
- <%= link_to h(issue.subject), :controller => 'issues', :action => 'show', :id => issue %>
- </td>
+ <%= link_to h(truncate(issue.subject, 60)), :controller => 'issues', :action => 'show', :id => issue %> (<%=h issue.status %>)
+ </td>
</tr>
<% end %>
</tbody>
diff --git a/app/views/issues/_relations.rhtml b/app/views/issues/_relations.rhtml
index d4b3e5aa6..f99976f5c 100644
--- a/app/views/issues/_relations.rhtml
+++ b/app/views/issues/_relations.rhtml
@@ -8,9 +8,10 @@
<% if @issue.relations.any? %>
<table style="width:100%">
-<% @issue.relations.each do |relation| %>
+<% @issue.relations.select {|r| r.other_issue(@issue).visible? }.each do |relation| %>
<tr>
-<td><%= l(relation.label_for(@issue)) %> <%= "(#{lwr(:actionview_datehelper_time_in_words_day, relation.delay)})" if relation.delay && relation.delay != 0 %> <%= link_to_issue relation.other_issue(@issue) %></td>
+<td><%= l(relation.label_for(@issue)) %> <%= "(#{lwr(:actionview_datehelper_time_in_words_day, relation.delay)})" if relation.delay && relation.delay != 0 %>
+ <%= h(relation.other_issue(@issue).project) + ' - ' if Setting.cross_project_issue_relations? %> <%= link_to_issue relation.other_issue(@issue) %></td>
<td><%=h relation.other_issue(@issue).subject %></td>
<td><%= relation.other_issue(@issue).status.name %></td>
<td><%= format_date(relation.other_issue(@issue).start_date) %></td>
diff --git a/app/views/issues/_sidebar.rhtml b/app/views/issues/_sidebar.rhtml
index 1b486966e..bbc00f091 100644
--- a/app/views/issues/_sidebar.rhtml
+++ b/app/views/issues/_sidebar.rhtml
@@ -2,23 +2,25 @@
<%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %><br />
<% if @project %>
<%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br />
-<%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %>
+<%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %><br />
+<% end %>
+<%= call_hook(:view_issues_sidebar_issues_bottom) %>
<% planning_links = []
- planning_links << link_to_if_authorized(l(:label_calendar), :action => 'calendar', :project_id => @project)
- planning_links << link_to_if_authorized(l(:label_gantt), :action => 'gantt', :project_id => @project)
- planning_links.compact!
- unless planning_links.empty? %>
+ planning_links << link_to(l(:label_calendar), :action => 'calendar', :project_id => @project) if User.current.allowed_to?(:view_calendar, @project, :global => true)
+ planning_links << link_to(l(:label_gantt), :action => 'gantt', :project_id => @project) if User.current.allowed_to?(:view_gantt, @project, :global => true)
+%>
+<% unless planning_links.empty? %>
<h3><%= l(:label_planning) %></h3>
<p><%= planning_links.join(' | ') %></p>
-<% end %>
-
+<%= call_hook(:view_issues_sidebar_planning_bottom) %>
<% end %>
<% unless sidebar_queries.empty? -%>
<h3><%= l(:label_query_plural) %></h3>
<% sidebar_queries.each do |query| -%>
-<%= link_to query.name, :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %><br />
+<%= link_to(h(query.name), :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query) %><br />
<% end -%>
+<%= call_hook(:view_issues_sidebar_queries_bottom) %>
<% end -%>
diff --git a/app/views/issues/bulk_edit.rhtml b/app/views/issues/bulk_edit.rhtml
index b916cf092..acda6a65f 100644
--- a/app/views/issues/bulk_edit.rhtml
+++ b/app/views/issues/bulk_edit.rhtml
@@ -27,7 +27,7 @@
<label><%= l(:field_fixed_version) %>:
<%= select_tag('fixed_version_id', content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_none), :value => 'none') +
- options_from_collection_for_select(@project.versions, :id, :name)) %></label>
+ options_from_collection_for_select(@project.versions.sort, :id, :name)) %></label>
</p>
<p>
@@ -38,12 +38,20 @@
<label><%= l(:field_done_ratio) %>:
<%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label>
</p>
+
+<% @custom_fields.each do |custom_field| %>
+<p><label><%= h(custom_field.name) %></label>
+<%= select_tag "custom_field_values[#{custom_field.id}]", options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values) %></label>
+</p>
+<% end %>
+
<%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %>
</fieldset>
<fieldset><legend><%= l(:field_notes) %></legend>
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
<%= wikitoolbar_for 'notes' %>
+</fieldset>
</div>
<p><%= submit_tag l(:button_submit) %>
diff --git a/app/views/issues/context_menu.rhtml b/app/views/issues/context_menu.rhtml
index 671655db7..0d50e5f76 100644
--- a/app/views/issues/context_menu.rhtml
+++ b/app/views/issues/context_menu.rhtml
@@ -79,7 +79,10 @@
:class => 'icon-copy', :disabled => !@can[:copy] %></li>
<% if @can[:log_time] -%>
<li><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue},
- :class => 'icon-time' %></li>
+ :class => 'icon-time-add' %></li>
+ <% end %>
+ <% if User.current.logged? %>
+ <li><%= watcher_link(@issue, User.current) %></li>
<% end %>
<% end %>
diff --git a/app/views/issues/gantt.rfpdf b/app/views/issues/gantt.rfpdf
deleted file mode 100644
index 692e0d261..000000000
--- a/app/views/issues/gantt.rfpdf
+++ /dev/null
@@ -1,188 +0,0 @@
-<%
-pdf=IfpdfHelper::IFPDF.new(current_language)
-pdf.SetTitle("#{@project.name} - #{l(:label_gantt)}")
-pdf.AliasNbPages
-pdf.footer_date = format_date(Date.today)
-pdf.AddPage("L")
-pdf.SetFontStyle('B',12)
-pdf.SetX(15)
-pdf.Cell(70, 20, @project.name)
-pdf.Ln
-pdf.SetFontStyle('B',9)
-
-subject_width = 70
-header_heigth = 5
-
-headers_heigth = header_heigth
-show_weeks = false
-show_days = false
-
-if @gantt.months < 7
- show_weeks = true
- headers_heigth = 2*header_heigth
- if @gantt.months < 3
- show_days = true
- headers_heigth = 3*header_heigth
- end
-end
-
-g_width = 210
-zoom = (g_width) / (@gantt.date_to - @gantt.date_from + 1)
-g_height = 120
-t_height = g_height + headers_heigth
-
-y_start = pdf.GetY
-
-
-#
-# Months headers
-#
-month_f = @gantt.date_from
-left = subject_width
-height = header_heigth
-@gantt.months.times do
- width = ((month_f >> 1) - month_f) * zoom
- pdf.SetY(y_start)
- pdf.SetX(left)
- pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
- left = left + width
- month_f = month_f >> 1
-end
-
-#
-# Weeks headers
-#
-if show_weeks
- left = subject_width
- height = header_heigth
- if @gantt.date_from.cwday == 1
- # @gantt.date_from is monday
- week_f = @gantt.date_from
- else
- # find next monday after @gantt.date_from
- week_f = @gantt.date_from + (7 - @gantt.date_from.cwday + 1)
- width = (7 - @gantt.date_from.cwday + 1) * zoom-1
- pdf.SetY(y_start + header_heigth)
- pdf.SetX(left)
- pdf.Cell(width + 1, height, "", "LTR")
- left = left + width+1
- end
- while week_f <= @gantt.date_to
- width = (week_f + 6 <= @gantt.date_to) ? 7 * zoom : (@gantt.date_to - week_f + 1) * zoom
- pdf.SetY(y_start + header_heigth)
- pdf.SetX(left)
- pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
- left = left + width
- week_f = week_f+7
- end
-end
-
-#
-# Days headers
-#
-if show_days
- left = subject_width
- height = header_heigth
- wday = @gantt.date_from.cwday
- pdf.SetFontStyle('B',7)
- (@gantt.date_to - @gantt.date_from + 1).to_i.times do
- width = zoom
- pdf.SetY(y_start + 2 * header_heigth)
- pdf.SetX(left)
- pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
- left = left + width
- wday = wday + 1
- wday = 1 if wday > 7
- end
-end
-
-pdf.SetY(y_start)
-pdf.SetX(15)
-pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
-
-
-#
-# Tasks
-#
-top = headers_heigth + y_start
-pdf.SetFontStyle('B',7)
-@gantt.events.each do |i|
- pdf.SetY(top)
- pdf.SetX(15)
-
- if i.is_a? Issue
- pdf.Cell(subject_width-15, 5, "#{i.tracker.name} #{i.id}: #{i.subject}".sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)'), "LR")
- else
- pdf.Cell(subject_width-15, 5, "#{l(:label_version)}: #{i.name}", "LR")
- end
-
- pdf.SetY(top)
- pdf.SetX(subject_width)
- pdf.Cell(g_width, 5, "", "LR")
-
- pdf.SetY(top+1.5)
-
- if i.is_a? Issue
- i_start_date = (i.start_date >= @gantt.date_from ? i.start_date : @gantt.date_from )
- i_end_date = (i.due_before <= @gantt.date_to ? i.due_before : @gantt.date_to )
-
- i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
- i_done_date = (i_done_date <= @gantt.date_from ? @gantt.date_from : i_done_date )
- i_done_date = (i_done_date >= @gantt.date_to ? @gantt.date_to : i_done_date )
-
- i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
-
- i_left = ((i_start_date - @gantt.date_from)*zoom)
- i_width = ((i_end_date - i_start_date + 1)*zoom)
- d_width = ((i_done_date - i_start_date)*zoom)
- l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date
- l_width ||= 0
-
- pdf.SetX(subject_width + i_left)
- pdf.SetFillColor(200,200,200)
- pdf.Cell(i_width, 2, "", 0, 0, "", 1)
-
- if l_width > 0
- pdf.SetY(top+1.5)
- pdf.SetX(subject_width + i_left)
- pdf.SetFillColor(255,100,100)
- pdf.Cell(l_width, 2, "", 0, 0, "", 1)
- end
- if d_width > 0
- pdf.SetY(top+1.5)
- pdf.SetX(subject_width + i_left)
- pdf.SetFillColor(100,100,255)
- pdf.Cell(d_width, 2, "", 0, 0, "", 1)
- end
-
- pdf.SetY(top+1.5)
- pdf.SetX(subject_width + i_left + i_width)
- pdf.Cell(30, 2, "#{i.status.name} #{i.done_ratio}%")
- else
- i_left = ((i.start_date - @gantt.date_from)*zoom)
-
- pdf.SetX(subject_width + i_left)
- pdf.SetFillColor(50,200,50)
- pdf.Cell(2, 2, "", 0, 0, "", 1)
-
- pdf.SetY(top+1.5)
- pdf.SetX(subject_width + i_left + 3)
- pdf.Cell(30, 2, "#{i.name}")
- end
-
-
- top = top + 5
- pdf.SetDrawColor(200, 200, 200)
- pdf.Line(15, top, subject_width+g_width, top)
- if pdf.GetY() > 180
- pdf.AddPage("L")
- top = 20
- pdf.Line(15, top, subject_width+g_width, top)
- end
- pdf.SetDrawColor(0, 0, 0)
-end
-
-pdf.Line(15, top, subject_width+g_width, top)
-
-%>
-<%= pdf.Output %> \ No newline at end of file
diff --git a/app/views/issues/gantt.rhtml b/app/views/issues/gantt.rhtml
index b9af1f961..0b38982ce 100644
--- a/app/views/issues/gantt.rhtml
+++ b/app/views/issues/gantt.rhtml
@@ -241,13 +241,10 @@ if Date.today >= @gantt.date_from and Date.today <= @gantt.date_to %>
</tr>
</table>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'PDF', @gantt.params.merge(:format => 'pdf'), :class => 'pdf' %></span>
-<% if @gantt.respond_to?('to_image') %>
-<span><%= link_to 'PNG', @gantt.params.merge(:format => 'png'), :class => 'image' %></span>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'PDF', :url => @gantt.params %>
+ <%= f.link_to('PNG', :url => @gantt.params) if @gantt.respond_to?('to_image') %>
<% end %>
-</p>
<% end # query.valid? %>
<% content_for :sidebar do %>
diff --git a/app/views/issues/index.rfpdf b/app/views/issues/index.rfpdf
deleted file mode 100644
index d5a8d3c31..000000000
--- a/app/views/issues/index.rfpdf
+++ /dev/null
@@ -1,50 +0,0 @@
-<% pdf=IfpdfHelper::IFPDF.new(current_language)
- title = @project ? "#{@project.name} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
- pdf.SetTitle(title)
- pdf.AliasNbPages
- pdf.footer_date = format_date(Date.today)
- pdf.AddPage("L")
- row_height = 7
-
- #
- # title
- #
- pdf.SetFontStyle('B',11)
- pdf.Cell(190,10, title)
- pdf.Ln
-
- #
- # headers
- #
- pdf.SetFontStyle('B',10)
- pdf.SetFillColor(230, 230, 230)
- pdf.Cell(15, row_height, "#", 0, 0, 'L', 1)
- pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1)
- pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1)
- pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1)
- pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1)
- pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1)
- pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1)
- pdf.Line(10, pdf.GetY, 287, pdf.GetY)
- pdf.Ln
- pdf.Line(10, pdf.GetY, 287, pdf.GetY)
- pdf.SetY(pdf.GetY() + 1)
-
- #
- # rows
- #
- pdf.SetFontStyle('',9)
- pdf.SetFillColor(255, 255, 255)
- @issues.each do |issue|
- pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1)
- pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1)
- pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1)
- pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1)
- pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.name : '', 0, 0, 'L', 1)
- pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1)
- pdf.MultiCell(0, row_height, (@project == issue.project ? issue.subject : "#{issue.project.name} - #{issue.subject}"))
- pdf.Line(10, pdf.GetY, 287, pdf.GetY)
- pdf.SetY(pdf.GetY() + 1)
- end
-%>
-<%= pdf.Output %> \ No newline at end of file
diff --git a/app/views/issues/index.rhtml b/app/views/issues/index.rhtml
index 973f3eb25..dc93e41f2 100644
--- a/app/views/issues/index.rhtml
+++ b/app/views/issues/index.rhtml
@@ -3,7 +3,7 @@
<% html_title(l(:label_issue_plural)) %>
<% form_tag({ :controller => 'queries', :action => 'new' }, :id => 'query_form') do %>
- <%= hidden_field_tag('project_id', @project.id) if @project %>
+ <%= hidden_field_tag('project_id', @project.to_param) if @project %>
<fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend>
<%= render :partial => 'queries/filters', :locals => {:query => @query} %>
<p class="buttons">
@@ -14,7 +14,8 @@
}, :class => 'icon icon-checked' %>
<%= link_to_remote l(:button_clear),
- { :url => { :set_filter => 1 },
+ { :url => { :set_filter => 1, :project_id => @project },
+ :method => :get,
:update => "content",
}, :class => 'icon icon-reload' %>
@@ -43,12 +44,12 @@
<%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
<p class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></p>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'Atom', {:query_id => @query, :format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
-<span><%= link_to 'CSV', {:format => 'csv'}, :class => 'csv' %></span>
-<span><%= link_to 'PDF', {:format => 'pdf'}, :class => 'pdf' %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => {:query_id => (@query.new_record? ? nil : @query), :key => User.current.rss_key} %>
+ <%= f.link_to 'CSV' %>
+ <%= f.link_to 'PDF' %>
+<% end %>
+
<% end %>
<% end %>
diff --git a/app/views/issues/move.rhtml b/app/views/issues/move.rhtml
index 35761e160..e1189db45 100644
--- a/app/views/issues/move.rhtml
+++ b/app/views/issues/move.rhtml
@@ -6,16 +6,19 @@
<%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
<div class="box tabular">
-<p><label for="new_project_id"><%=l(:field_project)%> :</label>
+<p><label for="new_project_id"><%=l(:field_project)%>:</label>
<%= select_tag "new_project_id",
- options_from_collection_for_select(@allowed_projects, 'id', 'name', @target_project.id),
+ project_tree_options_for_select(@allowed_projects, :selected => @target_project),
:onchange => remote_function(:url => { :action => 'move' },
:method => :get,
:update => 'content',
:with => "Form.serialize('move_form')") %></p>
-<p><label for="new_tracker_id"><%=l(:field_tracker)%> :</label>
+<p><label for="new_tracker_id"><%=l(:field_tracker)%>:</label>
<%= select_tag "new_tracker_id", "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@trackers, "id", "name") %></p>
+
+<p><label for="copy_options_copy"><%= l(:button_copy)%></label>
+<%= check_box_tag "copy_options[copy]", "1" %></p>
</div>
<%= submit_tag l(:button_move) %>
diff --git a/app/views/issues/new.rhtml b/app/views/issues/new.rhtml
index 280e2009b..94f86dde9 100644
--- a/app/views/issues/new.rhtml
+++ b/app/views/issues/new.rhtml
@@ -7,6 +7,7 @@
<%= render :partial => 'issues/form', :locals => {:f => f} %>
</div>
<%= submit_tag l(:button_create) %>
+ <%= submit_tag l(:button_create_and_continue), :name => 'continue' %>
<%= link_to_remote l(:label_preview),
{ :url => { :controller => 'issues', :action => 'preview', :project_id => @project },
:method => 'post',
@@ -14,6 +15,12 @@
:with => "Form.serialize('issue-form')",
:complete => "Element.scrollTo('preview')"
}, :accesskey => accesskey(:preview) %>
+
+ <%= javascript_tag "Form.Element.focus('issue_subject');" %>
<% end %>
<div id="preview" class="wiki"></div>
+
+<% content_for :header_tags do %>
+ <%= stylesheet_link_tag 'scm' %>
+<% end %>
diff --git a/app/views/issues/show.rfpdf b/app/views/issues/show.rfpdf
deleted file mode 100644
index 73d9d66b5..000000000
--- a/app/views/issues/show.rfpdf
+++ /dev/null
@@ -1,126 +0,0 @@
-<% pdf=IfpdfHelper::IFPDF.new(current_language)
- pdf.SetTitle("#{@project.name} - ##{@issue.tracker.name} #{@issue.id}")
- pdf.AliasNbPages
- pdf.footer_date = format_date(Date.today)
- pdf.AddPage
-
- 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/app/views/issues/show.rhtml b/app/views/issues/show.rhtml
index 463fa6960..2d947d047 100644
--- a/app/views/issues/show.rhtml
+++ b/app/views/issues/show.rhtml
@@ -1,6 +1,6 @@
<div class="contextual">
<%= 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' %>
+<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time-add' %>
<%= 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' %>
<%= link_to_if_authorized l(:button_move), {:controller => 'issues', :action => 'move', :id => @issue }, :class => 'icon icon-move' %>
@@ -9,7 +9,8 @@
<h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
-<div class="issue <%= "status-#{@issue.status.position} priority-#{@issue.priority.position}" %>">
+<div class="<%= css_issue_classes(@issue) %>">
+ <%= avatar(@issue.author, :size => "64") %>
<h3><%=h @issue.subject %></h3>
<p class="author">
<%= authoring @issue.created_on, @issue.author %>.
@@ -18,28 +19,28 @@
<table width="100%">
<tr>
- <td style="width:15%"><b><%=l(:field_status)%>:</b></td><td style="width:35%"><%= @issue.status.name %></td>
- <td style="width:15%"><b><%=l(:field_start_date)%>:</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
+ <td style="width:15%" class="status"><b><%=l(:field_status)%>:</b></td><td style="width:35%" class="status status-<%= @issue.status.name %>"><%= @issue.status.name %></td>
+ <td style="width:15%" class="start-date"><b><%=l(:field_start_date)%>:</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
</tr>
<tr>
- <td><b><%=l(:field_priority)%>:</b></td><td><%= @issue.priority.name %></td>
- <td><b><%=l(:field_due_date)%>:</b></td><td><%= format_date(@issue.due_date) %></td>
+ <td class="priority"><b><%=l(:field_priority)%>:</b></td><td class="priority"><%= @issue.priority.name %></td>
+ <td class="due-date"><b><%=l(:field_due_date)%>:</b></td><td class="due-date"><%= format_date(@issue.due_date) %></td>
</tr>
<tr>
- <td><b><%=l(:field_assigned_to)%>:</b></td><td><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
- <td><b><%=l(:field_done_ratio)%>:</b></td><td><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
+ <td class="assigned-to"><b><%=l(:field_assigned_to)%>:</b></td><td><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
+ <td class="progress"><b><%=l(:field_done_ratio)%>:</b></td><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
</tr>
<tr>
- <td><b><%=l(:field_category)%>:</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
+ <td class="category"><b><%=l(:field_category)%>:</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
<% if User.current.allowed_to?(:view_time_entries, @project) %>
- <td><b><%=l(:label_spent_time)%>:</b></td>
- <td><%= @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') : "-" %></td>
+ <td class="spent-time"><b><%=l(:label_spent_time)%>:</b></td>
+ <td class="spent-hours"><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}) : "-" %></td>
<% end %>
</tr>
<tr>
- <td><b><%=l(:field_fixed_version)%>:</b></td><td><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
+ <td class="fixed-version"><b><%=l(:field_fixed_version)%>:</b></td><td><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
<% if @issue.estimated_hours %>
- <td><b><%=l(:field_estimated_hours)%>:</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
+ <td class="estimated-hours"><b><%=l(:field_estimated_hours)%>:</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
<% end %>
</tr>
<tr>
@@ -58,7 +59,7 @@ end %>
<hr />
<div class="contextual">
-<%= link_to_remote_if_authorized l(:button_quote), { :url => {:action => 'reply', :id => @issue} }, :class => 'icon icon-comment' %>
+<%= link_to_remote_if_authorized(l(:button_quote), { :url => {:action => 'reply', :id => @issue} }, :class => 'icon icon-comment') unless @issue.description.blank? %>
</div>
<p><strong><%=l(:field_description)%></strong></p>
@@ -66,9 +67,7 @@ end %>
<%= textilizable @issue, :description, :attachments => @issue.attachments %>
</div>
-<% if @issue.attachments.any? %>
-<%= link_to_attachments @issue.attachments, :delete_url => (authorize_for('issues', 'destroy_attachment') ? {:controller => 'issues', :action => 'destroy_attachment', :id => @issue} : nil) %>
-<% end %>
+<%= link_to_attachments @issue %>
<% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
<hr />
@@ -109,11 +108,10 @@ end %>
</div>
<% end %>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'Atom', {:format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
-<span><%= link_to 'PDF', {:format => 'pdf'}, :class => 'pdf' %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
+ <%= f.link_to 'PDF' %>
+<% end %>
<% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
diff --git a/app/views/journals/_notes_form.rhtml b/app/views/journals/_notes_form.rhtml
index 9baec03fa..6e6ad0f88 100644
--- a/app/views/journals/_notes_form.rhtml
+++ b/app/views/journals/_notes_form.rhtml
@@ -1,6 +1,7 @@
<% form_remote_tag(:url => {}, :html => { :id => "journal-#{@journal.id}-form" }) do %>
- <%= text_area_tag :notes, @journal.notes, :class => 'wiki-edit',
- :rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %>
+ <%= text_area_tag :notes, h(@journal.notes), :class => 'wiki-edit',
+ :rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %>
+ <%= call_hook(:view_journals_notes_form_after_notes, { :journal => @journal}) %>
<p><%= submit_tag l(:button_save) %>
<%= link_to l(:button_cancel), '#', :onclick => "Element.remove('journal-#{@journal.id}-form'); " +
"Element.show('journal-#{@journal.id}-notes'); return false;" %></p>
diff --git a/app/views/journals/update.rjs b/app/views/journals/update.rjs
index 2b5a54c0a..55efb9bf3 100644
--- a/app/views/journals/update.rjs
+++ b/app/views/journals/update.rjs
@@ -6,3 +6,5 @@ else
page.show "journal-#{@journal.id}-notes"
page.remove "journal-#{@journal.id}-form"
end
+
+call_hook(:view_journals_update_rjs_bottom, { :page => page, :journal => @journal })
diff --git a/app/views/layouts/_project_selector.rhtml b/app/views/layouts/_project_selector.rhtml
deleted file mode 100644
index 7a2803534..000000000
--- a/app/views/layouts/_project_selector.rhtml
+++ /dev/null
@@ -1,12 +0,0 @@
-<% user_projects_by_root = User.current.projects.find(:all, :include => :parent).group_by(&:root) %>
-<select onchange="if (this.value != '') { window.location = this.value; }">
-<option selected="selected"><%= l(:label_jump_to_a_project) %></option>
-<option disabled="disabled">---</option>
-<% user_projects_by_root.keys.sort.each do |root| %>
- <%= content_tag('option', h(root.name), :value => url_for(:controller => 'projects', :action => 'show', :id => root)) %>
- <% user_projects_by_root[root].sort.each do |project| %>
- <% next if project == root %>
- <%= content_tag('option', ('&#187; ' + h(project.name)), :value => url_for(:controller => 'projects', :action => 'show', :id => project)) %>
- <% end %>
-<% end %>
-</select>
diff --git a/app/views/layouts/base.rhtml b/app/views/layouts/base.rhtml
index e866287a2..8cb8f5f69 100644
--- a/app/views/layouts/base.rhtml
+++ b/app/views/layouts/base.rhtml
@@ -7,7 +7,7 @@
<meta name="keywords" content="issue,bug,tracker" />
<%= stylesheet_link_tag 'application', :media => 'all' %>
<%= javascript_include_tag :defaults %>
-<%= stylesheet_link_tag 'jstoolbar' %>
+<%= heads_for_wiki_formatter %>
<!--[if IE]>
<style type="text/css">
* html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); }
@@ -24,7 +24,7 @@
<div id="account">
<%= render_menu :account_menu -%>
</div>
- <%= content_tag('div', "#{l(:label_logged_as)} #{User.current.login}", :id => 'loggedas') if User.current.logged? %>
+ <%= content_tag('div', "#{l(:label_logged_as)} #{link_to_user(User.current, :format => :username)}", :id => 'loggedas') if User.current.logged? %>
<%= render_menu :top_menu -%>
</div>
@@ -34,7 +34,7 @@
<%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project}, :accesskey => accesskey(:search) %>:
<%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %>
<% end %>
- <%= render :partial => 'layouts/project_selector' if User.current.memberships.any? %>
+ <%= render_project_jump_box %>
</div>
<h1><%= h(@project && !@project.new_record? ? @project.name : Setting.app_title) %></h1>
@@ -47,20 +47,22 @@
<%= tag('div', {:id => 'main', :class => (has_content?(:sidebar) ? '' : 'nosidebar')}, true) %>
<div id="sidebar">
<%= yield :sidebar %>
+ <%= call_hook :view_layouts_base_sidebar %>
</div>
<div id="content">
- <%= content_tag('div', flash[:error], :class => 'flash error') if flash[:error] %>
- <%= content_tag('div', flash[:notice], :class => 'flash notice') if flash[:notice] %>
+ <%= render_flash_messages %>
<%= yield %>
+ <%= call_hook :view_layouts_base_content %>
</div>
</div>
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
<div id="footer">
- Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> &copy; 2006-2008 Jean-Philippe Lang
+ Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> &copy; 2006-2009 Jean-Philippe Lang
</div>
</div>
+<%= call_hook :view_layouts_base_body_bottom %>
</body>
</html>
diff --git a/app/views/mailer/layout.text.html.rhtml b/app/views/mailer/layout.text.html.rhtml
index c95c94501..12fb654d6 100644
--- a/app/views/mailer/layout.text.html.rhtml
+++ b/app/views/mailer/layout.text.html.rhtml
@@ -32,6 +32,6 @@ hr {
<body>
<%= yield %>
<hr />
-<span class="footer"><%= Redmine::WikiFormatting.to_html(Setting.emails_footer) %></span>
+<span class="footer"><%= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_footer) %></span>
</body>
</html>
diff --git a/app/views/messages/show.rhtml b/app/views/messages/show.rhtml
index 31696d56d..c1c3105fc 100644
--- a/app/views/messages/show.rhtml
+++ b/app/views/messages/show.rhtml
@@ -4,8 +4,8 @@
<div class="contextual">
<%= watcher_tag(@topic, User.current) %>
<%= link_to_remote_if_authorized l(:button_quote), { :url => {:action => 'quote', :id => @topic} }, :class => 'icon icon-comment' %>
- <%= link_to_if_authorized l(:button_edit), {:action => 'edit', :id => @topic}, :class => 'icon icon-edit' %>
- <%= link_to_if_authorized l(:button_delete), {:action => 'destroy', :id => @topic}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del' %>
+ <%= link_to(l(:button_edit), {:action => 'edit', :id => @topic}, :class => 'icon icon-edit') if @message.editable_by?(User.current) %>
+ <%= link_to(l(:button_delete), {:action => 'destroy', :id => @topic}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') if @message.destroyable_by?(User.current) %>
</div>
<h2><%=h @topic.subject %></h2>
@@ -15,7 +15,7 @@
<div class="wiki">
<%= textilizable(@topic.content, :attachments => @topic.attachments) %>
</div>
-<%= link_to_attachments @topic.attachments, :no_author => true %>
+<%= link_to_attachments @topic, :author => false %>
</div>
<br />
@@ -23,15 +23,15 @@
<h3 class="icon22 icon22-comment"><%= l(:label_reply_plural) %></h3>
<% @replies.each do |message| %>
<a name="<%= "message-#{message.id}" %>"></a>
- <div class="contextual">
- <%= 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) %>
- </div>
<div class="message reply">
+ <div class="contextual">
+ <%= link_to_remote_if_authorized image_tag('comment.png'), { :url => {:action => 'quote', :id => message} }, :title => l(:button_quote) %>
+ <%= link_to(image_tag('edit.png'), {:action => 'edit', :id => message}, :title => l(:button_edit)) if message.editable_by?(User.current) %>
+ <%= link_to(image_tag('delete.png'), {:action => 'destroy', :id => message}, :method => :post, :confirm => l(:text_are_you_sure), :title => l(:button_delete)) if message.destroyable_by?(User.current) %>
+ </div>
<h4><%=h message.subject %> - <%= authoring message.created_on, message.author %></h4>
<div class="wiki"><%= textilizable message, :content, :attachments => message.attachments %></div>
- <%= link_to_attachments message.attachments, :no_author => true %>
+ <%= link_to_attachments message, :author => false %>
</div>
<% end %>
<% end %>
diff --git a/app/views/my/account.rhtml b/app/views/my/account.rhtml
index 20210c99a..ea226f5cb 100644
--- a/app/views/my/account.rhtml
+++ b/app/views/my/account.rhtml
@@ -15,6 +15,13 @@
<p><%= f.text_field :lastname, :required => true %></p>
<p><%= f.text_field :mail, :required => true %></p>
<p><%= f.select :language, lang_options_for_select %></p>
+<% if Setting.openid? %>
+<p><%= f.text_field :identity_url %></p>
+<% end %>
+
+<% @user.custom_field_values.select(&:editable?).each do |value| %>
+ <p><%= custom_field_tag_with_label :user, value %></p>
+<% end %>
</div>
<%= submit_tag l(:button_save) %>
@@ -38,7 +45,7 @@
<div class="box tabular">
<% fields_for :pref, @user.pref, :builder => TabularFormBuilder, :lang => current_language do |pref_fields| %>
<p><%= pref_fields.check_box :hide_mail %></p>
-<p><%= pref_fields.select :time_zone, TimeZone.all.collect {|z| [ z.to_s, z.name ]}, :include_blank => true %></p>
+<p><%= pref_fields.select :time_zone, ActiveSupport::TimeZone.all.collect {|z| [ z.to_s, z.name ]}, :include_blank => true %></p>
<p><%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %></p>
<% end %>
</div>
diff --git a/app/views/my/blocks/_issuesassignedtome.rhtml b/app/views/my/blocks/_issuesassignedtome.rhtml
index 99812f6d0..f5e2e3e65 100644
--- a/app/views/my/blocks/_issuesassignedtome.rhtml
+++ b/app/views/my/blocks/_issuesassignedtome.rhtml
@@ -1,6 +1,6 @@
<h3><%=l(:label_assigned_to_me_issues)%></h3>
-<% assigned_issues = Issue.find(:all,
- :conditions => ["assigned_to_id=? AND #{IssueStatus.table_name}.is_closed=? AND #{Project.table_name}.status=#{Project::STATUS_ACTIVE}", user.id, false],
+<% assigned_issues = Issue.visible.find(:all,
+ :conditions => ["assigned_to_id=? AND #{IssueStatus.table_name}.is_closed=?", user.id, false],
:limit => 10,
:include => [ :status, :project, :tracker, :priority ],
:order => "#{Enumeration.table_name}.position DESC, #{Issue.table_name}.updated_on DESC") %>
diff --git a/app/views/my/blocks/_issuesreportedbyme.rhtml b/app/views/my/blocks/_issuesreportedbyme.rhtml
index 317aebbfc..aa6e0e0eb 100644
--- a/app/views/my/blocks/_issuesreportedbyme.rhtml
+++ b/app/views/my/blocks/_issuesreportedbyme.rhtml
@@ -1,6 +1,6 @@
<h3><%=l(:label_reported_issues)%></h3>
-<% reported_issues = Issue.find(:all,
- :conditions => ["author_id=? AND #{Project.table_name}.status=#{Project::STATUS_ACTIVE}", user.id],
+<% reported_issues = Issue.visible.find(:all,
+ :conditions => { :author_id => user.id },
:limit => 10,
:include => [ :status, :project, :tracker ],
:order => "#{Issue.table_name}.updated_on DESC") %>
diff --git a/app/views/my/blocks/_issueswatched.rhtml b/app/views/my/blocks/_issueswatched.rhtml
index e5c2f23ab..22b911310 100644
--- a/app/views/my/blocks/_issueswatched.rhtml
+++ b/app/views/my/blocks/_issueswatched.rhtml
@@ -1,10 +1,10 @@
<h3><%=l(:label_watched_issues)%></h3>
-<% watched_issues = Issue.find(:all,
+<% watched_issues = Issue.visible.find(:all,
:include => [:status, :project, :tracker, :watchers],
:limit => 10,
- :conditions => ["#{Watcher.table_name}.user_id = ? AND #{Project.table_name}.status=#{Project::STATUS_ACTIVE}", user.id],
+ :conditions => ["#{Watcher.table_name}.user_id = ?", user.id],
:order => "#{Issue.table_name}.updated_on DESC") %>
<%= render :partial => 'issues/list_simple', :locals => { :issues => watched_issues } %>
<% if watched_issues.length > 0 %>
-<p><%=lwr(:label_last_updates, watched_issues.length)%></p>
+<p class="small"><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :set_filter => 1, :watcher_id => 'me' %></p>
<% end %>
diff --git a/app/views/my/blocks/_timelog.rhtml b/app/views/my/blocks/_timelog.rhtml
index a3f74e54d..ca66f7ee4 100644
--- a/app/views/my/blocks/_timelog.rhtml
+++ b/app/views/my/blocks/_timelog.rhtml
@@ -47,6 +47,6 @@ entries_by_day = entries.group_by(&:spent_on)
</tr>
<% end -%>
<% end -%>
-</tbdoy>
+</tbody>
</table>
<% end %>
diff --git a/app/views/news/index.rhtml b/app/views/news/index.rhtml
index 9cac39002..826894e08 100644
--- a/app/views/news/index.rhtml
+++ b/app/views/news/index.rhtml
@@ -2,7 +2,7 @@
<%= link_to_if_authorized(l(:label_news_new),
{:controller => 'news', :action => 'new', :project_id => @project},
:class => 'icon icon-add',
- :onclick => 'Element.show("add-news"); return false;') if @project %>
+ :onclick => 'Element.show("add-news"); Form.Element.focus("news_title"); return false;') if @project %>
</div>
<div id="add-news" style="display:none;">
@@ -39,10 +39,9 @@
<% end %>
<p class="pagination"><%= pagination_links_full @news_pages %></p>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'Atom', {:format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => {:project_id => @project, :key => User.current.rss_key} %>
+<% end %>
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :page => nil, :key => User.current.rss_key})) %>
diff --git a/app/views/news/show.rhtml b/app/views/news/show.rhtml
index 78be9c247..896492736 100644
--- a/app/views/news/show.rhtml
+++ b/app/views/news/show.rhtml
@@ -48,8 +48,10 @@
<% if authorize_for 'news', 'add_comment' %>
<p><%= toggle_link l(:label_comment_add), "add_comment_form", :focus => "comment_comments" %></p>
<% form_tag({:action => 'add_comment', :id => @news}, :id => "add_comment_form", :style => "display:none;") do %>
-<%= text_area 'comment', 'comments', :cols => 80, :rows => 15, :class => 'wiki-edit' %>
-<%= wikitoolbar_for 'comment_comments' %>
+<div class="box">
+ <%= text_area 'comment', 'comments', :cols => 80, :rows => 15, :class => 'wiki-edit' %>
+ <%= wikitoolbar_for 'comment_comments' %>
+</div>
<p><%= submit_tag l(:button_add) %></p>
<% end %>
<% end %>
diff --git a/app/views/projects/_form.rhtml b/app/views/projects/_form.rhtml
index 11f7e3933..24b1d9c1f 100644
--- a/app/views/projects/_form.rhtml
+++ b/app/views/projects/_form.rhtml
@@ -4,14 +4,14 @@
<!--[form:project]-->
<p><%= f.text_field :name, :required => true %><br /><em><%= l(:text_caracters_maximum, 30) %></em></p>
-<% if User.current.admin? and !@root_projects.empty? %>
- <p><%= f.select :parent_id, (@root_projects.collect {|p| [p.name, p.id]}), { :include_blank => true } %></p>
+<% if User.current.admin? && !@project.possible_parents.empty? %>
+ <p><label><%= l(:field_parent) %></label><%= parent_project_select_tag(@project) %></p>
<% end %>
<p><%= f.text_area :description, :rows => 5, :class => 'wiki-edit' %></p>
<p><%= f.text_field :identifier, :required => true, :disabled => @project.identifier_frozen? %>
<% unless @project.identifier_frozen? %>
-<br /><em><%= l(:text_length_between, 3, 20) %> <%= l(:text_project_identifier_info) %></em>
+<br /><em><%= l(:text_length_between, 2, 20) %> <%= l(:text_project_identifier_info) %></em>
<% end %></p>
<p><%= f.text_field :homepage, :size => 60 %></p>
<p><%= f.check_box :is_public %></p>
@@ -20,6 +20,7 @@
<% @project.custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :project, value %></p>
<% end %>
+<%= call_hook(:view_projects_form, :project => @project, :form => f) %>
</div>
<% unless @trackers.empty? %>
diff --git a/app/views/projects/activity.rhtml b/app/views/projects/activity.rhtml
index fa25812ac..5174cb33d 100644
--- a/app/views/projects/activity.rhtml
+++ b/app/views/projects/activity.rhtml
@@ -1,4 +1,4 @@
-<h2><%= l(:label_activity) %></h2>
+<h2><%= @author.nil? ? l(:label_activity) : l(:label_user_activity, link_to_user(@author)) %></h2>
<p class="subtitle"><%= "#{l(:label_date_from)} #{format_date(@date_to - @days)} #{l(:label_date_to).downcase} #{format_date(@date_to-1)}" %></p>
<div id="activity">
@@ -6,7 +6,8 @@
<h3><%= format_activity_day(day) %></h3>
<dl>
<% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%>
- <dt class="<%= e.event_type %> <%= User.current.logged? && e.respond_to?(:event_author) && User.current == e.event_author ? 'me' : nil %>">
+ <dt class="<%= e.event_type %> <%= User.current.logged? && e.respond_to?(:event_author) && User.current == e.event_author ? 'me' : nil %>">
+ <%= avatar(e.event_author, :size => "24") if e.respond_to?(:event_author) %>
<span class="time"><%= format_time(e.event_datetime, false) %></span>
<%= 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 %></dt>
@@ -21,24 +22,23 @@
<div style="float:left;">
<%= link_to_remote(('&#171; ' + l(:label_previous)),
- {:update => "content", :url => params.merge(:from => @date_to - @days), :complete => 'window.scrollTo(0,0)'},
- {:href => url_for(params.merge(:from => @date_to - @days)),
+ {:update => "content", :url => params.merge(:from => @date_to - @days - 1), :method => :get, :complete => 'window.scrollTo(0,0)'},
+ {:href => url_for(params.merge(:from => @date_to - @days - 1)),
:title => "#{l(:label_date_from)} #{format_date(@date_to - 2*@days)} #{l(:label_date_to).downcase} #{format_date(@date_to - @days - 1)}"}) %>
</div>
<div style="float:right;">
<%= link_to_remote((l(:label_next) + ' &#187;'),
- {:update => "content", :url => params.merge(:from => @date_to + @days), :complete => 'window.scrollTo(0,0)'},
- {:href => url_for(params.merge(:from => @date_to + @days)),
+ {:update => "content", :url => params.merge(:from => @date_to + @days - 1), :method => :get, :complete => 'window.scrollTo(0,0)'},
+ {:href => url_for(params.merge(:from => @date_to + @days - 1)),
:title => "#{l(:label_date_from)} #{format_date(@date_to)} #{l(:label_date_to).downcase} #{format_date(@date_to + @days - 1)}"}) unless @date_to >= Date.today %>
</div>
&nbsp;
-<p class="other-formats">
- <%= l(:label_export_to) %>
- <%= link_to 'Atom', params.merge(:format => :atom, :key => User.current.rss_key).delete_if{|k,v|k=="commit"}, :class => 'feed' %>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => params.merge(:from => nil, :key => User.current.rss_key) %>
+<% end %>
<% content_for :header_tags do %>
-<%= auto_discovery_link_tag(:atom, params.merge(:format => 'atom', :year => nil, :month => nil, :key => User.current.rss_key)) %>
+<%= auto_discovery_link_tag(:atom, params.merge(:format => 'atom', :from => nil, :key => User.current.rss_key)) %>
<% end %>
<% content_for :sidebar do %>
@@ -47,12 +47,13 @@
<p><% @activity.event_types.each do |t| %>
<label><%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> <%= l("label_#{t.singularize}_plural")%></label><br />
<% end %></p>
-<% if @project && @project.active_children.any? %>
+<% if @project && @project.descendants.active.any? %>
<p><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label></p>
<%= hidden_field_tag 'with_subprojects', 0 %>
<% end %>
+<%= hidden_field_tag('user_id', params[:user_id]) unless params[:user_id].blank? %>
<p><%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %></p>
<% end %>
<% end %>
-<% html_title(l(:label_activity)) -%>
+<% html_title(l(:label_activity), @author) -%>
diff --git a/app/views/projects/add.rhtml b/app/views/projects/add.rhtml
index bc3d7b09a..46ab1a9be 100644
--- a/app/views/projects/add.rhtml
+++ b/app/views/projects/add.rhtml
@@ -13,4 +13,5 @@
</fieldset>
<%= submit_tag l(:button_save) %>
+<%= javascript_tag "Form.Element.focus('project_name');" %>
<% end %>
diff --git a/app/views/projects/add_file.rhtml b/app/views/projects/add_file.rhtml
index 0ee55083d..ab9c7352d 100644
--- a/app/views/projects/add_file.rhtml
+++ b/app/views/projects/add_file.rhtml
@@ -4,10 +4,13 @@
<div class="box">
<% form_tag({ :action => 'add_file', :id => @project }, :multipart => true, :class => "tabular") do %>
-<p><label for="version_id"><%=l(:field_version)%> <span class="required">*</span></label>
-<%= select_tag "version_id", options_from_collection_for_select(@versions, "id", "name") %></p>
+<% if @versions.any? %>
+<p><label for="version_id"><%=l(:field_version)%></label>
+<%= select_tag "version_id", content_tag('option', '') +
+ options_from_collection_for_select(@versions, "id", "name") %></p>
+<% end %>
<p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
</div>
<%= submit_tag l(:button_add) %>
-<% end %> \ No newline at end of file
+<% end %>
diff --git a/app/views/projects/changelog.rhtml b/app/views/projects/changelog.rhtml
index e4d32a393..e44d1d1ee 100644
--- a/app/views/projects/changelog.rhtml
+++ b/app/views/projects/changelog.rhtml
@@ -26,7 +26,7 @@
<% end %>
<% content_for :sidebar do %>
-<% form_tag do %>
+<% form_tag({},:method => :get) do %>
<h3><%= l(:label_change_log) %></h3>
<% @trackers.each do |tracker| %>
<label><%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s) %>
diff --git a/app/views/projects/destroy.rhtml b/app/views/projects/destroy.rhtml
index a1913c115..09d7d2a1c 100644
--- a/app/views/projects/destroy.rhtml
+++ b/app/views/projects/destroy.rhtml
@@ -3,8 +3,8 @@
<p><strong><%=h @project_to_destroy %></strong><br />
<%=l(:text_project_destroy_confirmation)%>
-<% if @project_to_destroy.children.any? %>
-<br /><%= l(:text_subprojects_destroy_warning, content_tag('strong', h(@project_to_destroy.children.sort.collect{|p| p.to_s}.join(', ')))) %>
+<% if @project_to_destroy.descendants.any? %>
+<br /><%= l(:text_subprojects_destroy_warning, content_tag('strong', h(@project_to_destroy.descendants.collect{|p| p.to_s}.join(', ')))) %>
<% end %>
</p>
<p>
diff --git a/app/views/projects/index.rhtml b/app/views/projects/index.rhtml
index 4c68717f5..047d11ff5 100644
--- a/app/views/projects/index.rhtml
+++ b/app/views/projects/index.rhtml
@@ -6,26 +6,16 @@
<h2><%=l(:label_project_plural)%></h2>
-<% @project_tree.keys.sort.each do |project| %>
-<h3><%= link_to h(project.name), {:action => 'show', :id => project}, :class => (User.current.member_of?(project) ? "icon icon-fav" : "") %></h3>
-<%= textilizable(project.short_description, :project => project) %>
-
-<% if @project_tree[project].any? %>
- <p><%= 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(', ') %></p>
-<% end %>
-<% end %>
+<%= render_project_hierarchy(@projects)%>
<% if User.current.logged? %>
<p style="text-align:right;">
-<span class="icon icon-fav"><%= l(:label_my_projects) %></span>
+<span class="my-project"><%= l(:label_my_projects) %></span>
</p>
<% end %>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'Atom', {:format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
+<% end %>
<% html_title(l(:label_project_plural)) -%>
diff --git a/app/views/projects/list_files.rhtml b/app/views/projects/list_files.rhtml
index 79e41f16d..0871ba249 100644
--- a/app/views/projects/list_files.rhtml
+++ b/app/views/projects/list_files.rhtml
@@ -4,39 +4,37 @@
<h2><%=l(:label_attachment_plural)%></h2>
-<% delete_allowed = authorize_for('versions', 'destroy_file') %>
+<% delete_allowed = User.current.allowed_to?(:manage_files, @project) %>
<table class="list">
<thead><tr>
- <th><%=l(:field_version)%></th>
- <%= sort_header_tag("#{Attachment.table_name}.filename", :caption => l(:field_filename)) %>
- <%= sort_header_tag("#{Attachment.table_name}.created_on", :caption => l(:label_date), :default_order => 'desc') %>
- <%= sort_header_tag("#{Attachment.table_name}.filesize", :caption => l(:field_filesize), :default_order => 'desc') %>
- <%= sort_header_tag("#{Attachment.table_name}.downloads", :caption => l(:label_downloads_abbr), :default_order => 'desc') %>
+ <%= sort_header_tag('filename', :caption => l(:field_filename)) %>
+ <%= sort_header_tag('created_on', :caption => l(:label_date), :default_order => 'desc') %>
+ <%= sort_header_tag('size', :caption => l(:field_filesize), :default_order => 'desc') %>
+ <%= sort_header_tag('downloads', :caption => l(:label_downloads_abbr), :default_order => 'desc') %>
<th>MD5</th>
- <% if delete_allowed %><th></th><% end %>
+ <th></th>
</tr></thead>
<tbody>
-<% for version in @versions %>
- <% unless version.attachments.empty? %>
- <tr><th colspan="7" align="left"><span class="icon icon-package"><b><%= version.name %></b></span></th></tr>
- <% for file in version.attachments %>
+<% @containers.each do |container| %>
+ <% next if container.attachments.empty? -%>
+ <% if container.is_a?(Version) -%>
+ <tr><th colspan="6" align="left"><span class="icon icon-package"><b><%=h container %></b></span></th></tr>
+ <% end -%>
+ <% container.attachments.each do |file| %>
<tr class="<%= cycle("odd", "even") %>">
- <td></td>
<td><%= link_to_attachment file, :download => true, :title => file.description %></td>
<td align="center"><%= format_time(file.created_on) %></td>
<td align="center"><%= number_to_human_size(file.filesize) %></td>
<td align="center"><%= file.downloads %></td>
<td align="center"><small><%= file.digest %></small></td>
- <% if delete_allowed %>
<td align="center">
- <%= link_to_if_authorized image_tag('delete.png'), {:controller => 'versions', :action => 'destroy_file', :id => version, :attachment_id => file}, :confirm => l(:text_are_you_sure), :method => :post %>
+ <%= link_to(image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => file},
+ :confirm => l(:text_are_you_sure), :method => :post) if delete_allowed %>
</td>
- <% end %>
</tr>
<% end
reset_cycle %>
- <% end %>
<% end %>
</tbody>
</table>
diff --git a/app/views/projects/roadmap.rhtml b/app/views/projects/roadmap.rhtml
index 0778d8138..bcba13abd 100644
--- a/app/views/projects/roadmap.rhtml
+++ b/app/views/projects/roadmap.rhtml
@@ -25,6 +25,7 @@
</ul>
</fieldset>
<% end %>
+ <%= call_hook :view_projects_roadmap_version_bottom, :version => version %>
<% end %>
</div>
<% end %>
diff --git a/app/views/projects/settings/_members.rhtml b/app/views/projects/settings/_members.rhtml
index 67c3fb505..20806fe2d 100644
--- a/app/views/projects/settings/_members.rhtml
+++ b/app/views/projects/settings/_members.rhtml
@@ -1,6 +1,6 @@
<%= error_messages_for 'member' %>
<% roles = Role.find_all_givable %>
-<% users = User.find_active(:all).sort - @project.users %>
+<% users = User.active.find(:all).sort - @project.users %>
<% # members sorted by role position
members = @project.members.find(:all, :include => [:role, :user]).sort %>
diff --git a/app/views/projects/settings/_repository.rhtml b/app/views/projects/settings/_repository.rhtml
index dcfabbbf0..cdfb7feb4 100644
--- a/app/views/projects/settings/_repository.rhtml
+++ b/app/views/projects/settings/_repository.rhtml
@@ -11,10 +11,13 @@
</div>
<div class="contextual">
+<% if @repository && !@repository.new_record? %>
+<%= link_to(l(:label_user_plural), {:controller => 'repositories', :action => 'committers', :id => @project}, :class => 'icon icon-user') %>
<%= link_to(l(:button_delete), {:controller => 'repositories', :action => 'destroy', :id => @project},
:confirm => l(:text_are_you_sure),
:method => :post,
- :class => 'icon icon-del') if @repository && !@repository.new_record? %>
+ :class => 'icon icon-del') %>
+<% end %>
</div>
<%= submit_tag((@repository.nil? || @repository.new_record?) ? l(:button_create) : l(:button_save), :disabled => @repository.nil?) %>
diff --git a/app/views/projects/settings/_versions.rhtml b/app/views/projects/settings/_versions.rhtml
index 7329c7f3b..79d92d81e 100644
--- a/app/views/projects/settings/_versions.rhtml
+++ b/app/views/projects/settings/_versions.rhtml
@@ -17,7 +17,6 @@
<td><%= link_to(version.wiki_page_title, :controller => 'wiki', :page => Wiki.titleize(version.wiki_page_title)) unless version.wiki_page_title.blank? || @project.wiki.nil? %></td>
<td align="center"><%= link_to_if_authorized l(:button_edit), { :controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %></td>
<td align="center"><%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %></td>
- </td>
</tr>
<% end; reset_cycle %>
</tbody>
diff --git a/app/views/projects/show.rhtml b/app/views/projects/show.rhtml
index c1d8bb2c6..6d5a1536b 100644
--- a/app/views/projects/show.rhtml
+++ b/app/views/projects/show.rhtml
@@ -3,12 +3,14 @@
<div class="splitcontentleft">
<%= textilizable @project.description %>
<ul>
- <% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= auto_link(h(@project.homepage)) %></li><% end %>
- <% if @subprojects.any? %>
- <li><%=l(:label_subproject_plural)%>: <%= @subprojects.collect{|p| link_to(h(p.name), :action => 'show', :id => p)}.join(", ") %></li>
- <% end %>
- <% if @project.parent %>
- <li><%=l(:field_parent)%>: <%= link_to h(@project.parent.name), :controller => 'projects', :action => 'show', :id => @project.parent %></li>
+ <% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= link_to(h(@project.homepage), @project.homepage) %></li><% end %>
+ <% if @subprojects.any? %>
+ <li><%=l(:label_subproject_plural)%>:
+ <%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join(", ") %></li>
+ <% end %>
+ <% if @ancestors.any? %>
+ <li><%=l(:field_parent)%>:
+ <%= @ancestors.collect {|p| link_to(h(p), :action => 'show', :id => p)}.join(" &#187; ") %></li>
<% end %>
<% @project.custom_values.each do |custom_value| %>
<% if !custom_value.value.empty? %>
diff --git a/app/views/queries/_filters.rhtml b/app/views/queries/_filters.rhtml
index c9d612364..b25cd01aa 100644
--- a/app/views/queries/_filters.rhtml
+++ b/app/views/queries/_filters.rhtml
@@ -95,7 +95,10 @@ function toggle_multi_select(field) {
</td>
<td class="add-filter">
<%= l(:label_filter_add) %>:
-<%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [ field[1][:name] || l(("field_"+field[0].to_s.gsub(/\_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact), :onchange => "add_filter();", :class => "select-small" %>
+<%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [ field[1][:name] || l(("field_"+field[0].to_s.gsub(/_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact),
+ :onchange => "add_filter();",
+ :class => "select-small",
+ :name => nil %>
</td>
</tr>
</table>
diff --git a/app/views/repositories/_dir_list_content.rhtml b/app/views/repositories/_dir_list_content.rhtml
index 20473a264..12ee44dbf 100644
--- a/app/views/repositories/_dir_list_content.rhtml
+++ b/app/views/repositories/_dir_list_content.rhtml
@@ -15,10 +15,10 @@
:class => (entry.is_dir? ? 'icon icon-folder' : 'icon icon-file')%>
</td>
<td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
+<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %>
<td class="revision"><%= link_to(format_revision(entry.lastrev.name), :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %></td>
<td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
-<td class="author"><%=h(entry.lastrev.author.to_s.split('<').first) if entry.lastrev %></td>
-<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %>
+<td class="author"><%= changeset.nil? ? h(entry.lastrev.author.to_s.split('<').first) : changeset.author if entry.lastrev %></td>
<td class="comments"><%=h truncate(changeset.comments, 50) unless changeset.nil? %></td>
</tr>
<% end %>
diff --git a/app/views/repositories/_link_to_functions.rhtml b/app/views/repositories/_link_to_functions.rhtml
new file mode 100644
index 000000000..7737bf484
--- /dev/null
+++ b/app/views/repositories/_link_to_functions.rhtml
@@ -0,0 +1,10 @@
+<p>
+<% if @repository.supports_cat? %>
+ <%= link_to_if action_name != 'entry', l(:button_view), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
+<% end %>
+<% if @repository.supports_annotate? %>
+ <%= link_to_if action_name != 'annotate', 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 => to_path_param(@path), :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %>
+<%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
+</p>
diff --git a/app/views/repositories/_revisions.rhtml b/app/views/repositories/_revisions.rhtml
index a938fecb8..fb0131c82 100644
--- a/app/views/repositories/_revisions.rhtml
+++ b/app/views/repositories/_revisions.rhtml
@@ -17,8 +17,8 @@
<td class="checkbox"><%= radio_button_tag('rev', changeset.revision, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td>
<td class="checkbox"><%= radio_button_tag('rev_to', changeset.revision, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td>
<td class="committed_on"><%= format_time(changeset.committed_on) %></td>
-<td class="author"><%=h changeset.committer.to_s.split('<').first %></td>
-<td class="comments"><%= textilizable(changeset.comments) %></td>
+<td class="author"><%=h changeset.author %></td>
+<td class="comments"><%= textilizable(truncate_at_line_break(changeset.comments)) %></td>
</tr>
<% line_num += 1 %>
<% end %>
diff --git a/app/views/repositories/annotate.rhtml b/app/views/repositories/annotate.rhtml
index b5669ef76..44b5a81a6 100644
--- a/app/views/repositories/annotate.rhtml
+++ b/app/views/repositories/annotate.rhtml
@@ -1,5 +1,7 @@
<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
+<p><%= render :partial => 'link_to_functions' %></p>
+
<% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %>
<div class="autoscroll">
diff --git a/app/views/repositories/changes.rhtml b/app/views/repositories/changes.rhtml
index ca5c58328..aa359ef4d 100644
--- a/app/views/repositories/changes.rhtml
+++ b/app/views/repositories/changes.rhtml
@@ -1,15 +1,6 @@
<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
-<p>
-<% if @repository.supports_cat? %>
- <%= 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 => to_path_param(@path), :rev => @rev } %> |
-<% end %>
-<%= 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 %>
-</p>
+<p><%= render :partial => 'link_to_functions' %></p>
<%= render_properties(@properties) %>
diff --git a/app/views/repositories/committers.rhtml b/app/views/repositories/committers.rhtml
new file mode 100644
index 000000000..597a414aa
--- /dev/null
+++ b/app/views/repositories/committers.rhtml
@@ -0,0 +1,34 @@
+<h2><%= l(:label_repository) %></h2>
+
+<%= simple_format(l(:text_repository_usernames_mapping)) %>
+
+<% if @committers.empty? %>
+<p class="nodata"><%= l(:label_no_data) %></p>
+<% else %>
+
+<% form_tag({}) do %>
+<table class="list">
+<thead>
+ <tr>
+ <th><%= l(:field_login) %></th>
+ <th><%= l(:label_user) %></th>
+ </tr>
+</thead>
+<tbody>
+<% i = 0 -%>
+<% @committers.each do |committer, user_id| -%>
+ <tr class="<%= cycle 'odd', 'even' %>">
+ <td><%=h committer %></td>
+ <td>
+ <%= hidden_field_tag "committers[#{i}][]", committer %>
+ <%= select_tag "committers[#{i}][]", content_tag('option', "-- #{l :actionview_instancetag_blank_option} --", :value => '') + options_from_collection_for_select(@users, 'id', 'name', user_id.to_i) %>
+ </td>
+ </tr>
+ <% i += 1 -%>
+<% end -%>
+</tbody>
+</table>
+<p><%= submit_tag(l(:button_update)) %></p>
+<% end %>
+
+<% end %> \ No newline at end of file
diff --git a/app/views/repositories/diff.rhtml b/app/views/repositories/diff.rhtml
index 52a5d6057..8538f30de 100644
--- a/app/views/repositories/diff.rhtml
+++ b/app/views/repositories/diff.rhtml
@@ -1,12 +1,9 @@
<h2><%= l(:label_revision) %> <%= format_revision(@rev) %> <%= @path.gsub(/^.*\//, '') %></h2>
<!-- Choose view type -->
-<% form_tag({ :controller => 'repositories', :action => 'diff'}, :method => 'get') do %>
- <% params.each do |k, p| %>
- <% if k != "type" %>
- <%= hidden_field_tag(k,p) %>
- <% end %>
- <% end %>
+<% form_tag({}, :method => 'get') do %>
+ <%= hidden_field_tag('rev', params[:rev]) if params[:rev] %>
+ <%= hidden_field_tag('rev_to', params[:rev_to]) if params[:rev_to] %>
<p><label><%= l(:label_view_diff) %></label>
<%= 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()}" %></p>
<% end %>
@@ -15,10 +12,9 @@
<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type} %>
<% end -%>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'Unified diff', params.merge(:format => 'diff') %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Diff', :url => params, :caption => 'Unified diff' %>
+<% end %>
<% html_title(with_leading_slash(@path), 'Diff') -%>
diff --git a/app/views/repositories/entry.rhtml b/app/views/repositories/entry.rhtml
index 8e1e1992c..12ba9f428 100644
--- a/app/views/repositories/entry.rhtml
+++ b/app/views/repositories/entry.rhtml
@@ -1,5 +1,7 @@
<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
+<p><%= render :partial => 'link_to_functions' %></p>
+
<%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %>
<% content_for :header_tags do %>
diff --git a/app/views/repositories/revision.rhtml b/app/views/repositories/revision.rhtml
index 123fccf26..c5bac720b 100644
--- a/app/views/repositories/revision.rhtml
+++ b/app/views/repositories/revision.rhtml
@@ -22,7 +22,7 @@
<h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2>
<p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
-<em><%= @changeset.committer.to_s.split('<').first %>, <%= format_time(@changeset.committed_on) %></em></p>
+<span class="author"><%= authoring(@changeset.committed_on, @changeset.author) %></span></p>
<%= textilizable @changeset.comments %>
diff --git a/app/views/repositories/revisions.rhtml b/app/views/repositories/revisions.rhtml
index 8da7d582d..c06c204cd 100644
--- a/app/views/repositories/revisions.rhtml
+++ b/app/views/repositories/revisions.rhtml
@@ -16,9 +16,8 @@
<%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :page => nil, :key => User.current.rss_key})) %>
<% end %>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'Atom', {:format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
+<% end %>
<% html_title(l(:label_revision_plural)) -%>
diff --git a/app/views/repositories/show.rhtml b/app/views/repositories/show.rhtml
index 9dc38a832..943fe9485 100644
--- a/app/views/repositories/show.rhtml
+++ b/app/views/repositories/show.rhtml
@@ -1,4 +1,5 @@
<div class="contextual">
+<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %>
<%= link_to l(:label_statistics), {:action => 'stats', :id => @project}, :class => 'icon icon-stats' %>
<% if !@entries.nil? && authorize_for('repositories', 'browse') -%>
@@ -21,10 +22,10 @@
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :action => 'revisions', :id => @project, :page => nil, :key => User.current.rss_key})) %>
<% end %>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'Atom', {:action => 'revisions', :id => @project, :format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
-</p>
+
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => {:action => 'revisions', :id => @project, :key => User.current.rss_key} %>
+<% end %>
<% end %>
<% content_for :header_tags do %>
diff --git a/app/views/roles/_form.rhtml b/app/views/roles/_form.rhtml
index 4aad45471..a167907e8 100644
--- a/app/views/roles/_form.rhtml
+++ b/app/views/roles/_form.rhtml
@@ -15,11 +15,11 @@
<div class="box" id="permissions">
<% perms_by_module = @permissions.group_by {|p| p.project_module.to_s} %>
<% perms_by_module.keys.sort.each do |mod| %>
- <fieldset><legend><%= mod.blank? ? l(:label_project) : mod.humanize %></legend>
+ <fieldset><legend><%= mod.blank? ? l(:label_project) : l_or_humanize(mod, :prefix => 'project_module_') %></legend>
<% perms_by_module[mod].each do |permission| %>
<label class="floating">
<%= check_box_tag 'role[permissions][]', permission.name, (@role.permissions.include? permission.name) %>
- <%= permission.name.to_s.humanize %>
+ <%= l_or_humanize(permission.name, :prefix => 'permission_') %>
</label>
<% end %>
</fieldset>
diff --git a/app/views/roles/report.rhtml b/app/views/roles/report.rhtml
index 8e254379e..f5ebc8845 100644
--- a/app/views/roles/report.rhtml
+++ b/app/views/roles/report.rhtml
@@ -19,14 +19,14 @@
<% perms_by_module = @permissions.group_by {|p| p.project_module.to_s} %>
<% perms_by_module.keys.sort.each do |mod| %>
<% unless mod.blank? %>
- <tr><%= content_tag('th', mod.humanize, :colspan => (@roles.size + 1), :align => 'left') %></tr>
+ <tr><%= content_tag('th', l_or_humanize(mod, :prefix => 'project_module_'), :colspan => (@roles.size + 1), :align => 'left') %></tr>
<% end %>
<% perms_by_module[mod].each do |permission| %>
<tr class="<%= cycle('odd', 'even') %> permission-<%= permission.name %>">
<td>
<%= 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 %>
+ <%= l_or_humanize(permission.name, :prefix => 'permission_') %>
</td>
<% @roles.each do |role| %>
<td align="center">
diff --git a/app/views/settings/_authentication.rhtml b/app/views/settings/_authentication.rhtml
index 6bf20cbce..a3ac555ed 100644
--- a/app/views/settings/_authentication.rhtml
+++ b/app/views/settings/_authentication.rhtml
@@ -17,6 +17,9 @@
<p><label><%= l(:label_password_lost) %></label>
<%= check_box_tag 'settings[lost_password]', 1, Setting.lost_password? %><%= hidden_field_tag 'settings[lost_password]', 0 %></p>
+
+<p><label><%= l(:setting_openid) %></label>
+<%= check_box_tag 'settings[openid]', 1, Setting.openid?, :disabled => !Object.const_defined?(:OpenID) %><%= hidden_field_tag 'settings[openid]', 0 %></p>
</div>
<div style="float:right;">
diff --git a/app/views/settings/_display.rhtml b/app/views/settings/_display.rhtml
new file mode 100644
index 000000000..a14ff47ce
--- /dev/null
+++ b/app/views/settings/_display.rhtml
@@ -0,0 +1,24 @@
+<% form_tag({:action => 'edit', :tab => 'display'}) do %>
+
+<div class="box tabular settings">
+<p><label><%= l(:label_theme) %></label>
+<%= select_tag 'settings[ui_theme]', options_for_select( ([[l(:label_default), '']] + Redmine::Themes.themes.collect {|t| [t.name, t.id]}), Setting.ui_theme) %></p>
+
+<p><label><%= l(:setting_default_language) %></label>
+<%= select_tag 'settings[default_language]', options_for_select( lang_options_for_select(false), Setting.default_language) %></p>
+
+<p><label><%= l(:setting_date_format) %></label>
+<%= select_tag 'settings[date_format]', options_for_select( [[l(:label_language_based), '']] + Setting::DATE_FORMATS.collect {|f| [Date.today.strftime(f), f]}, Setting.date_format) %></p>
+
+<p><label><%= l(:setting_time_format) %></label>
+<%= select_tag 'settings[time_format]', options_for_select( [[l(:label_language_based), '']] + Setting::TIME_FORMATS.collect {|f| [Time.now.strftime(f), f]}, Setting.time_format) %></p>
+
+<p><label><%= l(:setting_user_format) %></label>
+<%= select_tag 'settings[user_format]', options_for_select( @options[:user_format], Setting.user_format.to_s ) %></p>
+
+<p><label><%= l(:setting_gravatar_enabled) %></label>
+<%= check_box_tag 'settings[gravatar_enabled]', 1, Setting.gravatar_enabled? %><%= hidden_field_tag 'settings[gravatar_enabled]', 0 %></p>
+</div>
+
+<%= submit_tag l(:button_save) %>
+<% end %>
diff --git a/app/views/settings/_general.rhtml b/app/views/settings/_general.rhtml
index bb56c43db..e78667154 100644
--- a/app/views/settings/_general.rhtml
+++ b/app/views/settings/_general.rhtml
@@ -8,21 +8,6 @@
<%= text_area_tag 'settings[welcome_text]', Setting.welcome_text, :cols => 60, :rows => 5, :class => 'wiki-edit' %></p>
<%= wikitoolbar_for 'settings[welcome_text]' %>
-<p><label><%= l(:label_theme) %></label>
-<%= select_tag 'settings[ui_theme]', options_for_select( ([[l(:label_default), '']] + Redmine::Themes.themes.collect {|t| [t.name, t.id]}), Setting.ui_theme) %></p>
-
-<p><label><%= l(:setting_default_language) %></label>
-<%= select_tag 'settings[default_language]', options_for_select( lang_options_for_select(false), Setting.default_language) %></p>
-
-<p><label><%= l(:setting_date_format) %></label>
-<%= select_tag 'settings[date_format]', options_for_select( [[l(:label_language_based), '']] + Setting::DATE_FORMATS.collect {|f| [Date.today.strftime(f), f]}, Setting.date_format) %></p>
-
-<p><label><%= l(:setting_time_format) %></label>
-<%= select_tag 'settings[time_format]', options_for_select( [[l(:label_language_based), '']] + Setting::TIME_FORMATS.collect {|f| [Time.now.strftime(f), f]}, Setting.time_format) %></p>
-
-<p><label><%= l(:setting_user_format) %></label>
-<%= select_tag 'settings[user_format]', options_for_select( @options[:user_format], Setting.user_format.to_s ) %></p>
-
<p><label><%= l(:setting_attachment_max_size) %></label>
<%= text_field_tag 'settings[attachment_max_size]', Setting.attachment_max_size, :size => 6 %> KB</p>
@@ -33,19 +18,23 @@
<%= text_field_tag 'settings[activity_days_default]', Setting.activity_days_default, :size => 6 %> <%= l(:label_day_plural) %></p>
<p><label><%= l(:setting_host_name) %></label>
-<%= text_field_tag 'settings[host_name]', Setting.host_name, :size => 60 %></p>
+<%= text_field_tag 'settings[host_name]', Setting.host_name, :size => 60 %><br />
+<em><%= l(:label_example) %>: <%= @guessed_host_and_path %></em></p>
<p><label><%= l(:setting_protocol) %></label>
<%= select_tag 'settings[protocol]', options_for_select(['http', 'https'], Setting.protocol) %></p>
<p><label><%= l(:setting_text_formatting) %></label>
-<%= select_tag 'settings[text_formatting]', options_for_select([[l(:label_none), "0"], ["textile", "textile"]], Setting.text_formatting) %></p>
+<%= select_tag 'settings[text_formatting]', options_for_select([[l(:label_none), "0"], *Redmine::WikiFormatting.format_names.collect{|name| [name, name]} ], Setting.text_formatting.to_sym) %></p>
<p><label><%= l(:setting_wiki_compression) %></label>
<%= select_tag 'settings[wiki_compression]', options_for_select( [[l(:label_none), 0], ["gzip", "gzip"]], Setting.wiki_compression) %></p>
<p><label><%= l(:setting_feeds_limit) %></label>
<%= text_field_tag 'settings[feeds_limit]', Setting.feeds_limit, :size => 6 %></p>
+
+<p><label><%= l(:setting_diff_max_lines_displayed) %></label>
+<%= text_field_tag 'settings[diff_max_lines_displayed]', Setting.diff_max_lines_displayed, :size => 6 %></p>
</div>
<%= submit_tag l(:button_save) %>
diff --git a/app/views/settings/_notifications.rhtml b/app/views/settings/_notifications.rhtml
index 36701463a..bc0141187 100644
--- a/app/views/settings/_notifications.rhtml
+++ b/app/views/settings/_notifications.rhtml
@@ -8,12 +8,16 @@
<p><label><%= l(:setting_bcc_recipients) %></label>
<%= check_box_tag 'settings[bcc_recipients]', 1, Setting.bcc_recipients? %>
<%= hidden_field_tag 'settings[bcc_recipients]', 0 %></p>
+
+<p><label><%= l(:setting_plain_text_mail) %></label>
+<%= check_box_tag 'settings[plain_text_mail]', 1, Setting.plain_text_mail? %>
+<%= hidden_field_tag 'settings[plain_text_mail]', 0 %></p>
</div>
<fieldset class="box" id="notified_events"><legend><%=l(:text_select_mail_notifications)%></legend>
<% @notifiables.each do |notifiable| %>
<label><%= check_box_tag 'settings[notified_events][]', notifiable, Setting.notified_events.include?(notifiable) %>
- <%= l_or_humanize(notifiable) %></label><br />
+ <%= l_or_humanize(notifiable, :prefix => 'label_') %></label><br />
<% end %>
<%= hidden_field_tag 'settings[notified_events][]', '' %>
<p><%= check_all_links('notified_events') %></p>
diff --git a/app/views/settings/_repositories.rhtml b/app/views/settings/_repositories.rhtml
index fb4e88f4c..dfb9d6454 100644
--- a/app/views/settings/_repositories.rhtml
+++ b/app/views/settings/_repositories.rhtml
@@ -22,6 +22,9 @@
<p><label><%= l(:setting_commit_logs_encoding) %></label>
<%= select_tag 'settings[commit_logs_encoding]', options_for_select(Setting::ENCODINGS, Setting.commit_logs_encoding) %></p>
+
+<p><label><%= l(:setting_repository_log_display_limit) %></label>
+<%= text_field_tag 'settings[repository_log_display_limit]', Setting.repository_log_display_limit, :size => 6 %></p>
</div>
<fieldset class="box tabular settings"><legend><%= l(:text_issues_ref_in_commit_messages) %></legend>
diff --git a/app/views/timelog/_list.rhtml b/app/views/timelog/_list.rhtml
index 8aebd75de..1144d42cc 100644
--- a/app/views/timelog/_list.rhtml
+++ b/app/views/timelog/_list.rhtml
@@ -2,10 +2,10 @@
<thead>
<tr>
<%= sort_header_tag('spent_on', :caption => l(:label_date), :default_order => 'desc') %>
-<%= sort_header_tag('user_id', :caption => l(:label_member)) %>
-<%= sort_header_tag('activity_id', :caption => l(:label_activity)) %>
-<%= sort_header_tag("#{Project.table_name}.name", :caption => l(:label_project)) %>
-<%= sort_header_tag('issue_id', :caption => l(:label_issue), :default_order => 'desc') %>
+<%= sort_header_tag('user', :caption => l(:label_member)) %>
+<%= sort_header_tag('activity', :caption => l(:label_activity)) %>
+<%= sort_header_tag('project', :caption => l(:label_project)) %>
+<%= sort_header_tag('issue', :caption => l(:label_issue), :default_order => 'desc') %>
<th><%= l(:field_comments) %></th>
<%= sort_header_tag('hours', :caption => l(:field_hours)) %>
<th></th>
diff --git a/app/views/timelog/details.rhtml b/app/views/timelog/details.rhtml
index db62fae66..f4ae68aa9 100644
--- a/app/views/timelog/details.rhtml
+++ b/app/views/timelog/details.rhtml
@@ -1,12 +1,14 @@
<div class="contextual">
-<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>
+<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time-add' %>
</div>
<%= render_timelog_breadcrumb %>
<h2><%= l(:label_spent_time) %></h2>
-<% form_remote_tag( :url => {}, :method => :get, :update => 'content' ) do %>
+<% form_remote_tag( :url => {}, :html => {:method => :get}, :method => :get, :update => 'content' ) do %>
+<%# TOOD: remove the project_id and issue_id hidden fields, that information is
+already in the URI %>
<%= hidden_field_tag 'project_id', params[:project_id] %>
<%= hidden_field_tag 'issue_id', params[:issue_id] if @issue %>
<%= render :partial => 'date_range' %>
@@ -20,11 +22,10 @@
<%= render :partial => 'list', :locals => { :entries => @entries }%>
<p class="pagination"><%= pagination_links_full @entry_pages, @entry_count %></p>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'Atom', {:issue_id => @issue, :format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
-<span><%= link_to 'CSV', params.merge(:format => 'csv'), :class => 'csv' %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => params.merge({:issue_id => @issue, :key => User.current.rss_key}) %>
+ <%= f.link_to 'CSV', :url => params %>
+<% end %>
<% end %>
<% html_title l(:label_spent_time), l(:label_details) %>
diff --git a/app/views/timelog/edit.rhtml b/app/views/timelog/edit.rhtml
index 0dd3503ec..085f3d805 100644
--- a/app/views/timelog/edit.rhtml
+++ b/app/views/timelog/edit.rhtml
@@ -1,6 +1,6 @@
<h2><%= l(:label_spent_time) %></h2>
-<% labelled_tabular_form_for :time_entry, @time_entry, :url => {:action => 'edit', :project_id => @time_entry.project} do |f| %>
+<% labelled_tabular_form_for :time_entry, @time_entry, :url => {:action => 'edit', :id => @time_entry, :project_id => @time_entry.project} do |f| %>
<%= error_messages_for 'time_entry' %>
<%= back_url_hidden_field_tag %>
@@ -13,6 +13,7 @@
<% @time_entry.custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :time_entry, value %></p>
<% end %>
+<%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %>
</div>
<%= submit_tag l(:button_save) %>
diff --git a/app/views/timelog/report.rhtml b/app/views/timelog/report.rhtml
index eea0d0fc7..5f3ed09bd 100644
--- a/app/views/timelog/report.rhtml
+++ b/app/views/timelog/report.rhtml
@@ -1,15 +1,16 @@
<div class="contextual">
-<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>
+<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time-add' %>
</div>
<%= render_timelog_breadcrumb %>
<h2><%= l(:label_spent_time) %></h2>
-<% form_remote_tag(:url => {}, :update => 'content') do %>
+<% form_remote_tag(:url => {}, :html => {:method => :get}, :method => :get, :update => 'content') do %>
<% @criterias.each do |criteria| %>
<%= hidden_field_tag 'criterias[]', criteria, :id => nil %>
<% end %>
+ <%# TODO: get rid of the project_id field, that should already be in the URL %>
<%= hidden_field_tag 'project_id', params[:project_id] %>
<%= render :partial => 'date_range' %>
@@ -25,6 +26,7 @@
:id => nil,
:disabled => (@criterias.length >= 3)) %>
<%= link_to_remote l(:button_clear), {:url => {:project_id => @project, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @columns},
+ :method => :get,
:update => 'content'
}, :class => 'icon icon-reload' %></p>
<% end %>
@@ -63,10 +65,9 @@
</tbody>
</table>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'CSV', params.merge({:format => 'csv'}), :class => 'csv' %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'CSV', :url => params %>
+<% end %>
<% end %>
<% end %>
diff --git a/app/views/trackers/_form.rhtml b/app/views/trackers/_form.rhtml
index 856b70bbc..d8d35ba36 100644
--- a/app/views/trackers/_form.rhtml
+++ b/app/views/trackers/_form.rhtml
@@ -1,5 +1,7 @@
<%= error_messages_for 'tracker' %>
-<div class="box">
+
+<div class="splitcontentleft">
+<div class="box tabular">
<!--[form:tracker]-->
<p><%= f.text_field :name, :required => true %></p>
<p><%= f.check_box :is_in_chlog %></p>
@@ -10,3 +12,16 @@
<% end %>
<!--[eoform:tracker]-->
</div>
+</div>
+
+<div class="splitcontentright">
+<% if @projects.any? %>
+<fieldset class="box" id="tracker_project_ids"><legend><%= l(:label_project_plural) %></legend>
+<%= project_nested_ul(@projects) do |p|
+ content_tag('label', check_box_tag('tracker[project_ids][]', p.id, @tracker.projects.include?(p), :id => nil) + ' ' + h(p))
+end %>
+<%= hidden_field_tag('tracker[project_ids][]', '', :id => nil) %>
+<p><%= check_all_links 'tracker_project_ids' %></p>
+</fieldset>
+<% end %>
+</div>
diff --git a/app/views/trackers/edit.rhtml b/app/views/trackers/edit.rhtml
index d8411099c..038acc86e 100644
--- a/app/views/trackers/edit.rhtml
+++ b/app/views/trackers/edit.rhtml
@@ -1,6 +1,6 @@
<h2><%=l(:label_tracker)%></h2>
-<% labelled_tabular_form_for :tracker, @tracker, :url => { :action => 'edit' } do |f| %>
+<% form_for :tracker, @tracker, :url => { :action => 'edit' }, :builder => TabularFormBuilder do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
-<% end %> \ No newline at end of file
+<% end %>
diff --git a/app/views/trackers/list.rhtml b/app/views/trackers/list.rhtml
index 9ccfda386..2531f334b 100644
--- a/app/views/trackers/list.rhtml
+++ b/app/views/trackers/list.rhtml
@@ -15,7 +15,7 @@
<% for tracker in @trackers %>
<tr class="<%= cycle("odd", "even") %>">
<td><%= link_to tracker.name, :action => 'edit', :id => tracker %></td>
- <td align="center"><% unless tracker.workflows.count > 0 %><span class="icon icon-warning"><%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), {:controller => "roles", :action => "workflow", :tracker_id => tracker} %>)</span><% end %></td>
+ <td align="center"><% unless tracker.workflows.count > 0 %><span class="icon icon-warning"><%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), {:controller => 'workflows', :action => 'edit', :tracker_id => tracker} %>)</span><% end %></td>
<td align="center" style="width:15%;">
<%= link_to image_tag('2uparrow.png', :alt => l(:label_sort_highest)), {:action => 'move', :id => tracker, :position => 'highest'}, :method => :post, :title => l(:label_sort_highest) %>
<%= link_to image_tag('1uparrow.png', :alt => l(:label_sort_higher)), {:action => 'move', :id => tracker, :position => 'higher'}, :method => :post, :title => l(:label_sort_higher) %> -
diff --git a/app/views/trackers/new.rhtml b/app/views/trackers/new.rhtml
index b318a5dc4..3f8384cdd 100644
--- a/app/views/trackers/new.rhtml
+++ b/app/views/trackers/new.rhtml
@@ -1,6 +1,6 @@
<h2><%=l(:label_tracker_new)%></h2>
-<% labelled_tabular_form_for :tracker, @tracker, :url => { :action => 'new' } do |f| %>
+<% form_for :tracker, @tracker, :url => { :action => 'new' }, :builder => TabularFormBuilder do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_create) %>
-<% end %> \ No newline at end of file
+<% end %>
diff --git a/app/views/users/_form.rhtml b/app/views/users/_form.rhtml
index 799ebde47..00b6aeac5 100644
--- a/app/views/users/_form.rhtml
+++ b/app/views/users/_form.rhtml
@@ -7,6 +7,9 @@
<p><%= f.text_field :lastname, :required => true %></p>
<p><%= f.text_field :mail, :required => true %></p>
<p><%= f.select :language, lang_options_for_select %></p>
+<% if Setting.openid? %>
+<p><%= f.text_field :identity_url %></p>
+<% end %>
<% @user.custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :user, value %></p>
diff --git a/app/views/users/_general.rhtml b/app/views/users/_general.rhtml
index 80615ff6c..352c002d0 100644
--- a/app/views/users/_general.rhtml
+++ b/app/views/users/_general.rhtml
@@ -1,4 +1,4 @@
-<% labelled_tabular_form_for :user, @user, :url => { :action => "edit" } do |f| %>
+<% labelled_tabular_form_for :user, @user, :url => { :action => "edit", :tab => nil } do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
<% end %>
diff --git a/app/views/users/_memberships.rhtml b/app/views/users/_memberships.rhtml
index 94b49159e..d1657fb98 100644
--- a/app/views/users/_memberships.rhtml
+++ b/app/views/users/_memberships.rhtml
@@ -31,7 +31,7 @@
<p>
<label><%=l(:label_project_new)%></label><br/>
<% form_tag({ :action => 'edit_membership', :id => @user }) do %>
-<%= select_tag 'membership[project_id]', projects_options_for_select(@projects) %>
+<%= select_tag 'membership[project_id]', options_for_membership_project_select(@user, @projects) %>
<%= l(:label_role) %>:
<%= select_tag 'membership[role_id]', options_from_collection_for_select(@roles, "id", "name") %>
<%= submit_tag l(:button_add) %>
diff --git a/app/views/users/list.rhtml b/app/views/users/list.rhtml
index 77d45a6bb..758f1767a 100644
--- a/app/views/users/list.rhtml
+++ b/app/views/users/list.rhtml
@@ -6,8 +6,11 @@
<% form_tag({}, :method => :get) do %>
<fieldset><legend><%= l(:label_filter_plural) %></legend>
-<label><%= l(:field_status) %> :</label>
+<label><%= l(:field_status) %>:</label>
<%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %>
+<label><%= l(:label_user) %>:</label>
+<%= text_field_tag 'name', params[:name], :size => 30 %>
+<%= submit_tag l(:button_apply), :class => "small", :name => nil %>
</fieldset>
<% end %>
&nbsp;
@@ -26,7 +29,7 @@
<tbody>
<% for user in @users -%>
<tr class="user <%= cycle("odd", "even") %> <%= %w(anon active registered locked)[user.status] %>">
- <td class="username"><%= link_to h(user.login), :action => 'edit', :id => user %></td>
+ <td class="username"><%= avatar(user, :size => "14") %><%= link_to h(user.login), :action => 'edit', :id => user %></td>
<td class="firstname"><%= h(user.firstname) %></td>
<td class="lastname"><%= h(user.lastname) %></td>
<td class="email"><%= mail_to(h(user.mail)) %></td>
diff --git a/app/views/welcome/robots.rhtml b/app/views/welcome/robots.rhtml
new file mode 100644
index 000000000..c6e206bd6
--- /dev/null
+++ b/app/views/welcome/robots.rhtml
@@ -0,0 +1,9 @@
+User-agent: *
+<% @projects.each do |p| -%>
+Disallow: /projects/<%= p.to_param %>/repository
+Disallow: /projects/<%= p.to_param %>/issues
+Disallow: /projects/<%= p.to_param %>/activity
+<% end -%>
+Disallow: /issues/gantt
+Disallow: /issues/calendar
+Disallow: /activity
diff --git a/app/views/wiki/annotate.rhtml b/app/views/wiki/annotate.rhtml
index 1c683404b..c27451606 100644
--- a/app/views/wiki/annotate.rhtml
+++ b/app/views/wiki/annotate.rhtml
@@ -20,7 +20,7 @@
<th class="line-num"><%= line_num %></th>
<td class="revision"><%= link_to line[0], :controller => 'wiki', :action => 'index', :id => @project, :page => @page.title, :version => line[0] %></td>
<td class="author"><%= h(line[1]) %></td>
- <td class="line-code"><pre><%= line[2] %></pre></td>
+ <td class="line-code"><pre><%=h line[2] %></pre></td>
</tr>
<% line_num += 1 %>
<% end -%>
diff --git a/app/views/wiki/export.rhtml b/app/views/wiki/export.rhtml
index 94b4e6f0d..7f861facf 100644
--- a/app/views/wiki/export.rhtml
+++ b/app/views/wiki/export.rhtml
@@ -5,7 +5,7 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<style>
body { font:80% Verdana,Tahoma,Arial,sans-serif; }
-h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; }
+h1, h2, h3, h4 { font-family: "Trebuchet MS",Georgia,"Times New Roman",serif; }
ul.toc { padding: 4px; margin-left: 0; }
ul.toc li { list-style-type:none; }
ul.toc li.heading2 { margin-left: 1em; }
diff --git a/app/views/wiki/export_multiple.rhtml b/app/views/wiki/export_multiple.rhtml
index 6f6c603ad..a4e4c5e21 100644
--- a/app/views/wiki/export_multiple.rhtml
+++ b/app/views/wiki/export_multiple.rhtml
@@ -5,7 +5,7 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<style>
body { font:80% Verdana,Tahoma,Arial,sans-serif; }
-h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; }
+h1, h2, h3, h4 { font-family: "Trebuchet MS",Georgia,"Times New Roman",serif; }
</style>
</head>
<body>
diff --git a/app/views/wiki/show.rhtml b/app/views/wiki/show.rhtml
index 844c6c0f8..7abfd8a28 100644
--- a/app/views/wiki/show.rhtml
+++ b/app/views/wiki/show.rhtml
@@ -28,7 +28,7 @@
<%= render(:partial => "wiki/content", :locals => {:content => @content}) %>
-<%= link_to_attachments @page.attachments, :delete_url => ((@editable && authorize_for('wiki', 'destroy_attachment')) ? {:controller => 'wiki', :action => 'destroy_attachment', :page => @page.title} : nil) %>
+<%= link_to_attachments @page %>
<% if @editable && authorize_for('wiki', 'add_attachment') %>
<p><%= link_to l(:label_attachment_new), {}, :onclick => "Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;",
@@ -42,11 +42,10 @@
<% end %>
<% end %>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'HTML', {:page => @page.title, :export => 'html', :version => @content.version}, :class => 'html' %></span>
-<span><%= link_to 'TXT', {:page => @page.title, :export => 'txt', :version => @content.version}, :class => 'text' %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'HTML', :url => {:page => @page.title, :version => @content.version} %>
+ <%= f.link_to 'TXT', :url => {:page => @page.title, :version => @content.version} %>
+<% end %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag 'scm' %>
diff --git a/app/views/wiki/special_date_index.rhtml b/app/views/wiki/special_date_index.rhtml
index 6717ebc85..008f89293 100644
--- a/app/views/wiki/special_date_index.rhtml
+++ b/app/views/wiki/special_date_index.rhtml
@@ -18,11 +18,10 @@
<% end %>
<% unless @pages.empty? %>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'Atom', {:controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
-<span><%= link_to 'HTML', {:action => 'special', :page => 'export'}, :class => 'html' %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :key => User.current.rss_key} %>
+ <%= f.link_to 'HTML', :url => {:action => 'special', :page => 'export'} %>
+<% end %>
<% end %>
<% content_for :header_tags do %>
diff --git a/app/views/wiki/special_page_index.rhtml b/app/views/wiki/special_page_index.rhtml
index 72b395ef7..5547f5b1c 100644
--- a/app/views/wiki/special_page_index.rhtml
+++ b/app/views/wiki/special_page_index.rhtml
@@ -11,11 +11,10 @@
<% end %>
<% unless @pages.empty? %>
-<p class="other-formats">
-<%= l(:label_export_to) %>
-<span><%= link_to 'Atom', {:controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
-<span><%= link_to 'HTML', {:action => 'special', :page => 'export'} %></span>
-</p>
+<% other_formats_links do |f| %>
+ <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => @project, :show_wiki_pages => 1, :key => User.current.rss_key} %>
+ <%= f.link_to 'HTML', :url => {:action => 'special', :page => 'export'} %>
+<% end %>
<% end %>
<% content_for :header_tags do %>
diff --git a/app/views/roles/workflow.rhtml b/app/views/workflows/edit.rhtml
index 0f08b0d22..1aef8eb7e 100644
--- a/app/views/roles/workflow.rhtml
+++ b/app/views/workflows/edit.rhtml
@@ -1,40 +1,47 @@
+<div class="contextual">
+<%= link_to l(:field_summary), :action => 'index' %>
+</div>
+
<h2><%=l(:label_workflow)%></h2>
<p><%=l(:text_workflow_edit)%>:</p>
-<% form_tag({:action => 'workflow'}, :method => 'get') do %>
+<% form_tag({}, :method => 'get') do %>
<p><label for="role_id"><%=l(:label_role)%>:</label>
-<select id="role_id" name="role_id">
+<select name="role_id">
<%= options_from_collection_for_select @roles, "id", "name", (@role.id unless @role.nil?) %>
</select>
<label for="tracker_id"><%=l(:label_tracker)%>:</label>
-<select id="tracker_id" name="tracker_id">
+<select name="tracker_id">
<%= options_from_collection_for_select @trackers, "id", "name", (@tracker.id unless @tracker.nil?) %>
</select>
-<%= submit_tag l(:button_edit) %>
+<%= submit_tag l(:button_edit), :name => nil %>
</p>
<% end %>
-<% unless @tracker.nil? or @role.nil? %>
-<% form_tag({:action => 'workflow', :role_id => @role, :tracker_id => @tracker }, :id => 'workflow_form' ) do %>
-<div class="box">
- <table>
+<% unless @tracker.nil? or @role.nil? or @statuses.empty? %>
+<% form_tag({}, :id => 'workflow_form' ) do %>
+<%= hidden_field_tag 'tracker_id', @tracker.id %>
+<%= hidden_field_tag 'role_id', @role.id %>
+<table class="list">
+<thead>
<tr>
- <td align="center"><strong><%=l(:label_current_status)%></strong></td>
- <td align="center" colspan="<%= @statuses.length %>"><strong><%=l(:label_new_statuses_allowed)%></strong></td>
+ <th align="left"><%=l(:label_current_status)%></th>
+ <th align="center" colspan="<%= @statuses.length %>"><%=l(:label_new_statuses_allowed)%></th>
</tr>
<tr>
<td></td>
<% for new_status in @statuses %>
- <td width="80" align="center"><%= new_status.name %></td>
+ <td width="<%= 75 / @statuses.size %>%" align="center"><%= new_status.name %></td>
<% end %>
</tr>
-
+</thead>
+<tbody>
<% for old_status in @statuses %>
- <tr>
+ <tr class="<%= cycle("odd", "even") %>">
<td><%= old_status.name %></td>
<% new_status_ids_allowed = old_status.find_new_statuses_allowed_to(@role, @tracker).collect(&:id) -%>
<% for new_status in @statuses -%>
@@ -42,14 +49,15 @@
<input type="checkbox"
name="issue_status[<%= old_status.id %>][]"
value="<%= new_status.id %>"
- <%= 'checked="checked"' if new_status_ids_allowed.include? new_status.id %>>
+ <%= 'checked="checked"' if new_status_ids_allowed.include? new_status.id %> />
</td>
<% end -%>
</tr>
<% end %>
- </table>
- <p><%= check_all_links 'workflow_form' %></p>
-</div>
+</tbody>
+</table>
+<p><%= check_all_links 'workflow_form' %></p>
+
<%= submit_tag l(:button_save) %>
<% end %>
diff --git a/app/views/workflows/index.rhtml b/app/views/workflows/index.rhtml
new file mode 100644
index 000000000..2fd080d8f
--- /dev/null
+++ b/app/views/workflows/index.rhtml
@@ -0,0 +1,31 @@
+<h2><%=l(:label_workflow)%></h2>
+
+<% if @workflow_counts.empty? %>
+<p class="nodata"><%= l(:label_no_data) %></p>
+<% else %>
+<table class="list">
+<thead>
+ <tr>
+ <th></th>
+ <% @workflow_counts.first.last.each do |role, count| %>
+ <th>
+ <%= content_tag(role.builtin? ? 'em' : 'span', h(role.name)) %>
+ </th>
+
+ <% end %>
+ </tr>
+</thead>
+<tbody>
+<% @workflow_counts.each do |tracker, roles| -%>
+<tr class="<%= cycle('odd', 'even') %>">
+ <td><%= h tracker %></td>
+ <% roles.each do |role, count| -%>
+ <td align="center">
+ <%= link_to((count > 1 ? count : image_tag('false.png')), {:action => 'edit', :role_id => role, :tracker_id => tracker}, :title => l(:button_edit)) %>
+ </td>
+ <% end -%>
+</tr>
+<% end -%>
+</tbody>
+</table>
+<% end %>
diff --git a/config/environment.rb b/config/environment.rb
index 9a3bf4b1d..fdac6b4f0 100644
--- a/config/environment.rb
+++ b/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.1.0' unless defined? RAILS_GEM_VERSION
+RAILS_GEM_VERSION = '2.1.2' unless defined? RAILS_GEM_VERSION
# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')
diff --git a/config/initializers/10-patches.rb b/config/initializers/10-patches.rb
index fcc091997..d1235d93f 100644
--- a/config/initializers/10-patches.rb
+++ b/config/initializers/10-patches.rb
@@ -12,6 +12,20 @@ ActiveRecord::Errors.default_error_messages = {
:wrong_length => "activerecord_error_wrong_length",
:taken => "activerecord_error_taken",
:not_a_number => "activerecord_error_not_a_number"
-}
+} if ActiveRecord::Errors.respond_to?('default_error_messages=')
ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
+
+# Adds :async_smtp and :async_sendmail delivery methods
+# to perform email deliveries asynchronously
+module AsynchronousMailer
+ %w(smtp sendmail).each do |type|
+ define_method("perform_delivery_async_#{type}") do |mail|
+ Thread.start do
+ send "perform_delivery_#{type}", mail
+ end
+ end
+ end
+end
+
+ActionMailer::Base.send :include, AsynchronousMailer
diff --git a/config/routes.rb b/config/routes.rb
index 913a8020f..d20bc3c9f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -6,41 +6,255 @@ ActionController::Routing::Routes.draw do |map|
# map.connect 'products/:id', :controller => 'catalog', :action => 'view'
# Keep in mind you can assign values other than :controller and :action
+ # Allow Redmine plugins to map routes and potentially override them
+ Rails.plugins.each do |plugin|
+ map.from_plugin plugin.name.to_sym
+ end
+
map.home '', :controller => 'welcome'
+
map.signin 'login', :controller => 'account', :action => 'login'
map.signout 'logout', :controller => 'account', :action => 'logout'
- map.connect 'wiki/:id/:page/:action', :controller => 'wiki', :page => nil
map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
map.connect 'help/:ctrl/:page', :controller => 'help'
- #map.connect ':controller/:action/:id/:sort_key/:sort_order'
- map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
+ map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog'
+ map.connect 'projects/:project_id/time_entries/new', :action => 'edit', :controller => 'timelog'
+ map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'edit', :controller => 'timelog'
+
+ map.with_options :controller => 'timelog' do |timelog|
+ timelog.connect 'projects/:project_id/time_entries', :action => 'details'
+
+ timelog.with_options :action => 'details', :conditions => {:method => :get} do |time_details|
+ time_details.connect 'time_entries'
+ time_details.connect 'time_entries.:format'
+ time_details.connect 'issues/:issue_id/time_entries'
+ time_details.connect 'issues/:issue_id/time_entries.:format'
+ time_details.connect 'projects/:project_id/time_entries.:format'
+ time_details.connect 'projects/:project_id/issues/:issue_id/time_entries'
+ time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format'
+ end
+ timelog.connect 'projects/:project_id/time_entries/report', :action => 'report'
+ timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report|
+ time_report.connect 'time_entries/report'
+ time_report.connect 'time_entries/report.:format'
+ time_report.connect 'projects/:project_id/time_entries/report.:format'
+ end
+
+ timelog.with_options :action => 'edit', :conditions => {:method => :get} do |time_edit|
+ time_edit.connect 'issues/:issue_id/time_entries/new'
+ end
+
+ timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post}
+ end
+
+ map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
+ map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
+ map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
+ map.with_options :controller => 'wiki' do |wiki_routes|
+ wiki_routes.with_options :conditions => {:method => :get} do |wiki_views|
+ wiki_views.connect 'projects/:id/wiki/:page', :action => 'special', :page => /page_index|date_index|export/i
+ wiki_views.connect 'projects/:id/wiki/:page', :action => 'index', :page => nil
+ wiki_views.connect 'projects/:id/wiki/:page/edit', :action => 'edit'
+ wiki_views.connect 'projects/:id/wiki/:page/rename', :action => 'rename'
+ wiki_views.connect 'projects/:id/wiki/:page/history', :action => 'history'
+ wiki_views.connect 'projects/:id/wiki/:page/diff/:version/vs/:version_from', :action => 'diff'
+ wiki_views.connect 'projects/:id/wiki/:page/annotate/:version', :action => 'annotate'
+ end
+
+ wiki_routes.connect 'projects/:id/wiki/:page/:action',
+ :action => /edit|rename|destroy|preview|protect/,
+ :conditions => {:method => :post}
+ end
+
+ map.with_options :controller => 'messages' do |messages_routes|
+ messages_routes.with_options :conditions => {:method => :get} do |messages_views|
+ messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
+ messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
+ messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
+ end
+ messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
+ messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
+ messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
+ messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
+ end
+ end
+
+ map.with_options :controller => 'boards' do |board_routes|
+ board_routes.with_options :conditions => {:method => :get} do |board_views|
+ board_views.connect 'projects/:project_id/boards', :action => 'index'
+ board_views.connect 'projects/:project_id/boards/new', :action => 'new'
+ board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
+ board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
+ end
+ board_routes.with_options :conditions => {:method => :post} do |board_actions|
+ board_actions.connect 'projects/:project_id/boards', :action => 'new'
+ board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
+ end
+ end
+
+ map.with_options :controller => 'documents' do |document_routes|
+ document_routes.with_options :conditions => {:method => :get} do |document_views|
+ document_views.connect 'projects/:project_id/documents', :action => 'index'
+ document_views.connect 'projects/:project_id/documents/new', :action => 'new'
+ document_views.connect 'documents/:id', :action => 'show'
+ document_views.connect 'documents/:id/edit', :action => 'edit'
+ end
+ document_routes.with_options :conditions => {:method => :post} do |document_actions|
+ document_actions.connect 'projects/:project_id/documents', :action => 'new'
+ document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
+ end
+ end
+
+ map.with_options :controller => 'issues' do |issues_routes|
+ issues_routes.with_options :conditions => {:method => :get} do |issues_views|
+ issues_views.connect 'issues', :action => 'index'
+ issues_views.connect 'issues.:format', :action => 'index'
+ issues_views.connect 'projects/:project_id/issues', :action => 'index'
+ issues_views.connect 'projects/:project_id/issues.:format', :action => 'index'
+ issues_views.connect 'projects/:project_id/issues/new', :action => 'new'
+ issues_views.connect 'projects/:project_id/issues/gantt', :action => 'gantt'
+ issues_views.connect 'projects/:project_id/issues/calendar', :action => 'calendar'
+ issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new'
+ issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/
+ issues_views.connect 'issues/:id.:format', :action => 'show', :id => /\d+/
+ issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/
+ issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/
+ end
+ issues_routes.with_options :conditions => {:method => :post} do |issues_actions|
+ issues_actions.connect 'projects/:project_id/issues', :action => 'new'
+ issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/
+ issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/
+ end
+ issues_routes.connect 'issues/:action'
+ end
+
+ map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
+ relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
+ relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
+ end
+
+ map.with_options :controller => 'reports', :action => 'issue_report', :conditions => {:method => :get} do |reports|
+ reports.connect 'projects/:id/issues/report'
+ reports.connect 'projects/:id/issues/report/:detail'
+ end
+
+ map.with_options :controller => 'news' do |news_routes|
+ news_routes.with_options :conditions => {:method => :get} do |news_views|
+ news_views.connect 'news', :action => 'index'
+ news_views.connect 'projects/:project_id/news', :action => 'index'
+ news_views.connect 'projects/:project_id/news.:format', :action => 'index'
+ news_views.connect 'news.:format', :action => 'index'
+ news_views.connect 'projects/:project_id/news/new', :action => 'new'
+ news_views.connect 'news/:id', :action => 'show'
+ news_views.connect 'news/:id/edit', :action => 'edit'
+ end
+ news_routes.with_options do |news_actions|
+ news_actions.connect 'projects/:project_id/news', :action => 'new'
+ news_actions.connect 'news/:id/edit', :action => 'edit'
+ news_actions.connect 'news/:id/destroy', :action => 'destroy'
+ end
+ end
+
+ map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
+
+ map.with_options :controller => 'users' do |users|
+ users.with_options :conditions => {:method => :get} do |user_views|
+ user_views.connect 'users', :action => 'list'
+ user_views.connect 'users', :action => 'index'
+ user_views.connect 'users/new', :action => 'add'
+ user_views.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil
+ end
+ users.with_options :conditions => {:method => :post} do |user_actions|
+ user_actions.connect 'users', :action => 'add'
+ user_actions.connect 'users/new', :action => 'add'
+ user_actions.connect 'users/:id/edit', :action => 'edit'
+ user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
+ user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
+ user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
+ end
+ end
+
+ map.with_options :controller => 'projects' do |projects|
+ projects.with_options :conditions => {:method => :get} do |project_views|
+ project_views.connect 'projects', :action => 'index'
+ project_views.connect 'projects.:format', :action => 'index'
+ project_views.connect 'projects/new', :action => 'add'
+ project_views.connect 'projects/:id', :action => 'show'
+ project_views.connect 'projects/:id/:action', :action => /roadmap|changelog|destroy|settings/
+ project_views.connect 'projects/:id/files', :action => 'list_files'
+ project_views.connect 'projects/:id/files/new', :action => 'add_file'
+ project_views.connect 'projects/:id/versions/new', :action => 'add_version'
+ project_views.connect 'projects/:id/categories/new', :action => 'add_issue_category'
+ project_views.connect 'projects/:id/settings/:tab', :action => 'settings'
+ end
+
+ projects.with_options :action => 'activity', :conditions => {:method => :get} do |activity|
+ activity.connect 'projects/:id/activity'
+ activity.connect 'projects/:id/activity.:format'
+ activity.connect 'activity'
+ activity.connect 'activity.:format'
+ end
+
+ projects.with_options :conditions => {:method => :post} do |project_actions|
+ project_actions.connect 'projects/new', :action => 'add'
+ project_actions.connect 'projects', :action => 'add'
+ project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/
+ project_actions.connect 'projects/:id/files/new', :action => 'add_file'
+ project_actions.connect 'projects/:id/versions/new', :action => 'add_version'
+ project_actions.connect 'projects/:id/categories/new', :action => 'add_issue_category'
+ end
+ end
+
+ map.with_options :controller => 'repositories' do |repositories|
+ repositories.with_options :conditions => {:method => :get} do |repository_views|
+ repositories.connect 'projects/:id/repository', :action => 'show'
+ repositories.connect 'projects/:id/repository/edit', :action => 'edit'
+ repositories.connect 'projects/:id/repository/statistics', :action => 'stats'
+ repositories.connect 'projects/:id/repository/revisions', :action => 'revisions'
+ repositories.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
+ repositories.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
+ repositories.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
+ repositories.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
+ repositories.connect 'projects/:id/repository/revisions/:rev/:action/*path'
+ repositories.connect 'projects/:id/repository/:action/*path'
+ end
+
+ repositories.connect 'projects/:id/repository/edit', :action => 'edit', :conditions => {:method => :post}
+ 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 => /.*/
+
+
+ #left old routes at the bottom for backwards compat
map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
- map.connect 'projects/:project_id/news/:action', :controller => 'news'
map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
- map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
-
+ map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
+ map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
+ map.connect 'projects/:project_id/news/:action', :controller => 'news'
+ map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
map.with_options :controller => 'repositories' do |omap|
omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
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'
+ omap.connect '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'
-
+ map.with_options :controller => 'sys' do |sys|
+ sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
+ sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
+ end
# Install the default route as the lowest priority.
map.connect ':controller/:action/:id'
+ map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
+ # Used for OpenID
+ map.root :controller => 'account', :action => 'login'
end
diff --git a/config/settings.yml b/config/settings.yml
index 52f7bed87..56cdf8659 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -46,6 +46,8 @@ mail_from:
default: redmine@example.net
bcc_recipients:
default: 1
+plain_text_mail:
+ default: 0
text_formatting:
default: textile
wiki_compression:
@@ -59,6 +61,9 @@ protocol:
feeds_limit:
format: int
default: 15
+diff_max_lines_displayed:
+ format: int
+ default: 1500
enabled_scm:
serialized: true
default:
@@ -129,10 +134,16 @@ repositories_cache_directory:
# encoding used to convert commit logs to UTF-8
commit_logs_encoding:
default: 'UTF-8'
+repository_log_display_limit:
+ format: int
+ default: 100
ui_theme:
default: ''
emails_footer:
default: |-
You have received this notification because you have either subscribed to it, or are involved in it.
To change your notification preferences, please click here: http://hostname/my/account
-
+gravatar_enabled:
+ default: 0
+openid:
+ default: 0
diff --git a/db/migrate/098_set_topic_authors_as_watchers.rb b/db/migrate/098_set_topic_authors_as_watchers.rb
index 5bc41fb38..92a53f4a1 100644
--- a/db/migrate/098_set_topic_authors_as_watchers.rb
+++ b/db/migrate/098_set_topic_authors_as_watchers.rb
@@ -2,9 +2,10 @@ class SetTopicAuthorsAsWatchers < ActiveRecord::Migration
def self.up
# Sets active users who created/replied a topic as watchers of the topic
# so that the new watch functionality at topic level doesn't affect notifications behaviour
- Message.connection.execute("INSERT INTO watchers (watchable_type, watchable_id, user_id)" +
- " SELECT DISTINCT 'Message', COALESCE(messages.parent_id, messages.id), messages.author_id FROM messages, users" +
- " WHERE messages.author_id = users.id AND users.status = 1")
+ Message.connection.execute("INSERT INTO #{Watcher.table_name} (watchable_type, watchable_id, user_id)" +
+ " SELECT DISTINCT 'Message', COALESCE(m.parent_id, m.id), m.author_id" +
+ " FROM #{Message.table_name} m, #{User.table_name} u" +
+ " WHERE m.author_id = u.id AND u.status = 1")
end
def self.down
diff --git a/db/migrate/099_add_delete_wiki_pages_attachments_permission.rb b/db/migrate/099_add_delete_wiki_pages_attachments_permission.rb
new file mode 100644
index 000000000..1ff888f3c
--- /dev/null
+++ b/db/migrate/099_add_delete_wiki_pages_attachments_permission.rb
@@ -0,0 +1,13 @@
+class AddDeleteWikiPagesAttachmentsPermission < ActiveRecord::Migration
+ def self.up
+ Role.find(:all).each do |r|
+ r.add_permission!(:delete_wiki_pages_attachments) if r.has_permission?(:edit_wiki_pages)
+ end
+ end
+
+ def self.down
+ Role.find(:all).each do |r|
+ r.remove_permission!(:delete_wiki_pages_attachments)
+ end
+ end
+end
diff --git a/db/migrate/100_add_changesets_user_id.rb b/db/migrate/100_add_changesets_user_id.rb
new file mode 100644
index 000000000..9b25fd7bc
--- /dev/null
+++ b/db/migrate/100_add_changesets_user_id.rb
@@ -0,0 +1,9 @@
+class AddChangesetsUserId < ActiveRecord::Migration
+ def self.up
+ add_column :changesets, :user_id, :integer, :default => nil
+ end
+
+ def self.down
+ remove_column :changesets, :user_id
+ end
+end
diff --git a/db/migrate/101_populate_changesets_user_id.rb b/db/migrate/101_populate_changesets_user_id.rb
new file mode 100644
index 000000000..dd493d17d
--- /dev/null
+++ b/db/migrate/101_populate_changesets_user_id.rb
@@ -0,0 +1,18 @@
+class PopulateChangesetsUserId < ActiveRecord::Migration
+ def self.up
+ committers = Changeset.connection.select_values("SELECT DISTINCT committer FROM #{Changeset.table_name}")
+ committers.each do |committer|
+ next if committer.blank?
+ if committer.strip =~ /^([^<]+)(<(.*)>)?$/
+ username, email = $1.strip, $3
+ u = User.find_by_login(username)
+ u ||= User.find_by_mail(email) unless email.blank?
+ Changeset.update_all("user_id = #{u.id}", ["committer = ?", committer]) unless u.nil?
+ end
+ end
+ end
+
+ def self.down
+ Changeset.update_all('user_id = NULL')
+ end
+end
diff --git a/db/migrate/102_add_custom_fields_editable.rb b/db/migrate/102_add_custom_fields_editable.rb
new file mode 100644
index 000000000..949f9db9d
--- /dev/null
+++ b/db/migrate/102_add_custom_fields_editable.rb
@@ -0,0 +1,9 @@
+class AddCustomFieldsEditable < ActiveRecord::Migration
+ def self.up
+ add_column :custom_fields, :editable, :boolean, :default => true
+ end
+
+ def self.down
+ remove_column :custom_fields, :editable
+ end
+end
diff --git a/db/migrate/103_set_custom_fields_editable.rb b/db/migrate/103_set_custom_fields_editable.rb
new file mode 100644
index 000000000..937649e61
--- /dev/null
+++ b/db/migrate/103_set_custom_fields_editable.rb
@@ -0,0 +1,9 @@
+class SetCustomFieldsEditable < ActiveRecord::Migration
+ def self.up
+ UserCustomField.update_all("editable = #{CustomField.connection.quoted_false}")
+ end
+
+ def self.down
+ UserCustomField.update_all("editable = #{CustomField.connection.quoted_true}")
+ end
+end
diff --git a/db/migrate/104_add_projects_lft_and_rgt.rb b/db/migrate/104_add_projects_lft_and_rgt.rb
new file mode 100644
index 000000000..8952c16e1
--- /dev/null
+++ b/db/migrate/104_add_projects_lft_and_rgt.rb
@@ -0,0 +1,11 @@
+class AddProjectsLftAndRgt < ActiveRecord::Migration
+ def self.up
+ add_column :projects, :lft, :integer
+ add_column :projects, :rgt, :integer
+ end
+
+ def self.down
+ remove_column :projects, :lft
+ remove_column :projects, :rgt
+ end
+end
diff --git a/db/migrate/105_build_projects_tree.rb b/db/migrate/105_build_projects_tree.rb
new file mode 100644
index 000000000..92799f97d
--- /dev/null
+++ b/db/migrate/105_build_projects_tree.rb
@@ -0,0 +1,8 @@
+class BuildProjectsTree < ActiveRecord::Migration
+ def self.up
+ Project.rebuild!
+ end
+
+ def self.down
+ end
+end
diff --git a/db/migrate/106_remove_projects_projects_count.rb b/db/migrate/106_remove_projects_projects_count.rb
new file mode 100644
index 000000000..68bb3d115
--- /dev/null
+++ b/db/migrate/106_remove_projects_projects_count.rb
@@ -0,0 +1,9 @@
+class RemoveProjectsProjectsCount < ActiveRecord::Migration
+ def self.up
+ remove_column :projects, :projects_count
+ end
+
+ def self.down
+ add_column :projects, :projects_count, :integer, :default => 0
+ end
+end
diff --git a/db/migrate/107_add_open_id_authentication_tables.rb b/db/migrate/107_add_open_id_authentication_tables.rb
new file mode 100644
index 000000000..caae0d8c7
--- /dev/null
+++ b/db/migrate/107_add_open_id_authentication_tables.rb
@@ -0,0 +1,20 @@
+class AddOpenIdAuthenticationTables < ActiveRecord::Migration
+ def self.up
+ create_table :open_id_authentication_associations, :force => true do |t|
+ t.integer :issued, :lifetime
+ t.string :handle, :assoc_type
+ t.binary :server_url, :secret
+ end
+
+ create_table :open_id_authentication_nonces, :force => true do |t|
+ t.integer :timestamp, :null => false
+ t.string :server_url, :null => true
+ t.string :salt, :null => false
+ end
+ end
+
+ def self.down
+ drop_table :open_id_authentication_associations
+ drop_table :open_id_authentication_nonces
+ end
+end
diff --git a/db/migrate/108_add_identity_url_to_users.rb b/db/migrate/108_add_identity_url_to_users.rb
new file mode 100644
index 000000000..f5af77b24
--- /dev/null
+++ b/db/migrate/108_add_identity_url_to_users.rb
@@ -0,0 +1,9 @@
+class AddIdentityUrlToUsers < ActiveRecord::Migration
+ def self.up
+ add_column :users, :identity_url, :string
+ end
+
+ def self.down
+ remove_column :users, :identity_url
+ end
+end
diff --git a/doc/CHANGELOG b/doc/CHANGELOG
index ac8cb6673..31f316557 100644
--- a/doc/CHANGELOG
+++ b/doc/CHANGELOG
@@ -5,6 +5,204 @@ Copyright (C) 2006-2008 Jean-Philippe Lang
http://www.redmine.org/
+== v0.8.1
+
+* Select watchers on new issue form
+* Files module: ability to add files without version
+* Show view/annotate/download links on entry and annotate views
+* Fixed: Deleted files are shown when using Darcs
+
+
+== 2008-12-30 v0.8.0
+
+* Setting added in order to limit the number of diff lines that should be displayed
+* Makes logged-in username in topbar linking to
+* Mail handler: strip tags when receiving a html-only email
+* Mail handler: add watchers before sending notification
+* Adds a css class (overdue) to overdue issues on issue lists and detail views
+* Fixed: project activity truncated after viewing user's activity
+* Fixed: email address entered for password recovery shouldn't be case-sensitive
+* Fixed: default flag removed when editing a default enumeration
+* Fixed: default category ignored when adding a document
+* Fixed: error on repository user mapping when a repository username is blank
+* Fixed: Firefox cuts off large diffs
+* Fixed: CVS browser should not show dead revisions (deleted files)
+* Fixed: escape double-quotes in image titles
+* Fixed: escape textarea content when editing a issue note
+* Fixed: JS error on context menu with IE
+* Fixed: bold syntax around single character in series doesn't work
+* Fixed several XSS vulnerabilities
+* Fixed a SQL injection vulnerability
+
+
+== 2008-12-07 v0.8.0-rc1
+
+* Wiki page protection
+* Wiki page hierarchy. Parent page can be assigned on the Rename screen
+* Adds support for issue creation via email
+* Adds support for free ticket filtering and custom queries on Gantt chart and calendar
+* Cross-project search
+* Ability to search a project and its subprojects
+* Ability to search the projects the user belongs to
+* Adds custom fields on time entries
+* Adds boolean and list custom fields for time entries as criteria on time report
+* Cross-project time reports
+* Display latest user's activity on account/show view
+* Show last connexion time on user's page
+* Obfuscates email address on user's account page using javascript
+* wiki TOC rendered as an unordered list
+* Adds the ability to search for a user on the administration users list
+* Adds the ability to search for a project name or identifier on the administration projects list
+* Redirect user to the previous page after logging in
+* Adds a permission 'view wiki edits' so that wiki history can be hidden to certain users
+* Adds permissions for viewing the watcher list and adding new watchers on the issue detail view
+* Adds permissions to let users edit and/or delete their messages
+* Link to activity view when displaying dates
+* Hide Redmine version in atom feeds and pdf properties
+* Maps repository users to Redmine users. Users with same username or email are automatically mapped. Mapping can be manually adjusted in repository settings. Multiple usernames can be mapped to the same Redmine user.
+* Sort users by their display names so that user dropdown lists are sorted alphabetically
+* Adds estimated hours to issue filters
+* Switch order of current and previous revisions in side-by-side diff
+* Render the commit changes list as a tree
+* Adds watch/unwatch functionality at forum topic level
+* When moving an issue to another project, reassign it to the category with same name if any
+* Adds child_pages macro for wiki pages
+* Use GET instead of POST on roadmap (#718), gantt and calendar forms
+* Search engine: display total results count and count by result type
+* Email delivery configuration moved to an unversioned YAML file (config/email.yml, see the sample file)
+* Adds icons on search results
+* Adds 'Edit' link on account/show for admin users
+* Adds Lock/Unlock/Activate link on user edit screen
+* Adds user count in status drop down on admin user list
+* Adds multi-levels blockquotes support by using > at the beginning of lines
+* Adds a Reply link to each issue note
+* Adds plain text only option for mail notifications
+* Gravatar support for issue detail, user grid, and activity stream (disabled by default)
+* Adds 'Delete wiki pages attachments' permission
+* Show the most recent file when displaying an inline image
+* Makes permission screens localized
+* AuthSource list: display associated users count and disable 'Delete' buton if any
+* Make the 'duplicates of' relation asymmetric
+* Adds username to the password reminder email
+* Adds links to forum messages using message#id syntax
+* Allow same name for custom fields on different object types
+* One-click bulk edition using the issue list context menu within the same project
+* Adds support for commit logs reencoding to UTF-8 before insertion in the database. Source encoding of commit logs can be selected in Application settings -> Repositories.
+* Adds checkboxes toggle links on permissions report
+* Adds Trac-Like anchors on wiki headings
+* Adds support for wiki links with anchor
+* Adds category to the issue context menu
+* Adds a workflow overview screen
+* Appends the filename to the attachment url so that clients that ignore content-disposition http header get the real filename
+* Dots allowed in custom field name
+* Adds posts quoting functionality
+* Adds an option to generate sequential project identifiers
+* Adds mailto link on the user administration list
+* Ability to remove enumerations (activities, priorities, document categories) that are in use. Associated objects can be reassigned to another value
+* Gantt chart: display issues that don't have a due date if they are assigned to a version with a date
+* Change projects homepage limit to 255 chars
+* Improved on-the-fly account creation. If some attributes are missing (eg. not present in the LDAP) or are invalid, the registration form is displayed so that the user is able to fill or fix these attributes
+* Adds "please select" to activity select box if no activity is set as default
+* Do not silently ignore timelog validation failure on issue edit
+* Adds a rake task to send reminder emails
+* Allow empty cells in wiki tables
+* Makes wiki text formatter pluggable
+* Adds back textile acronyms support
+* Remove pre tag attributes
+* Plugin hooks
+* Pluggable admin menu
+* Plugins can provide activity content
+* Moves plugin list to its own administration menu item
+* Adds url and author_url plugin attributes
+* Adds Plugin#requires_redmine method so that plugin compatibility can be checked against current Redmine version
+* Adds atom feed on time entries details
+* Adds project name to issues feed title
+* Adds a css class on menu items in order to apply item specific styles (eg. icons)
+* Adds a Redmine plugin generators
+* Adds timelog link to the issue context menu
+* Adds links to the user page on various views
+* Turkish translation by Ismail Sezen
+* Catalan translation
+* Vietnamese translation
+* Slovak translation
+* Better naming of activity feed if only one kind of event is displayed
+* Enable syntax highlight on issues, messages and news
+* Add target version to the issue list context menu
+* Hide 'Target version' filter if no version is defined
+* Add filters on cross-project issue list for custom fields marked as 'For all projects'
+* Turn ftp urls into links
+* Hiding the View Differences button when a wiki page's history only has one version
+* Messages on a Board can now be sorted by the number of replies
+* Adds a class ('me') to events of the activity view created by current user
+* Strip pre/code tags content from activity view events
+* Display issue notes in the activity view
+* Adds links to changesets atom feed on repository browser
+* Track project and tracker changes in issue history
+* Adds anchor to atom feed messages links
+* Adds a key in lang files to set the decimal separator (point or comma) in csv exports
+* Makes importer work with Trac 0.8.x
+* Upgraded to Prototype 1.6.0.1
+* File viewer for attached text files
+* Menu mapper: add support for :before, :after and :last options to #push method and add #delete method
+* Removed inconsistent revision numbers on diff view
+* CVS: add support for modules names with spaces
+* Log the user in after registration if account activation is not needed
+* Mercurial adapter improvements
+* Trac importer: read session_attribute table to find user's email and real name
+* Ability to disable unused SCM adapters in application settings
+* Adds Filesystem adapter
+* Clear changesets and changes with raw sql when deleting a repository for performance
+* Redmine.pm now uses the 'commit access' permission defined in Redmine
+* Reposman can create any type of scm (--scm option)
+* Reposman creates a repository if the 'repository' module is enabled at project level only
+* Display svn properties in the browser, svn >= 1.5.0 only
+* Reduces memory usage when importing large git repositories
+* Wider SVG graphs in repository stats
+* SubversionAdapter#entries performance improvement
+* SCM browser: ability to download raw unified diffs
+* More detailed error message in log when scm command fails
+* Adds support for file viewing with Darcs 2.0+
+* Check that git changeset is not in the database before creating it
+* Unified diff viewer for attached files with .patch or .diff extension
+* File size display with Bazaar repositories
+* Git adapter: use commit time instead of author time
+* Prettier url for changesets
+* Makes changes link to entries on the revision view
+* Adds a field on the repository view to browse at specific revision
+* Adds new projects atom feed
+* Added rake tasks to generate rcov code coverage reports
+* Add Redcloth's :block_markdown_rule to allow horizontal rules in wiki
+* Show the project hierarchy in the drop down list for new membership on user administration screen
+* Split user edit screen into tabs
+* Renames bundled RedCloth to RedCloth3 to avoid RedCloth 4 to be loaded instead
+* Fixed: Roadmap crashes when a version has a due date > 2037
+* Fixed: invalid effective date (eg. 99999-01-01) causes an error on version edition screen
+* Fixed: login filter providing incorrect back_url for Redmine installed in sub-directory
+* Fixed: logtime entry duplicated when edited from parent project
+* Fixed: wrong digest for text files under Windows
+* Fixed: associated revisions are displayed in wrong order on issue view
+* Fixed: Git Adapter date parsing ignores timezone
+* Fixed: Printing long roadmap doesn't split across pages
+* Fixes custom fields display order at several places
+* Fixed: urls containing @ are parsed as email adress by the wiki formatter
+* Fixed date filters accuracy with SQLite
+* Fixed: tokens not escaped in highlight_tokens regexp
+* Fixed Bazaar shared repository browsing
+* Fixes platform determination under JRuby
+* Fixed: Estimated time in issue's journal should be rounded to two decimals
+* Fixed: 'search titles only' box ignored after one search is done on titles only
+* Fixed: non-ASCII subversion path can't be displayed
+* Fixed: Inline images don't work if file name has upper case letters or if image is in BMP format
+* Fixed: document listing shows on "my page" when viewing documents is disabled for the role
+* Fixed: Latest news appear on the homepage for projects with the News module disabled
+* Fixed: cross-project issue list should not show issues of projects for which the issue tracking module was disabled
+* Fixed: the default status is lost when reordering issue statuses
+* Fixes error with Postgresql and non-UTF8 commit logs
+* Fixed: textile footnotes no longer work
+* Fixed: http links containing parentheses fail to reder correctly
+* Fixed: GitAdapter#get_rev should use current branch instead of hardwiring master
+
+
== 2008-07-06 v0.7.3
* Allow dot in firstnames and lastnames
diff --git a/doc/RUNNING_TESTS b/doc/RUNNING_TESTS
index 6ee977811..b5bfa3862 100644
--- a/doc/RUNNING_TESTS
+++ b/doc/RUNNING_TESTS
@@ -1,45 +1,16 @@
-Creating test repositories
-===================
-
-mkdir tmp/test
-
-Subversion
-----------
-svnadmin create tmp/test/subversion_repository
-gunzip < test/fixtures/repositories/subversion_repository.dump.gz | svnadmin load tmp/test/subversion_repository
-
-CVS
----
-gunzip < test/fixtures/repositories/cvs_repository.tar.gz | tar -xv -C tmp/test
-
-Bazaar
-------
-gunzip < test/fixtures/repositories/bazaar_repository.tar.gz | tar -xv -C tmp/test
-
-Mercurial
----------
-gunzip < test/fixtures/repositories/mercurial_repository.tar.gz | tar -xv -C tmp/test
-
-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
=============
-Run
+Run `rake --tasks test` to see available tests.
+`rake test` will run the entire testsuite.
+
+
+Creating test repositories
+===================
- rake --tasks | grep test
+Redmine supports a wide array of different version control systems.
+To test the support, a test repository needs to be created for each of those.
-to see available tests.
+Run `rake --tasks test:scm:setup` for a list of available test-repositories or
+run `rake test:scm:setup:all` to set up all of them
-RAILS_ENV=test rake test will run tests.
diff --git a/doc/UPGRADING b/doc/UPGRADING
index 1dd901171..40c691fea 100644
--- a/doc/UPGRADING
+++ b/doc/UPGRADING
@@ -22,7 +22,7 @@ http://www.redmine.org/
== Notes
-1. Rails 2.0.2 is required for version 0.7 and later.
+1. Rails 2.1.2 is required for version 0.8.
2. When upgrading your code with svn update, don't forget to clear
the application cache (RAILS_ROOT/tmp/cache) before restarting.
diff --git a/extra/mail_handler/rdm-mailhandler.rb b/extra/mail_handler/rdm-mailhandler.rb
index 0f8020c76..93484ca34 100644
--- a/extra/mail_handler/rdm-mailhandler.rb
+++ b/extra/mail_handler/rdm-mailhandler.rb
@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#!/usr/bin/env ruby
# == Synopsis
#
@@ -21,6 +21,7 @@
#
# Issue attributes control options:
# -p, --project=PROJECT identifier of the target project
+# -s, --status=STATUS name of the target status
# -t, --tracker=TRACKER name of the target tracker
# --category=CATEGORY name of the target category
# --priority=PRIORITY name of the target priority
@@ -75,6 +76,7 @@ class RedmineMailHandler
[ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ],
[ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
[ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ],
+ [ '--status', '-s', GetoptLong::REQUIRED_ARGUMENT ],
[ '--tracker', '-t', GetoptLong::REQUIRED_ARGUMENT],
[ '--category', GetoptLong::REQUIRED_ARGUMENT],
[ '--priority', GetoptLong::REQUIRED_ARGUMENT],
@@ -93,7 +95,7 @@ class RedmineMailHandler
self.verbose = true
when '--version'
puts VERSION; exit
- when '--project', '--tracker', '--category', '--priority'
+ when '--project', '--status', '--tracker', '--category', '--priority'
self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup
when '--allow-override'
self.allow_override = arg.dup
diff --git a/extra/svn/reposman.rb b/extra/svn/reposman.rb
index b7aa2a462..84e9bc2da 100755
--- a/extra/svn/reposman.rb
+++ b/extra/svn/reposman.rb
@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#!/usr/bin/env ruby
# == Synopsis
#
@@ -57,11 +57,10 @@
require 'getoptlong'
require 'rdoc/usage'
-require 'soap/wsdlDriver'
require 'find'
require 'etc'
-Version = "1.1"
+Version = "1.2"
SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
opts = GetoptLong.new(
@@ -164,21 +163,28 @@ unless File.directory?($repos_base)
log("directory '#{$repos_base}' doesn't exists", :exit => true)
end
+begin
+ require 'activeresource'
+rescue LoadError
+ log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
+end
+
+class Project < ActiveResource::Base; end
+
log("querying Redmine for projects...", :level => 1);
$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
$redmine_host.gsub!(/\/$/, '')
-wsdl_url = "#{$redmine_host}/sys/service.wsdl";
+Project.site = "#{$redmine_host}/sys";
begin
- soap = SOAP::WSDLDriverFactory.new(wsdl_url).create_rpc_driver
+ # Get all active projects that have the Repository module enabled
+ projects = Project.find(:all)
rescue => e
- log("Unable to connect to #{wsdl_url} : #{e}", :exit => true)
+ log("Unable to connect to #{Project.site}: #{e}", :exit => true)
end
-projects = soap.ProjectsWithRepositoryEnabled
-
if projects.nil?
log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
end
@@ -247,7 +253,7 @@ projects.each do |project|
else
# if repository is already declared in redmine, we don't create
# unless user use -f with reposman
- if $force == false and not project.repository.nil?
+ if $force == false and project.respond_to?(:repository)
log("\trepository for project #{project.identifier} already exists in Redmine", :level => 1)
next
end
@@ -274,11 +280,11 @@ projects.each do |project|
end
if $svn_url
- ret = soap.RepositoryCreated project.identifier, $scm, "#{$svn_url}#{project.identifier}"
- if ret > 0
+ begin
+ project.post(:repository, :vendor => $scm, :repository => {:url => "#{$svn_url}#{project.identifier}"})
log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}");
- else
- log("\trepository #{repos_path} not registered in Redmine. Look in your log to find why.");
+ rescue => e
+ log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
end
end
diff --git a/lang/bg.yml b/lang/bg.yml
index 7c171a461..0079c4ab2 100644
--- a/lang/bg.yml
+++ b/lang/bg.yml
@@ -640,5 +640,70 @@ setting_sequential_project_identifiers: Generate sequential project identifiers
notice_unable_delete_version: Unable to delete version
label_renamed: renamed
label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+setting_plain_text_mail: plain text only (no HTML)
+permission_view_files: View files
+permission_edit_issues: Edit issues
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_public_queries: Manage public queries
+permission_add_issues: Add issues
+permission_log_time: Log spent time
+permission_view_changesets: View changesets
+permission_view_time_entries: View spent time
+permission_manage_versions: Manage versions
+permission_manage_wiki: Manage wiki
+permission_manage_categories: Manage issue categories
+permission_protect_wiki_pages: Protect wiki pages
+permission_comment_news: Comment news
+permission_delete_messages: Delete messages
+permission_select_project_modules: Select project modules
+permission_manage_documents: Manage documents
+permission_edit_wiki_pages: Edit wiki pages
+permission_add_issue_watchers: Add watchers
+permission_view_gantt: View gantt chart
+permission_move_issues: Move issues
+permission_manage_issue_relations: Manage issue relations
+permission_delete_wiki_pages: Delete wiki pages
+permission_manage_boards: Manage boards
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_view_wiki_edits: View wiki history
+permission_add_messages: Post messages
+permission_view_messages: View messages
+permission_manage_files: Manage files
+permission_edit_issue_notes: Edit notes
+permission_manage_news: Manage news
+permission_view_calendar: View calendrier
+permission_manage_members: Manage members
+permission_edit_messages: Edit messages
+permission_delete_issues: Delete issues
+permission_view_issue_watchers: View watchers list
+permission_manage_repository: Manage repository
+permission_commit_access: Commit access
+permission_browse_repository: Browse repository
+permission_view_documents: View documents
+permission_edit_project: Edit project
+permission_add_issue_notes: Add notes
+permission_save_queries: Save queries
+permission_view_wiki_pages: View wiki
+permission_rename_wiki_pages: Rename wiki pages
+permission_edit_time_entries: Edit time logs
+permission_edit_own_issue_notes: Edit own notes
+setting_gravatar_enabled: Use Gravatar user icons
+label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+permission_edit_own_messages: Edit own messages
+permission_delete_own_messages: Delete own messages
+label_user_activity: "%s's activity"
+label_updated_time_by: Updated by %s %s ago
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/ca.yml b/lang/ca.yml
index 0641d781b..770cc305c 100644
--- a/lang/ca.yml
+++ b/lang/ca.yml
@@ -77,6 +77,7 @@ notice_failed_to_save_issues: "No s'han pogut desar %s assumptes de %d seleccion
notice_no_issue_selected: "No s'ha seleccionat cap assumpte. Activeu els assumptes que voleu editar."
notice_account_pending: "S'ha creat el compte i ara està pendent de l'aprovació de l'administrador."
notice_default_data_loaded: "S'ha carregat correctament la configuració predeterminada."
+notice_unable_delete_version: "No s'ha pogut suprimir la versió."
error_can_t_load_default_data: "No s'ha pogut carregar la configuració predeterminada: %s"
error_scm_not_found: "No s'ha trobat l'entrada o la revisió en el dipòsit."
@@ -163,7 +164,7 @@ field_start_date: Inici
field_done_ratio: %% realitzat
field_auth_source: "Mode d'autenticació"
field_hide_mail: "Oculta l'adreça de correu electrònic"
-field_comment: Comentari
+field_comments: Comentari
field_url: URL
field_start_page: Pàgina inicial
field_subproject: Subprojecte
@@ -209,6 +210,7 @@ setting_time_format: Format de hora
setting_cross_project_issue_relations: "Permet les relacions d'assumptes entre projectes"
setting_issue_list_default_columns: "Columnes mostrades per defecte en la llista d'assumptes"
setting_repositories_encodings: Codificacions del dipòsit
+setting_commit_logs_encoding: Codificació dels missatges publicats
setting_emails_footer: Peu dels correus electrònics
setting_protocol: Protocol
setting_per_page_options: Opcions dels objectes per pàgina
@@ -218,6 +220,7 @@ setting_display_subprojects_issues: "Mostra els assumptes d'un subprojecte en el
setting_enabled_scm: "Habilita l'SCM"
setting_mail_handler_api_enabled: "Habilita el WS per correus electrònics d'entrada"
setting_mail_handler_api_key: Clau API
+setting_sequential_project_identifiers: Genera identificadors de projecte seqüencials
project_module_issue_tracking: "Seguidor d'assumptes"
project_module_time_tracking: Seguidor de temps
@@ -402,6 +405,8 @@ label_revision_plural: Revisions
label_associated_revisions: Revisions associades
label_added: afegit
label_modified: modificat
+label_renamed: reanomenat
+label_copied: copiat
label_deleted: suprimit
label_latest_revision: Última revisió
label_latest_revision_plural: Últimes revisions
@@ -462,7 +467,7 @@ label_end_to_start: final al començament
label_end_to_end: final al final
label_start_to_start: començament al començament
label_start_to_end: començament al final
-label_stay_logged_in: "Manté l'entrada en"
+label_stay_logged_in: "Manté l'entrada"
label_disabled: inhabilitat
label_show_completed_versions: Mostra les versions completes
label_me: jo mateix
@@ -560,6 +565,7 @@ button_copy: Copia
button_annotate: Anota
button_update: Actualitza
button_configure: Configura
+button_quote: Cita
status_active: actiu
status_registered: informat
@@ -635,12 +641,70 @@ default_activity_development: Desenvolupament
enumeration_issue_priorities: Prioritat dels assumptes
enumeration_doc_categories: Categories del document
enumeration_activities: Activitats (seguidor de temps)
-button_quote: Quote
-setting_sequential_project_identifiers: Generate sequential project identifiers
-notice_unable_delete_version: Unable to delete version.
-field_comments: Comment
-setting_commit_logs_encoding: Commit messages encoding
-label_renamed: renamed
-label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+setting_plain_text_mail: plain text only (no HTML)
+permission_view_files: View files
+permission_edit_issues: Edit issues
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_public_queries: Manage public queries
+permission_add_issues: Add issues
+permission_log_time: Log spent time
+permission_view_changesets: View changesets
+permission_view_time_entries: View spent time
+permission_manage_versions: Manage versions
+permission_manage_wiki: Manage wiki
+permission_manage_categories: Manage issue categories
+permission_protect_wiki_pages: Protect wiki pages
+permission_comment_news: Comment news
+permission_delete_messages: Delete messages
+permission_select_project_modules: Select project modules
+permission_manage_documents: Manage documents
+permission_edit_wiki_pages: Edit wiki pages
+permission_add_issue_watchers: Add watchers
+permission_view_gantt: View gantt chart
+permission_move_issues: Move issues
+permission_manage_issue_relations: Manage issue relations
+permission_delete_wiki_pages: Delete wiki pages
+permission_manage_boards: Manage boards
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_view_wiki_edits: View wiki history
+permission_add_messages: Post messages
+permission_view_messages: View messages
+permission_manage_files: Manage files
+permission_edit_issue_notes: Edit notes
+permission_manage_news: Manage news
+permission_view_calendar: View calendrier
+permission_manage_members: Manage members
+permission_edit_messages: Edit messages
+permission_delete_issues: Delete issues
+permission_view_issue_watchers: View watchers list
+permission_manage_repository: Manage repository
+permission_commit_access: Commit access
+permission_browse_repository: Browse repository
+permission_view_documents: View documents
+permission_edit_project: Edit project
+permission_add_issue_notes: Add notes
+permission_save_queries: Save queries
+permission_view_wiki_pages: View wiki
+permission_rename_wiki_pages: Rename wiki pages
+permission_edit_time_entries: Edit time logs
+permission_edit_own_issue_notes: Edit own notes
+setting_gravatar_enabled: Use Gravatar user icons
+label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+permission_edit_own_messages: Edit own messages
+permission_delete_own_messages: Delete own messages
+label_user_activity: "%s's activity"
+label_updated_time_by: Updated by %s %s ago
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/cs.yml b/lang/cs.yml
index 29babd3a9..bbfdfae4d 100644
--- a/lang/cs.yml
+++ b/lang/cs.yml
@@ -645,5 +645,70 @@ setting_sequential_project_identifiers: Generate sequential project identifiers
notice_unable_delete_version: Unable to delete version
label_renamed: renamed
label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+setting_plain_text_mail: plain text only (no HTML)
+permission_view_files: View files
+permission_edit_issues: Edit issues
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_public_queries: Manage public queries
+permission_add_issues: Add issues
+permission_log_time: Log spent time
+permission_view_changesets: View changesets
+permission_view_time_entries: View spent time
+permission_manage_versions: Manage versions
+permission_manage_wiki: Manage wiki
+permission_manage_categories: Manage issue categories
+permission_protect_wiki_pages: Protect wiki pages
+permission_comment_news: Comment news
+permission_delete_messages: Delete messages
+permission_select_project_modules: Select project modules
+permission_manage_documents: Manage documents
+permission_edit_wiki_pages: Edit wiki pages
+permission_add_issue_watchers: Add watchers
+permission_view_gantt: View gantt chart
+permission_move_issues: Move issues
+permission_manage_issue_relations: Manage issue relations
+permission_delete_wiki_pages: Delete wiki pages
+permission_manage_boards: Manage boards
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_view_wiki_edits: View wiki history
+permission_add_messages: Post messages
+permission_view_messages: View messages
+permission_manage_files: Manage files
+permission_edit_issue_notes: Edit notes
+permission_manage_news: Manage news
+permission_view_calendar: View calendrier
+permission_manage_members: Manage members
+permission_edit_messages: Edit messages
+permission_delete_issues: Delete issues
+permission_view_issue_watchers: View watchers list
+permission_manage_repository: Manage repository
+permission_commit_access: Commit access
+permission_browse_repository: Browse repository
+permission_view_documents: View documents
+permission_edit_project: Edit project
+permission_add_issue_notes: Add notes
+permission_save_queries: Save queries
+permission_view_wiki_pages: View wiki
+permission_rename_wiki_pages: Rename wiki pages
+permission_edit_time_entries: Edit time logs
+permission_edit_own_issue_notes: Edit own notes
+setting_gravatar_enabled: Use Gravatar user icons
+label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+permission_edit_own_messages: Edit own messages
+permission_delete_own_messages: Delete own messages
+label_user_activity: "%s's activity"
+label_updated_time_by: Updated by %s %s ago
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/da.yml b/lang/da.yml
index 92f5501a2..c07d6be88 100644
--- a/lang/da.yml
+++ b/lang/da.yml
@@ -30,62 +30,58 @@ activerecord_error_too_long: er for lang
activerecord_error_too_short: er for kort
activerecord_error_wrong_length: har den forkerte længde
activerecord_error_taken: er allerede valgt
-activerecord_error_not_a_number: er ikke et tal
+activerecord_error_not_a_number: er ikke et nummer
activerecord_error_not_a_date: er en ugyldig dato
activerecord_error_greater_than_start_date: skal være senere end startdatoen
-activerecord_error_not_same_project: hører ikke til samme projekt
-activerecord_error_circular_dependency: Denne relation vil skabe en cirkulær afhængighed
+activerecord_error_not_same_project: høre ikke til samme projekt
+activerecord_error_circular_dependency: Denne relation vil skabe et afhængighedsforhold
general_fmt_age: %d år
general_fmt_age_plural: %d år
-general_fmt_date: %%m/%%d/%%Y
-general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p
-general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
-general_fmt_time: %%I:%%M %%p
+general_fmt_date: %%d/%%m/%%Y
+general_fmt_datetime: %%d/%%m/%%Y %%H:%%M
+general_fmt_datetime_short: %%b %%d, %%H:%%M
+general_fmt_time: %%H:%%M
general_text_No: 'Nej'
general_text_Yes: 'Ja'
general_text_no: 'nej'
general_text_yes: 'ja'
-general_lang_name: 'Dansk'
+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
general_first_day_of_week: '1'
notice_account_updated: Kontoen er opdateret.
-notice_account_invalid_creditentials: Forkert brugernavn eller kodeord
+notice_account_invalid_creditentials: Ugyldig bruger og kodeord
notice_account_password_updated: Kodeordet er opdateret.
notice_account_wrong_password: Forkert kodeord
-notice_account_register_done: Kontoen er oprettet. For at aktivere kontoen, skal du klikke på linket i den tilsendte e-mail.
-notice_account_unknown_email: Ukendt brugernavn.
+notice_account_register_done: Kontoen er oprettet. For at aktivere kontoen skal du klikke på linket i den tilsendte email.
+notice_account_unknown_email: Ukendt bruger.
notice_can_t_change_password: Denne konto benytter en ekstern sikkerhedsgodkendelse. Det er ikke muligt at skifte kodeord.
-notice_account_lost_email_sent: En e-mail med instruktioner til at vælge et nyt kodeord er afsendt til dig.
+notice_account_lost_email_sent: En email med instruktioner til at vælge et nyt kodeord er afsendt til dig.
notice_account_activated: Din konto er aktiveret. Du kan nu logge ind.
-notice_successful_create: Oprettelse lykkedes.
-notice_successful_update: Opdatering lykkedes.
-notice_successful_delete: Sletning lykkedes.
+notice_successful_create: Succesfuld oprettelse.
+notice_successful_update: Succesfuld opdatering.
+notice_successful_delete: Succesfuld sletning.
notice_successful_connection: Succesfuld forbindelse.
-notice_file_not_found: Siden, du forsøger at tilgå, eksisterer ikke eller er blevet fjernet.
+notice_file_not_found: Siden du forsøger at tilgå, eksisterer ikke eller er blevet fjernet.
notice_locking_conflict: Data er opdateret af en anden bruger.
notice_not_authorized: Du har ikke adgang til denne side.
-notice_email_sent: En e-mail er sendt til %s
-notice_email_error: En fejl opstod under afsendelse af e-mail (%s)
-notice_feeds_access_key_reseted: Din RSS-adgangsnøgle er nulstillet.
+notice_email_sent: En email er sendt til %s
+notice_email_error: En fejl opstod under afsendelse af email (%s)
+notice_feeds_access_key_reseted: Din adgangsnøgle til RSS er nulstillet.
notice_failed_to_save_issues: "Det mislykkedes at gemme %d sage(r) på %d valgt: %s."
-notice_no_issue_selected: "Ingen sag er valgt! Vælg venligst, hvilke emner du vil rette."
-notice_account_pending: "Din konto er oprettet og afventer administratorens godkendelse."
-notice_default_data_loaded: Standardkonfiguration er indlæst.
-notice_unable_delete_version: Kan ikke slette version
+notice_no_issue_selected: "Ingen sag er valgt! Vælg venligst hvilke emner du vil rette."
+notice_account_pending: "Din konto er oprettet, og afventer administrators godkendelse."
+notice_default_data_loaded: Standardopsætningen er indlæst.
-error_can_t_load_default_data: "Standardkonfiguration kunne ikke indlæses: %s"
-error_scm_not_found: "Fil og/eller revision blev ikke fundet i det valgte filarkiv."
-error_scm_command_failed: "En fejl opstod under forbindelsen til det valgte filarkiv: %s"
-error_scm_annotate: "Filen findes ikke eller kan ikke annoteres."
-error_issue_not_found_in_project: 'Sagen blev ikke fundet eller tilhører ikke dette projekt'
+error_can_t_load_default_data: "Standardopsætning kunne ikke indlæses: %s"
+error_scm_not_found: "Adgang og/eller revision blev ikke fundet i det valgte repository."
+error_scm_command_failed: "En fejl opstod under fobindelsen til det valgte repository: %s"
-mail_subject_lost_password: Dit kodeord til %s
+mail_subject_lost_password: Dit %s kodeord
mail_body_lost_password: 'For at ændre dit kodeord, klik på dette link:'
mail_subject_register: %s kontoaktivering
mail_body_register: 'For at aktivere din konto, klik på dette link:'
@@ -93,31 +89,29 @@ mail_body_account_information_external: Du kan bruge din "%s" konto til at logge
mail_body_account_information: Din kontoinformation
mail_subject_account_activation_request: %s kontoaktivering
mail_body_account_activation_request: 'En ny bruger (%s) er registreret. Godkend venligst kontoen:'
-mail_subject_reminder: "%d sag(er) har snart deadline"
-mail_body_reminder: "%d sag(er) der er tildelt dig har deadline i løbet af de kommende %d dage:"
gui_validation_error: 1 fejl
gui_validation_error_plural: %d fejl
field_name: Navn
field_description: Beskrivelse
-field_summary: Oversigt
+field_summary: Sammenfatning
field_is_required: Skal udfyldes
field_firstname: Fornavn
field_lastname: Efternavn
-field_mail: E-mail
+field_mail: Email
field_filename: Fil
field_filesize: Størrelse
field_downloads: Downloads
-field_author: Opretter
+field_author: Forfatter
field_created_on: Oprettet
field_updated_on: Opdateret
field_field_format: Format
field_is_for_all: For alle projekter
field_possible_values: Mulige værdier
field_regexp: Regulære udtryk
-field_min_length: Minimum længde
-field_max_length: Maksimal længde
+field_min_length: Mindste længde
+field_max_length: Største længde
field_value: Værdi
field_category: Kategori
field_title: Titel
@@ -132,18 +126,18 @@ field_subject: Emne
field_due_date: Deadline
field_assigned_to: Tildelt til
field_priority: Prioritet
-field_fixed_version: Planlagt version
+field_fixed_version: Target version
field_user: Bruger
field_role: Rolle
field_homepage: Hjemmeside
field_is_public: Offentlig
field_parent: Underprojekt af
-field_is_in_chlog: Sager vist i ændringslog
-field_is_in_roadmap: Sager vist i plan
-field_login: Brugernavn
-field_mail_notification: E-mail-notifikationer
+field_is_in_chlog: Sager vist i ændringer
+field_is_in_roadmap: Sager vist i roadmap
+field_login: Login
+field_mail_notification: Email-påmindelser
field_admin: Administrator
-field_last_login_on: Sidst logget ind
+field_last_login_on: Sidste forbindelse
field_language: Sprog
field_effective_date: Dato
field_password: Kodeord
@@ -154,16 +148,16 @@ field_type: Type
field_host: Vært
field_port: Port
field_account: Kode
-field_base_dn: Base-DN
-field_attr_login: Attribut for brugernavn
-field_attr_firstname: Attribut for fornavn
-field_attr_lastname: Attribut for efternavn
-field_attr_mail: Attribut for e-mail
-field_onthefly: Løbende brugeroprettelse
+field_base_dn: Base DN
+field_attr_login: Login attribut
+field_attr_firstname: Fornavn attribut
+field_attr_lastname: Efternavn attribut
+field_attr_mail: Email attribut
+field_onthefly: løbende brugeroprettelse
field_start_date: Start
-field_done_ratio: %% færdig
-field_auth_source: Godkendelsesmetode
-field_hide_mail: Skjul min e-mail
+field_done_ratio: %% Færdig
+field_auth_source: Sikkerhedsmetode
+field_hide_mail: Skjul min email
field_comments: Kommentar
field_url: URL
field_start_page: Startside
@@ -171,64 +165,54 @@ field_subproject: Underprojekt
field_hours: Timer
field_activity: Aktivitet
field_spent_on: Dato
-field_identifier: Identifikator
+field_identifier: Identificering
field_is_filter: Brugt som et filter
-field_issue_to_id: Relateret sag
+field_issue_to_id: Beslægtede sag
field_delay: Udsættelse
field_assignable: Sager kan tildeles denne rolle
field_redirect_existing_links: Videresend eksisterende links
-field_estimated_hours: Tidsestimat
+field_estimated_hours: Anslået tid
field_column_names: Kolonner
field_time_zone: Tidszone
field_searchable: Søgbar
field_default_value: Standardværdi
-field_comments_sorting: Vis kommentarer
-field_parent_title: Forælderside
-setting_app_title: Applikationens titel
-setting_app_subtitle: Applikationes undertitel
+setting_app_title: Applikationstitel
+setting_app_subtitle: Applikationsundertekst
setting_welcome_text: Velkomsttekst
-setting_default_language: Standardsprog
-setting_login_required: Indlogning påkrævet
+setting_default_language: Standardsporg
+setting_login_required: Sikkerhed påkrævet
setting_self_registration: Brugeroprettelse
-setting_attachment_max_size: Maks. størrelse for vedhæftede filer
-setting_issues_export_limit: Maks. antal sager i eksport
-setting_mail_from: Afsender-e-mail
-setting_bcc_recipients: Blindkopimodtager (bcc)
-setting_host_name: Værtsnavn
+setting_attachment_max_size: Vedhæftede filers max størrelse
+setting_issues_export_limit: Sagseksporteringsbegrænsning
+setting_mail_from: Afsender-email
+setting_bcc_recipients: Skjult modtager (bcc)
+setting_host_name: Værts navn
setting_text_formatting: Tekstformatering
-setting_wiki_compression: Komprimer wiki-historik
-setting_feeds_limit: Antal objekter i feeds
-setting_default_projects_public: Nye projekter er som standard offentlige
-setting_autofetch_changesets: Hent automatisk commits
-setting_sys_api_enabled: Aktiver webservice til versionsstyring
-setting_commit_ref_keywords: Nøgleord for sagsreferencer
-setting_commit_fix_keywords: Nøgleord for lukning af sager
+setting_wiki_compression: Wiki historikkomprimering
+setting_feeds_limit: Feed indholdsbegrænsning
+setting_autofetch_changesets: Automatisk hent commits
+setting_sys_api_enabled: Aktiver webservice for automatisk administration af repository
+setting_commit_ref_keywords: Referencenøgleord
+setting_commit_fix_keywords: Afslutningsnøgleord
setting_autologin: Autologin
setting_date_format: Datoformat
setting_time_format: Tidsformat
setting_cross_project_issue_relations: Tillad sagsrelationer på tværs af projekter
setting_issue_list_default_columns: Standardkolonner på sagslisten
-setting_repositories_encodings: Filarkivtegnsæt
-setting_commit_logs_encoding: Tegnsæt for commitbeskeder
-setting_emails_footer: Sidefod i e-mail
+setting_repositories_encodings: Repository-tegnsæt
+setting_emails_footer: Email-fodnote
setting_protocol: Protokol
-setting_per_page_options: Valgmuligheder for antal objekter pr. side
+setting_per_page_options: Objekter pr. side-indstillinger
setting_user_format: Brugervisningsformat
-setting_activity_days_default: Antal dage der vises under projektaktivitet
-setting_display_subprojects_issues: Vis som standard sager for underprojekter på hovedprojektet
-setting_enabled_scm: Aktiveret versionsstyring
-setting_mail_handler_api_enabled: Aktiver redigering af sager via mail
-setting_mail_handler_api_key: API-nøgle
-setting_sequential_project_identifiers: Generer fortløbende identifikatorer
project_module_issue_tracking: Sagssøgning
-project_module_time_tracking: Tidsregistrering
+project_module_time_tracking: Tidsstyring
project_module_news: Nyheder
project_module_documents: Dokumenter
project_module_files: Filer
project_module_wiki: Wiki
-project_module_repository: Versionsstyring
+project_module_repository: Repository
project_module_boards: Opslagstavle
label_user: Bruger
@@ -240,16 +224,16 @@ label_project_plural: Projekter
label_project_all: Alle projekter
label_project_latest: Seneste projekter
label_issue: Sag
-label_issue_new: Ny sag
+label_issue_new: Opret sag
label_issue_plural: Sager
label_issue_view_all: Vis alle sager
label_issues_by: Sager fra %s
-label_issue_added: Sag oprettet
-label_issue_updated: Sag opdateret
+label_issue_added: Sagen er oprettet
+label_issue_updated: Sagen er opdateret
label_document: Dokument
label_document_new: Nyt dokument
label_document_plural: Dokumenter
-label_document_added: Dokument oprettet
+label_document_added: Dokument tilføjet
label_role: Rolle
label_role_plural: Roller
label_role_new: Ny rolle
@@ -261,20 +245,20 @@ label_tracker: Type
label_tracker_plural: Typer
label_tracker_new: Ny type
label_workflow: Arbejdsgang
-label_issue_status: Statuskode
-label_issue_status_plural: Statuskoder
-label_issue_status_new: Ny statuskode
+label_issue_status: Sagsstatus
+label_issue_status_plural: Sagsstatuser
+label_issue_status_new: Ny status
label_issue_category: Sagskategori
label_issue_category_plural: Sagskategorier
label_issue_category_new: Ny kategori
label_custom_field: Brugerdefineret felt
-label_custom_field_plural: Brugerdefinerede felter
+label_custom_field_plural: Brugerdefineret felt
label_custom_field_new: Nyt brugerdefineret felt
label_enumerations: Værdier
label_enumeration_new: Ny værdi
label_information: Information
label_information_plural: Information
-label_please_login: Log venligst ind
+label_please_login: Login
label_register: Registrer
label_password_lost: Glemt kodeord
label_home: Forside
@@ -286,23 +270,21 @@ label_login: Log ind
label_logout: Log ud
label_help: Hjælp
label_reported_issues: Rapporterede sager
-label_assigned_to_me_issues: Sager tildelt mig
-label_last_login: Sidste indlogning
+label_assigned_to_me_issues: Sager tildelt til mig
+label_last_login: Sidste forbindelse
label_last_updates: Sidst opdateret
label_last_updates_plural: %d sidst opdateret
-label_registered_on: Oprettet den
+label_registered_on: Registeret den
label_activity: Aktivitet
-label_overall_activity: Al aktivitet
label_new: Ny
-label_logged_as: Logget ind som
+label_logged_as: Registreret som
label_environment: Miljø
-label_authentication: Godkendelse
-label_auth_source: Godkendelsesmetode
-label_auth_source_new: Ny godkendelsemetode
-label_auth_source_plural: Godkendelsesmetoder
+label_authentication: Sikkerhed
+label_auth_source: Sikkerhedsmetode
+label_auth_source_new: Ny sikkerhedsmetode
+label_auth_source_plural: Sikkerhedsmetoder
label_subproject_plural: Underprojekter
-label_and_its_subprojects: Projektet %s og dets underprojekter
-label_min_max_length: Min.-maks.-længde
+label_min_max_length: Min - Max længde
label_list: Liste
label_date: Dato
label_integer: Heltal
@@ -312,8 +294,8 @@ label_string: Tekst
label_text: Lang tekst
label_attribute: Attribut
label_attribute_plural: Attributter
-label_download: %d download
-label_download_plural: %d downloads
+label_download: %d Download
+label_download_plural: %d Downloads
label_no_data: Ingen data at vise
label_change_status: Ændringsstatus
label_history: Historik
@@ -321,22 +303,22 @@ label_attachment: Fil
label_attachment_new: Ny fil
label_attachment_delete: Slet fil
label_attachment_plural: Filer
-label_file_added: Fil oprettet
+label_file_added: Fil tilføjet
label_report: Rapport
label_report_plural: Rapporter
label_news: Nyheder
-label_news_new: Ny nyhed
+label_news_new: Tilføj nyheder
label_news_plural: Nyheder
label_news_latest: Seneste nyheder
label_news_view_all: Vis alle nyheder
-label_news_added: Nyhed oprettet
+label_news_added: Nyhed tilføjet
label_change_log: Ændringer
label_settings: Indstillinger
label_overview: Oversigt
label_version: Version
label_version_new: Ny version
label_version_plural: Versioner
-label_confirmation: Bekræftelser
+label_confirmation: Bekræftigelser
label_export_to: Eksporter til
label_read: Læs...
label_public_projects: Offentlige projekter
@@ -347,15 +329,15 @@ label_closed_issues_plural: lukkede
label_total: Total
label_permissions: Rettigheder
label_current_status: Nuværende status
-label_new_statuses_allowed: Tilladte nye statuskoder
+label_new_statuses_allowed: Ny status tilladt
label_all: alle
label_none: intet
label_nobody: ingen
label_next: Næste
-label_previous: Forrige
+label_previous: Forrig
label_used_by: Brugt af
label_details: Detaljer
-label_add_note: Ny note
+label_add_note: Tilføj en note
label_per_page: Pr. side
label_calendar: Kalender
label_months_from: måneder frem
@@ -367,11 +349,11 @@ label_personalize_page: Tilret denne side
label_comment: Kommentar
label_comment_plural: Kommentarer
label_comment_add: Tilføj en kommentar
-label_comment_added: Kommentar tilføjet
+label_comment_added: Kommentaren er tilføjet
label_comment_delete: Slet kommentar
-label_query: Brugerdefineret søgning
-label_query_plural: Brugerdefinerede søgning
-label_query_new: Ny søgning
+label_query: Brugerdefineret forespørgsel
+label_query_plural: Brugerdefinerede forespørgsler
+label_query_new: Ny forespørgsel
label_filter_add: Tilføj filter
label_filter_plural: Filtre
label_equals: er
@@ -388,25 +370,23 @@ label_last_n_days: sidste %d dage
label_this_month: denne måned
label_last_month: sidste måned
label_this_year: dette år
-label_date_range: Datointerval
+label_date_range: Dato interval
label_less_than_ago: mindre end dage siden
label_more_than_ago: mere end dage siden
label_ago: days siden
label_contains: indeholder
-label_not_contains: indeholder ikke
+label_not_contains: ikke indeholder
label_day_plural: dage
-label_repository: Versionsstyring
-label_repository_plural: Versionsstyring
+label_repository: Repository
+label_repository_plural: Repositories
label_browse: Gennemse
label_modification: %d ændring
label_modification_plural: %d ændringer
label_revision: Revision
label_revision_plural: Revisioner
label_associated_revisions: Tilnyttede revisioner
-label_added: oprettet
+label_added: tilføjet
label_modified: ændret
-label_copied: kopieret
-label_renamed: omdøbt
label_deleted: slettet
label_latest_revision: Seneste revision
label_latest_revision_plural: Seneste revisioner
@@ -417,48 +397,47 @@ label_sort_highest: Flyt til toppen
label_sort_higher: Flyt op
label_sort_lower: Flyt ned
label_sort_lowest: Flyt til bunden
-label_roadmap: Plan
-label_roadmap_due_in: Deadline %s
+label_roadmap: Roadmap
+label_roadmap_due_in: Deadline
label_roadmap_overdue: %s forsinket
-label_roadmap_no_issues: Ingen sager i denne version
+label_roadmap_no_issues: Ingen sager til denne version
label_search: Søg
label_result_plural: Resultater
label_all_words: Alle ord
label_wiki: Wiki
-label_wiki_edit: Wikiændring
-label_wiki_edit_plural: Wikiændringer
-label_wiki_page: Wikiside
-label_wiki_page_plural: Wikisider
+label_wiki_edit: Wiki ændring
+label_wiki_edit_plural: Wiki ændringer
+label_wiki_page: Wiki side
+label_wiki_page_plural: Wiki sider
label_index_by_title: Indhold efter titel
label_index_by_date: Indhold efter dato
label_current_version: Nuværende version
label_preview: Forhåndsvisning
label_feed_plural: Feeds
-label_changes_details: Detaljer for alle ændringer
-label_issue_tracking: Sagsstyring
-label_spent_time: Tidsforbrug
+label_changes_details: Detaljer for alle ænringer
+label_issue_tracking: Sags søgning
+label_spent_time: Brugt tid
label_f_hour: %.2f time
label_f_hour_plural: %.2f timer
-label_time_tracking: Tidsregistrering
+label_time_tracking: Tidsstyring
label_change_plural: Ændringer
label_statistics: Statistik
label_commits_per_month: Commits pr. måned
label_commits_per_author: Commits pr. bruger
-label_view_diff: Vis ændringer
+label_view_diff: Vis forskellighed
label_diff_inline: inline
label_diff_side_by_side: side om side
label_options: Optioner
label_copy_workflow_from: Kopier arbejdsgang fra
-label_permissions_report: Rettighedsoversigt
+label_permissions_report: Godkendelsesrapport
label_watched_issues: Overvågede sager
label_related_issues: Relaterede sager
-label_applied_status: Tildelt status
+label_applied_status: Anvendte statuser
label_loading: Indlæser...
label_relation_new: Ny relation
label_relation_delete: Slet relation
-label_relates_to: er relateret til
-label_duplicates: dublerer
-label_duplicated_by: dubleret af
+label_relates_to: relaterer til
+label_duplicates: kopierer
label_blocks: blokerer
label_blocked_by: blokeret af
label_precedes: kommer før
@@ -476,11 +455,11 @@ label_board_new: Nyt forum
label_board_plural: Fora
label_topic_plural: Emner
label_message_plural: Beskeder
-label_message_last: Seneste besked
+label_message_last: Sidste besked
label_message_new: Ny besked
-label_message_posted: Besked oprettet
+label_message_posted: Besked tilføjet
label_reply_plural: Besvarer
-label_send_information: Send kontoinformation til bruger
+label_send_information: Send konto information til bruger
label_year: Ã…r
label_month: MÃ¥ned
label_week: Uge
@@ -488,57 +467,48 @@ label_date_from: Fra
label_date_to: Til
label_language_based: Baseret på brugerens sprog
label_sort_by: Sorter efter %s
-label_send_test_email: Send en testmail
-label_feeds_access_key_created_on: RSS-adgangsnøgle genereret for %s siden
+label_send_test_email: Send en test email
+label_feeds_access_key_created_on: RSS adgangsnøgle danet for %s siden
label_module_plural: Moduler
-label_added_time_by: Oprettet af %s for %s siden
+label_added_time_by: Tilføjet af %s for %s siden
label_updated_time: Opdateret for %s siden
label_jump_to_a_project: Skift til projekt...
label_file_plural: Filer
label_changeset_plural: Ændringer
-label_default_columns: Standardkolonner
+label_default_columns: Standard kolonner
label_no_change_option: (Ingen ændringer)
label_bulk_edit_selected_issues: Masseret de valgte sager
label_theme: Tema
label_default: standard
label_search_titles_only: Søg kun i titler
label_user_mail_option_all: "For alle hændelser på mine projekter"
-label_user_mail_option_selected: "For alle hændelser på udvalgte projekter..."
-label_user_mail_option_none: "Kun for ting jeg overvåger eller er involveret i"
-label_user_mail_no_self_notified: "Jeg ønsker ikke at blive notificeret om ændringer foretaget af mig selv"
-label_registration_activation_by_email: kontoaktivering på e-mail
+label_user_mail_option_selected: "For alle hændelser, kun på de valgte projekter..."
+label_user_mail_option_none: "Kun for ting jeg overvåger, eller jeg er involveret i"
+label_user_mail_no_self_notified: "Jeg ønsker ikke besked, om ændring foretaget af mig selv"
+label_registration_activation_by_email: kontoaktivering på email
label_registration_manual_activation: manuel kontoaktivering
label_registration_automatic_activation: automatisk kontoaktivering
-label_display_per_page: 'Pr. side: %s'
+label_display_per_page: 'Per side: %s'
label_age: Alder
label_change_properties: Ændre indstillinger
-label_general: Generelt
+label_general: Generalt
label_more: Mere
-label_scm: Versionsstyring
+label_scm: SCM
label_plugins: Plugins
label_ldap_authentication: LDAP-godkendelse
label_downloads_abbr: D/L
-label_add_another_file: Opret endnu en fil
-label_optional_description: Valgfri beskrivelse
-label_preferences: Indstillinger
-label_chronological_order: I kronologisk rækkefølge
-label_reverse_chronological_order: I omvendt kronologisk rækkefølge
-label_planning: Planlægning
-label_incoming_emails: Indkommende e-mails
-label_generate_key: Generer en nøgle
-label_issue_watchers: Overvågere
-button_login: Log ind
+button_login: Login
button_submit: Send
button_save: Gem
-button_check_all: Vælg alle
-button_uncheck_all: Fravælg alle
+button_check_all: Vælg alt
+button_uncheck_all: Fravælg alt
button_delete: Slet
button_create: Opret
button_test: Test
button_edit: Ret
-button_add: Opret
-button_change: Skift
+button_add: Tilføj
+button_change: Ændre
button_apply: Anvend
button_clear: Nulstil
button_lock: LÃ¥s
@@ -551,8 +521,8 @@ button_back: Tilbage
button_cancel: Annuller
button_activate: Aktiver
button_sort: Sorter
-button_log_time: Registrer tid
-button_rollback: Rul tilbage til denne version
+button_log_time: Log tid
+button_rollback: Tilbagefør til denne version
button_watch: Overvåg
button_unwatch: Stop overvågning
button_reply: Besvar
@@ -565,56 +535,46 @@ button_copy: Kopier
button_annotate: Annotere
button_update: Opdater
button_configure: Konfigurer
-button_quote: Citer
status_active: aktiv
status_registered: registreret
status_locked: låst
-text_select_mail_notifications: Vælg handlinger for hvilke, der skal sendes en e-mail-notifikation.
+text_select_mail_notifications: Vælg handlinger der skal sendes email besked for.
text_regexp_info: f.eks. ^[A-ZÆØÅ0-9]+$
text_min_max_length_info: 0 betyder ingen begrænsninger
-text_project_destroy_confirmation: Er du sikker på, at du vil slette dette projekt og alle relaterede data ?
-text_subprojects_destroy_warning: 'Dets underprojekt(er): %s vil også blive slettet.'
+text_project_destroy_confirmation: Er du sikker på at du vil slette dette projekt og alle relaterede data?
text_workflow_edit: Vælg en rolle samt en type for at redigere arbejdsgangen
text_are_you_sure: Er du sikker?
text_journal_changed: ændret fra %s til %s
text_journal_set_to: sat til %s
text_journal_deleted: slettet
text_tip_task_begin_day: opgaven begynder denne dag
-text_tip_task_end_day: opgaven slutter denne dag
+text_tip_task_end_day: opaven slutter denne dag
text_tip_task_begin_end_day: opgaven begynder og slutter denne dag
-text_project_identifier_info: 'Små bogstaver (a-z), numre og bindestreg er tilladt.<br />Når den er gemt, kan identifikatoren ikke rettes.'
-text_caracters_maximum: maks. %d tegn.
-text_caracters_minimum: Skal være mindst %d tegn lang.
-text_length_between: Længde skal være mellem %d og %d tegn.
+text_project_identifier_info: 'Små bogstaver (a-z), numre og bindestreg er tilladt.<br />Når den er gemt, kan indifikatoren ikke rettes.'
+text_caracters_maximum: max %d karakterer.
+text_caracters_minimum: Skal være mindst %d karakterer lang.
+text_length_between: Længde skal være mellem %d og %d karakterer.
text_tracker_no_workflow: Ingen arbejdsgang defineret for denne type
-text_unallowed_characters: Ugyldige tegn
-text_comma_separated: Flere værdier tilladt (adskilt af komma).
-text_issues_ref_in_commit_messages: Referer og luk sager i commitbeskeder
+text_unallowed_characters: Ikke tilladte karakterer
+text_comma_separated: Adskillige værdier tilladt (adskilt med komma).
+text_issues_ref_in_commit_messages: Referer og løser sager i commit-beskeder
text_issue_added: Sag %s er rapporteret af %s.
text_issue_updated: Sag %s er blevet opdateret af %s.
-text_wiki_destroy_confirmation: Er du sikker på, at du vil slette denne wiki og alt dens indhold?
+text_wiki_destroy_confirmation: Er du sikker på at du vil slette denne wiki og dens indhold?
text_issue_category_destroy_question: Nogle sager (%d) er tildelt denne kategori. Hvad ønsker du at gøre?
-text_issue_category_destroy_assignments: Slet kategoritildelinger
+text_issue_category_destroy_assignments: Slet tildelinger af kategori
text_issue_category_reassign_to: Tildel sager til denne kategori
-text_user_mail_option: "For ikke-valgte projekter vil du kun modtage notifikationer omhandlende ting, du er involveret i eller overvåger (f.eks. sager du har oprettet eller er tildelt)."
-text_no_configuration_data: "Roller, typer, statuskoder og arbejdsgange er endnu ikke konfigureret.\nDet er anbefalet at indlæse standardkonfigurationen. Du vil kunne ændre denne, når den er indlæst."
-text_load_default_configuration: Indlæs standardkonfiguration
+text_user_mail_option: "For ikke-valgte projekter vil du kun modtage beskeder omhandlende ting du er involveret i eller overvåger (f.eks. sager du ha indberettet eller ejer)."
+text_no_configuration_data: "Roller, typer, sagsstatuser og arbejdsgange er endnu ikke konfigureret.\nDet er anbefalet at indlæse standardopsætningen. Du vil kunne ændre denne når den er indlæst."
+text_load_default_configuration: Indlæs standardopsætningen
text_status_changed_by_changeset: Anvendt i ændring %s.
-text_issues_destroy_confirmation: 'Er du sikker på, du ønsker at slette den/de valgte sag(er) ?'
-text_select_project_modules: 'Vælg moduler, der skal være aktiveret for dette projekt:'
-text_default_administrator_account_changed: Standardadministratorkonto ændret
-text_file_repository_writable: Filarkiv er skrivbart
+text_issues_destroy_confirmation: 'Er du sikker på du ønsker at slette den/de valgte sag(er)?'
+text_select_project_modules: 'Vælg moduler er skal være aktiveret for dette projekt:'
+text_default_administrator_account_changed: Standard administratorkonto ændret
+text_file_repository_writable: Filarkiv er skrivbar
text_rmagick_available: RMagick tilgængelig (valgfri)
-text_destroy_time_entries_question: %.02f timer er registreret på denne sag, som du er ved at slette. Hvad vil du gøre?
-text_destroy_time_entries: Slet registrerede timer
-text_assign_time_entries_to_project: Overfør registrerede timer til projektet
-text_reassign_time_entries: 'Tilbagefør registrerede timer til denne sag igen:'
-text_user_wrote: '%s skrev:'
-text_enumeration_destroy_question: '%d objekter er tildelt denne værdi.'
-text_enumeration_category_reassign_to: 'Tildel dem denne værdi:'
-text_email_delivery_not_configured: "E-mail-afsendelse er ikke konfigureret, så notifikationer er deaktiverede.\nKonfigurer din SMTP-server i config/email.yml og genstart applikationen for at aktivere disse."
default_role_manager: Leder
default_role_developper: Udvikler
@@ -633,13 +593,119 @@ default_doc_category_tech: Teknisk dokumentation
default_priority_low: Lav
default_priority_normal: Normal
default_priority_high: Høj
-default_priority_urgent: Haster
-default_priority_immediate: Akut
+default_priority_urgent: Akut
+default_priority_immediate: Omgående
default_activity_design: Design
default_activity_development: Udvikling
enumeration_issue_priorities: Sagsprioriteter
enumeration_doc_categories: Dokumentkategorier
-enumeration_activities: Aktiviteter (tidsregistrering)
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+enumeration_activities: Aktiviteter (tidsstyring)
+
+label_add_another_file: Tilføj endnu en fil
+label_chronological_order: I kronologisk rækkefølge
+setting_activity_days_default: Antal dage der vises under projektaktivitet
+text_destroy_time_entries_question: %.02f timer er reporteret på denne sag, som du er ved at slette. Hvad vil du gøre?
+error_issue_not_found_in_project: 'Sagen blev ikke fundet eller tilhører ikke dette projekt'
+text_assign_time_entries_to_project: Tildel raporterede timer til projektet
+setting_display_subprojects_issues: Vis sager for underprojekter på hovedprojektet som standard
+label_optional_description: Optionel beskrivelse
+text_destroy_time_entries: Slet raportede timer
+field_comments_sorting: Vis kommentar
+text_reassign_time_entries: 'Tildel raportede timer til denne sag igen'
+label_reverse_chronological_order: I omvendt kronologisk rækkefølge
+label_preferences: Preferences
+label_overall_activity: Overordnet aktivitet
+setting_default_projects_public: Nye projekter er offentlige som standard
+error_scm_annotate: "The entry does not exist or can not be annotated."
+label_planning: Planlægning
+text_subprojects_destroy_warning: 'Dets underprojekter(er): %s vil også blive slettet.'
+permission_edit_issues: Edit issues
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+permission_edit_own_issue_notes: Edit own notes
+setting_enabled_scm: Enabled SCM
+button_quote: Quote
+permission_view_files: View files
+permission_add_issues: Add issues
+permission_edit_own_messages: Edit own messages
+permission_delete_own_messages: Delete own messages
+permission_manage_public_queries: Manage public queries
+permission_log_time: Log spent time
+label_renamed: renamed
+label_incoming_emails: Incoming emails
+permission_view_changesets: View changesets
+permission_manage_versions: Manage versions
+permission_view_time_entries: View spent time
+label_generate_key: Generate a key
+permission_manage_categories: Manage issue categories
+permission_manage_wiki: Manage wiki
+setting_sequential_project_identifiers: Generate sequential project identifiers
+setting_plain_text_mail: Plain text mail (no HTML)
+field_parent_title: Parent page
+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."
+permission_protect_wiki_pages: Protect wiki pages
+permission_manage_documents: Manage documents
+permission_add_issue_watchers: Add watchers
+warning_attachments_not_saved: "%d file(s) could not be saved."
+permission_comment_news: Comment news
+text_enumeration_category_reassign_to: 'Reassign them to this value:'
+permission_select_project_modules: Select project modules
+permission_view_gantt: View gantt chart
+permission_delete_messages: Delete messages
+permission_move_issues: Move issues
+permission_edit_wiki_pages: Edit wiki pages
+label_user_activity: "%s's activity"
+permission_manage_issue_relations: Manage issue relations
+label_issue_watchers: Watchers
+permission_delete_wiki_pages: Delete wiki pages
+notice_unable_delete_version: Unable to delete version.
+permission_view_wiki_edits: View wiki history
+field_editable: Editable
+mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:"
+label_duplicated_by: duplicated by
+permission_manage_boards: Manage boards
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_view_messages: View messages
+text_enumeration_destroy_question: '%d objects are assigned to this value.'
+permission_manage_files: Manage files
+permission_add_messages: Post messages
+permission_edit_issue_notes: Edit notes
+permission_manage_news: Manage news
+text_plugin_assets_writable: Plugin assets directory writable
+label_display: Display
+label_and_its_subprojects: %s and its subprojects
+permission_view_calendar: View calender
+button_create_and_continue: Create and continue
+setting_gravatar_enabled: Use Gravatar user icons
+label_updated_time_by: Updated by %s %s ago
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+text_user_wrote: '%s wrote:'
+setting_mail_handler_api_enabled: Enable WS for incoming emails
+permission_delete_issues: Delete issues
+permission_view_documents: View documents
+permission_browse_repository: Browse repository
+permission_manage_repository: Manage repository
+permission_manage_members: Manage members
+mail_subject_reminder: "%d issue(s) due in the next days"
+permission_add_issue_notes: Add notes
+permission_edit_messages: Edit messages
+permission_view_issue_watchers: View watchers list
+permission_commit_access: Commit access
+setting_mail_handler_api_key: API key
+label_example: Example
+permission_rename_wiki_pages: Rename wiki pages
+text_custom_field_possible_values_info: 'One line for each value'
+permission_view_wiki_pages: View wiki
+permission_edit_project: Edit project
+permission_save_queries: Save queries
+label_copied: copied
+setting_commit_logs_encoding: Commit messages encoding
+text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+permission_edit_time_entries: Edit time logs
+general_csv_decimal_separator: '.'
+permission_edit_own_time_entries: Edit own time logs
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/de.yml b/lang/de.yml
index dbbb1183d..366b7dd06 100644
--- a/lang/de.yml
+++ b/lang/de.yml
@@ -67,7 +67,7 @@ notice_successful_create: Erfolgreich angelegt
notice_successful_update: Erfolgreich aktualisiert.
notice_successful_delete: Erfolgreich gelöscht.
notice_successful_connection: Verbindung erfolgreich.
-notice_file_not_found: Anhang besteht nicht oder ist gelöscht worden.
+notice_file_not_found: Anhang existiert nicht oder ist gelöscht worden.
notice_locking_conflict: Datum wurde von einem anderen Benutzer geändert.
notice_not_authorized: Sie sind nicht berechtigt, auf diese Seite zuzugreifen.
notice_email_sent: Eine E-Mail wurde an %s gesendet.
@@ -80,11 +80,13 @@ notice_default_data_loaded: Die Standard-Konfiguration wurde erfolgreich geladen
notice_unable_delete_version: Die Version konnte nicht gelöscht werden
error_can_t_load_default_data: "Die Standard-Konfiguration konnte nicht geladen werden: %s"
-error_scm_not_found: Eintrag und/oder Revision besteht nicht im Projektarchiv.
+error_scm_not_found: Eintrag und/oder Revision existiert nicht im Projektarchiv.
error_scm_command_failed: "Beim Zugriff auf das Projektarchiv ist ein Fehler aufgetreten: %s"
error_scm_annotate: "Der Eintrag existiert nicht oder kann nicht annotiert werden."
error_issue_not_found_in_project: 'Das Ticket wurde nicht gefunden oder gehört nicht zu diesem Projekt.'
+warning_attachments_not_saved: "%d Datei(en) konnten nicht gespeichert werden."
+
mail_subject_lost_password: Ihr %s Kennwort
mail_body_lost_password: 'Benutzen Sie den folgenden Link, um Ihr Kennwort zu ändern:'
mail_subject_register: %s Kontoaktivierung
@@ -195,6 +197,7 @@ setting_attachment_max_size: Max. Dateigröße
setting_issues_export_limit: Max. Anzahl Tickets bei CSV/PDF-Export
setting_mail_from: E-Mail-Absender
setting_bcc_recipients: E-Mails als Blindkopie (BCC) senden
+setting_plain_text_mail: Nur reinen Text (kein HTML) senden
setting_host_name: Hostname
setting_text_formatting: Textformatierung
setting_wiki_compression: Wiki-Historie komprimieren
@@ -210,7 +213,7 @@ setting_time_format: Zeitformat
setting_cross_project_issue_relations: Ticket-Beziehungen zwischen Projekten erlauben
setting_issue_list_default_columns: Default-Spalten in der Ticket-Auflistung
setting_repositories_encodings: Kodierungen der Projektarchive
-setting_commit_logs_encoding: Kodierung der Log-Meldungen
+setting_commit_logs_encoding: Kodierung der Commit-Log-Meldungen
setting_emails_footer: E-Mail-Fußzeile
setting_protocol: Protokoll
setting_per_page_options: Objekte pro Seite
@@ -221,6 +224,57 @@ setting_enabled_scm: Aktivierte Versionskontrollsysteme
setting_mail_handler_api_enabled: Abruf eingehender E-Mails aktivieren
setting_mail_handler_api_key: API-Schlüssel
setting_sequential_project_identifiers: Fortlaufende Projektkennungen generieren
+setting_gravatar_enabled: Gravatar Benutzerbilder benutzen
+setting_diff_max_lines_displayed: Maximale Anzahl anzuzeigender Diff-Zeilen
+
+permission_edit_project: Projekt bearbeiten
+permission_select_project_modules: Projektmodule auswählen
+permission_manage_members: Mitglieder verwalten
+permission_manage_versions: Versionen verwalten
+permission_manage_categories: Ticket-Kategorien verwalten
+permission_add_issues: Tickets hinzufügen
+permission_edit_issues: Tickets bearbeiten
+permission_manage_issue_relations: Ticket-Beziehungen verwalten
+permission_add_issue_notes: Kommentare hinzufügen
+permission_edit_issue_notes: Kommentare bearbeiten
+permission_edit_own_issue_notes: Eigene Kommentare bearbeiten
+permission_move_issues: Tickets verschieben
+permission_delete_issues: Tickets löschen
+permission_manage_public_queries: Öffentliche Filter verwalten
+permission_save_queries: Filter speichern
+permission_view_gantt: Gantt-Diagramm ansehen
+permission_view_calendar: Kalender ansehen
+permission_view_issue_watchers: Liste der Beobachter ansehen
+permission_add_issue_watchers: Beobachter hinzufügen
+permission_log_time: Aufwände buchen
+permission_view_time_entries: Gebuchte Aufwände ansehen
+permission_edit_time_entries: Gebuchte Aufwände bearbeiten
+permission_edit_own_time_entries: Selbst gebuchte Aufwände bearbeiten
+permission_manage_news: News verwalten
+permission_comment_news: News kommentieren
+permission_manage_documents: Dokumente verwalten
+permission_view_documents: Dokumente ansehen
+permission_manage_files: Dateien verwalten
+permission_view_files: Dateien ansehen
+permission_manage_wiki: Wiki verwalten
+permission_rename_wiki_pages: Wiki-Seiten umbenennen
+permission_delete_wiki_pages: Wiki-Seiten löschen
+permission_view_wiki_pages: Wiki ansehen
+permission_view_wiki_edits: Wiki-Versionsgeschichte ansehen
+permission_edit_wiki_pages: Wiki-Seiten bearbeiten
+permission_delete_wiki_pages_attachments: Anhänge löschen
+permission_protect_wiki_pages: Wiki-Seiten schützen
+permission_manage_repository: Projektarchiv verwalten
+permission_browse_repository: Projektarchiv ansehen
+permission_view_changesets: Changesets ansehen
+permission_commit_access: Commit-Zugriff (über WebDAV)
+permission_manage_boards: Foren verwalten
+permission_view_messages: Forenbeiträge ansehen
+permission_add_messages: Forenbeiträge hinzufügen
+permission_edit_messages: Forenbeiträge bearbeiten
+permission_edit_own_messages: Eigene Forenbeiträge bearbeiten
+permission_delete_messages: Forenbeiträge löschen
+permission_delete_own_messages: Eigene Forenbeiträge löschen
project_module_issue_tracking: Ticket-Verfolgung
project_module_time_tracking: Zeiterfassung
@@ -293,6 +347,7 @@ label_last_updates_plural: %d zuletzt aktualisierten
label_registered_on: Angemeldet am
label_activity: Aktivität
label_overall_activity: Aktivität aller Projekte anzeigen
+label_user_activity: "Aktivität von %s"
label_new: Neu
label_logged_as: Angemeldet als
label_environment: Environment
@@ -359,7 +414,7 @@ label_add_note: Kommentar hinzufügen
label_per_page: Pro Seite
label_calendar: Kalender
label_months_from: Monate ab
-label_gantt: Gantt
+label_gantt: Gantt-Diagramm
label_internal: Intern
label_last_changes: %d letzte Änderungen
label_change_view_all: Alle Änderungen anzeigen
@@ -418,7 +473,7 @@ label_sort_higher: Eins höher
label_sort_lower: Eins tiefer
label_sort_lowest: Ans Ende
label_roadmap: Roadmap
-label_roadmap_due_in: Fällig in
+label_roadmap_due_in: Fällig in %s
label_roadmap_overdue: %s verspätet
label_roadmap_no_issues: Keine Tickets für diese Version
label_search: Suche
@@ -475,10 +530,10 @@ label_board: Forum
label_board_new: Neues Forum
label_board_plural: Foren
label_topic_plural: Themen
-label_message_plural: Nachrichten
-label_message_last: Letzte Nachricht
-label_message_new: Neue Nachricht
-label_message_posted: Forums-Beitrag hinzugefügt
+label_message_plural: Forenbeiträge
+label_message_last: Letzter Forenbeitrag
+label_message_new: Neues Thema
+label_message_posted: Forenbeitrag hinzugefügt
label_reply_plural: Antworten
label_send_information: Sende Kontoinformationen zum Benutzer
label_year: Jahr
@@ -492,6 +547,7 @@ label_send_test_email: Test-E-Mail senden
label_feeds_access_key_created_on: Atom-Zugriffsschlüssel vor %s erstellt
label_module_plural: Module
label_added_time_by: Von %s vor %s hinzugefügt
+label_updated_time_by: Von %s vor %s aktualisiert
label_updated_time: Vor %s aktualisiert
label_jump_to_a_project: Zu einem Projekt springen...
label_file_plural: Dateien
@@ -527,6 +583,7 @@ label_planning: Terminplanung
label_incoming_emails: Eingehende E-Mails
label_generate_key: Generieren
label_issue_watchers: Beobachter
+label_example: Beispiel
button_login: Anmelden
button_submit: OK
@@ -606,6 +663,7 @@ text_issues_destroy_confirmation: 'Sind Sie sicher, dass Sie die ausgewählten T
text_select_project_modules: 'Bitte wählen Sie die Module aus, die in diesem Projekt aktiviert sein sollen:'
text_default_administrator_account_changed: Administrator-Kennwort geändert
text_file_repository_writable: Verzeichnis für Dateien beschreibbar
+text_plugin_assets_writable: Verzeichnis für Plugin-Assets beschreibbar
text_rmagick_available: RMagick verfügbar (optional)
text_destroy_time_entries_question: Es wurden bereits %.02f Stunden auf dieses Ticket gebucht. Was soll mit den Aufwänden geschehen?
text_destroy_time_entries: Gebuchte Aufwände löschen
@@ -615,6 +673,8 @@ text_user_wrote: '%s schrieb:'
text_enumeration_destroy_question: '%d Objekte sind diesem Wert zugeordnet.'
text_enumeration_category_reassign_to: 'Die Objekte stattdessen diesem Wert zuordnen:'
text_email_delivery_not_configured: "Der SMTP-Server ist nicht konfiguriert und Mailbenachrichtigungen sind ausgeschaltet.\nNehmen Sie die Einstellungen für Ihren SMTP-Server in config/email.yml vor und starten Sie die Applikation neu."
+text_repository_usernames_mapping: "Bitte legen Sie die Zuordnung der Redmine-Benutzer zu den Benutzernamen der Commit-Log-Meldungen des Projektarchivs fest.\nBenutzer mit identischen Redmine- und Projektarchiv-Benutzernamen oder -E-Mail-Adressen werden automatisch zugeordnet."
+text_diff_truncated: '... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet.'
default_role_manager: Manager
default_role_developper: Entwickler
@@ -641,5 +701,12 @@ default_activity_development: Entwicklung
enumeration_issue_priorities: Ticket-Prioritäten
enumeration_doc_categories: Dokumentenkategorien
enumeration_activities: Aktivitäten (Zeiterfassung)
-field_cache: Local cache
-setting_repositories_cache_directory: Cache directory for repositories
+field_editable: Editable
+label_display: Display
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/en.yml b/lang/en.yml
index 22543af26..f4f59df22 100644
--- a/lang/en.yml
+++ b/lang/en.yml
@@ -85,6 +85,8 @@ error_scm_command_failed: "An error occurred when trying to access the repositor
error_scm_annotate: "The entry does not exist or can not be annotated."
error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
+warning_attachments_not_saved: "%d file(s) could not be saved."
+
mail_subject_lost_password: Your %s password
mail_body_lost_password: 'To change your password, click on the following link:'
mail_subject_register: Your %s account activation
@@ -92,7 +94,7 @@ mail_body_register: 'To activate your account, click on the following link:'
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_body_account_activation_request: 'A new user (%s) has registered. The 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:"
@@ -145,6 +147,7 @@ field_mail_notification: Email notifications
field_admin: Administrator
field_last_login_on: Last connection
field_language: Language
+field_identity_url: OpenID URL
field_effective_date: Date
field_password: Password
field_new_password: New password
@@ -184,7 +187,8 @@ field_searchable: Searchable
field_default_value: Default value
field_comments_sorting: Display comments
field_parent_title: Parent page
-field_cache: Local cache
+field_editable: Editable
+field_watcher: Watcher
setting_app_title: Application title
setting_app_subtitle: Application subtitle
@@ -196,7 +200,8 @@ setting_attachment_max_size: Attachment max. size
setting_issues_export_limit: Issues export limit
setting_mail_from: Emission email address
setting_bcc_recipients: Blind carbon copy recipients (bcc)
-setting_host_name: Host name
+setting_plain_text_mail: Plain text mail (no HTML)
+setting_host_name: Host name and path
setting_text_formatting: Text formatting
setting_wiki_compression: Wiki history compression
setting_feeds_limit: Feed content limit
@@ -222,7 +227,59 @@ setting_enabled_scm: Enabled SCM
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key
setting_sequential_project_identifiers: Generate sequential project identifiers
-setting_repositories_cache_directory: Cache directory for repositories
+setting_gravatar_enabled: Use Gravatar user icons
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+setting_openid: Allow OpenID login and registration
+
+permission_edit_project: Edit project
+permission_select_project_modules: Select project modules
+permission_manage_members: Manage members
+permission_manage_versions: Manage versions
+permission_manage_categories: Manage issue categories
+permission_add_issues: Add issues
+permission_edit_issues: Edit issues
+permission_manage_issue_relations: Manage issue relations
+permission_add_issue_notes: Add notes
+permission_edit_issue_notes: Edit notes
+permission_edit_own_issue_notes: Edit own notes
+permission_move_issues: Move issues
+permission_delete_issues: Delete issues
+permission_manage_public_queries: Manage public queries
+permission_save_queries: Save queries
+permission_view_gantt: View gantt chart
+permission_view_calendar: View calender
+permission_view_issue_watchers: View watchers list
+permission_add_issue_watchers: Add watchers
+permission_log_time: Log spent time
+permission_view_time_entries: View spent time
+permission_edit_time_entries: Edit time logs
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_news: Manage news
+permission_comment_news: Comment news
+permission_manage_documents: Manage documents
+permission_view_documents: View documents
+permission_manage_files: Manage files
+permission_view_files: View files
+permission_manage_wiki: Manage wiki
+permission_rename_wiki_pages: Rename wiki pages
+permission_delete_wiki_pages: Delete wiki pages
+permission_view_wiki_pages: View wiki
+permission_view_wiki_edits: View wiki history
+permission_edit_wiki_pages: Edit wiki pages
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_protect_wiki_pages: Protect wiki pages
+permission_manage_repository: Manage repository
+permission_browse_repository: Browse repository
+permission_view_changesets: View changesets
+permission_commit_access: Commit access
+permission_manage_boards: Manage boards
+permission_view_messages: View messages
+permission_add_messages: Post messages
+permission_edit_messages: Edit messages
+permission_edit_own_messages: Edit own messages
+permission_delete_messages: Delete messages
+permission_delete_own_messages: Delete own messages
project_module_issue_tracking: Issue tracking
project_module_time_tracking: Time tracking
@@ -278,6 +335,7 @@ label_information: Information
label_information_plural: Information
label_please_login: Please log in
label_register: Register
+label_login_with_open_id_option: or login with OpenID
label_password_lost: Lost password
label_home: Home
label_my_page: My page
@@ -295,6 +353,7 @@ label_last_updates_plural: %d last updated
label_registered_on: Registered on
label_activity: Activity
label_overall_activity: Overall activity
+label_user_activity: "%s's activity"
label_new: New
label_logged_as: Logged in as
label_environment: Environment
@@ -494,6 +553,7 @@ 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_by: Updated by %s %s ago
label_updated_time: Updated %s ago
label_jump_to_a_project: Jump to a project...
label_file_plural: Files
@@ -529,6 +589,8 @@ label_planning: Planning
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
label_issue_watchers: Watchers
+label_example: Example
+label_display: Display
button_login: Login
button_submit: Submit
@@ -537,6 +599,7 @@ button_check_all: Check all
button_uncheck_all: Uncheck all
button_delete: Delete
button_create: Create
+button_create_and_continue: Create and continue
button_test: Test
button_edit: Edit
button_add: Add
@@ -607,7 +670,8 @@ text_status_changed_by_changeset: Applied in changeset %s.
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
text_select_project_modules: 'Select modules to enable for this project:'
text_default_administrator_account_changed: Default administrator account changed
-text_file_repository_writable: File repository writable
+text_file_repository_writable: Attachments directory writable
+text_plugin_assets_writable: Plugin assets directory writable
text_rmagick_available: RMagick available (optional)
text_destroy_time_entries_question: %.02f hours were reported on the issues you are about to delete. What do you want to do ?
text_destroy_time_entries: Delete reported hours
@@ -617,6 +681,9 @@ 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."
+text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+text_custom_field_possible_values_info: 'One line for each value'
default_role_manager: Manager
default_role_developper: Developer
diff --git a/lang/es.yml b/lang/es.yml
index 05d42549c..67f103416 100644
--- a/lang/es.yml
+++ b/lang/es.yml
@@ -1,5 +1,4 @@
_gloc_rule_default: '|n| n==1 ? "" : "_plural" '
-
actionview_datehelper_select_day_prefix:
actionview_datehelper_select_month_names: Enero,Febrero,Marzo,Abril,Mayo,Junio,Julio,Agosto,Septiembre,Octubre,Noviembre,Diciembre
actionview_datehelper_select_month_names_abbr: Ene,Feb,Mar,Abr,Mayo,Jun,Jul,Ago,Sep,Oct,Nov,Dic
@@ -18,630 +17,678 @@ actionview_datehelper_time_in_words_minute_single: 1 minuto
actionview_datehelper_time_in_words_second_less_than: menos de un segundo
actionview_datehelper_time_in_words_second_less_than_plural: menos de %d segundos
actionview_instancetag_blank_option: Por favor seleccione
-
-activerecord_error_inclusion: no está incluído en la lista
-activerecord_error_exclusion: está reservado
-activerecord_error_invalid: no es válido
-activerecord_error_confirmation: la confirmación no coincide
activerecord_error_accepted: debe ser aceptado
-activerecord_error_empty: no puede estar vacío
activerecord_error_blank: no puede estar en blanco
+activerecord_error_circular_dependency: Esta relación podría crear una dependencia circular
+activerecord_error_confirmation: la confirmación no coincide
+activerecord_error_empty: no puede estar vacío
+activerecord_error_exclusion: está reservado
+activerecord_error_greater_than_start_date: debe ser posterior a la fecha de comienzo
+activerecord_error_inclusion: no está incluído en la lista
+activerecord_error_invalid: no es válido
+activerecord_error_not_a_date: no es una fecha válida
+activerecord_error_not_a_number: no es un número
+activerecord_error_not_same_project: no pertenece al mismo proyecto
+activerecord_error_taken: ya está siendo usado
activerecord_error_too_long: es demasiado largo
activerecord_error_too_short: es demasiado corto
activerecord_error_wrong_length: la longitud es incorrecta
-activerecord_error_taken: ya está siendo usado
-activerecord_error_not_a_number: no es un número
-activerecord_error_not_a_date: no es una fecha válida
-activerecord_error_greater_than_start_date: debe ser la fecha mayor que del comienzo
-activerecord_error_not_same_project: no pertenece al mismo proyecto
-activerecord_error_circular_dependency: Esta relación podría crear una dependencia anidada
-
-general_fmt_age: %d año
-general_fmt_age_plural: %d años
-general_fmt_date: %%d/%%m/%%Y
-general_fmt_datetime: %%d/%%m/%%Y %%H:%%M
-general_fmt_datetime_short: %%d/%%m %%H:%%M
-general_fmt_time: %%H:%%M
-general_text_No: 'No'
-general_text_Yes: 'Sí'
-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
-general_first_day_of_week: '1'
-
-notice_account_updated: Cuenta actualizada correctamente.
-notice_account_invalid_creditentials: Usuario o contraseña inválido.
-notice_account_password_updated: Contraseña modificada correctamente.
-notice_account_wrong_password: Contraseña incorrecta.
-notice_account_register_done: Cuenta creada correctamente.
-notice_account_unknown_email: Usuario desconocido.
-notice_can_t_change_password: Esta cuenta utiliza una fuente de autenticación externa. No es posible cambiar la contraseña.
-notice_account_lost_email_sent: Se le ha enviado un correo con instrucciones para elegir una nueva contraseña.
-notice_account_activated: Su cuenta ha sido activada. Ahora se encuentra conectado.
-notice_successful_create: Creación correcta.
-notice_successful_update: Modificación correcta.
-notice_successful_delete: Borrado correcto.
-notice_successful_connection: Conexión correcta.
-notice_file_not_found: La página a la que intentas acceder no existe.
-notice_locking_conflict: Los datos han sido modificados por otro usuario.
-notice_not_authorized: No tiene autorización para acceder a esta página.
-
+button_activate: Activar
+button_add: Añadir
+button_annotate: Anotar
+button_apply: Aceptar
+button_archive: Archivar
+button_back: Atrás
+button_cancel: Cancelar
+button_change: Cambiar
+button_change_password: Cambiar contraseña
+button_check_all: Seleccionar todo
+button_clear: Anular
+button_configure: Configurar
+button_copy: Copiar
+button_create: Crear
+button_delete: Borrar
+button_download: Descargar
+button_edit: Modificar
+button_list: Listar
+button_lock: Bloquear
+button_log_time: Tiempo dedicado
+button_login: Conexión
+button_move: Mover
+button_quote: Citar
+button_rename: Renombrar
+button_reply: Responder
+button_reset: Reestablecer
+button_rollback: Volver a esta versión
+button_save: Guardar
+button_sort: Ordenar
+button_submit: Aceptar
+button_test: Probar
+button_unarchive: Desarchivar
+button_uncheck_all: No seleccionar nada
+button_unlock: Desbloquear
+button_unwatch: No monitorizar
+button_update: Actualizar
+button_view: Ver
+button_watch: Monitorizar
+default_activity_design: Diseño
+default_activity_development: Desarrollo
+default_doc_category_tech: Documentación técnica
+default_doc_category_user: Documentación de usuario
+default_issue_status_assigned: Asignada
+default_issue_status_closed: Cerrada
+default_issue_status_feedback: Comentarios
+default_issue_status_new: Nueva
+default_issue_status_rejected: Rechazada
+default_issue_status_resolved: Resuelta
+default_priority_high: Alta
+default_priority_immediate: Inmediata
+default_priority_low: Baja
+default_priority_normal: Normal
+default_priority_urgent: Urgente
+default_role_developper: Desarrollador
+default_role_manager: Jefe de proyecto
+default_role_reporter: Informador
+default_tracker_bug: Errores
+default_tracker_feature: Tareas
+default_tracker_support: Soporte
+enumeration_activities: Actividades (tiempo dedicado)
+enumeration_doc_categories: Categorías del documento
+enumeration_issue_priorities: Prioridad de las peticiones
+error_can_t_load_default_data: "No se ha podido cargar la configuración por defecto: %s"
+error_issue_not_found_in_project: 'La petición no se encuentra o no está asociada a este proyecto'
+error_scm_annotate: "No existe la entrada o no ha podido ser anotada"
+error_scm_command_failed: "Se produjo un error al acceder al repositorio: %s"
error_scm_not_found: "La entrada y/o la revisión no existe en el repositorio."
-error_scm_command_failed: "An error occurred when trying to access the repository: %s"
-
-mail_subject_lost_password: Tu contraseña del %s
-mail_body_lost_password: 'Para cambiar su contraseña, haga click en el siguiente enlace:'
-mail_subject_register: Activación de la cuenta del %s
-mail_body_register: 'Para activar su cuenta, haga click en el siguiente enlace:'
-
-gui_validation_error: 1 error
-gui_validation_error_plural: %d errores
-
-field_name: Nombre
-field_description: Descripción
-field_summary: Resumen
-field_is_required: Obligatorio
-field_firstname: Nombre
-field_lastname: Apellido
-field_mail: Correo electrónico
-field_filename: Fichero
-field_filesize: Tamaño
-field_downloads: Descargas
+field_account: Cuenta
+field_activity: Actividad
+field_admin: Administrador
+field_assignable: Se pueden asignar peticiones a este perfil
+field_assigned_to: Asignado a
+field_attr_firstname: Cualidad del nombre
+field_attr_lastname: Cualidad del apellido
+field_attr_login: Cualidad del identificador
+field_attr_mail: Cualidad del Email
+field_auth_source: Modo de identificación
field_author: Autor
+field_base_dn: DN base
+field_category: Categoría
+field_column_names: Columnas
+field_comments: Comentario
+field_comments_sorting: Mostrar comentarios
field_created_on: Creado
-field_updated_on: Actualizado
+field_default_value: Estado por defecto
+field_delay: Retraso
+field_description: Descripción
+field_done_ratio: %% Realizado
+field_downloads: Descargas
+field_due_date: Fecha fin
+field_effective_date: Fecha
+field_estimated_hours: Tiempo estimado
field_field_format: Formato
-field_is_for_all: Para todos los proyectos
-field_possible_values: Valores posibles
-field_regexp: Expresión regular
-field_min_length: Longitud mínima
-field_max_length: Longitud máxima
-field_value: Valor
-field_category: Categoría
-field_title: Título
-field_project: Proyecto
-field_issue: Petición
-field_status: Estado
-field_notes: Notas
+field_filename: Fichero
+field_filesize: Tamaño
+field_firstname: Nombre
+field_fixed_version: Versión prevista
+field_hide_mail: Ocultar mi dirección de correo
+field_homepage: Sitio web
+field_host: Anfitrión
+field_hours: Horas
+field_identifier: Identificador
field_is_closed: Petición resuelta
field_is_default: Estado por defecto
-field_tracker: Tracker
-field_subject: Tema
-field_due_date: Fecha fin
-field_assigned_to: Asignado a
-field_priority: Prioridad
-field_fixed_version: Target version
-field_user: Usuario
-field_role: Perfil
-field_homepage: Sitio web
-field_is_public: Público
-field_parent: Proyecto padre
+field_is_filter: Usado como filtro
+field_is_for_all: Para todos los proyectos
field_is_in_chlog: Consultar las peticiones en el histórico
-field_is_in_roadmap: Consultar las peticiones en el roadmap
+field_is_in_roadmap: Consultar las peticiones en la planificación
+field_is_public: Público
+field_is_required: Obligatorio
+field_issue: Petición
+field_issue_to_id: Petición relacionada
+field_language: Idioma
+field_last_login_on: Última conexión
+field_lastname: Apellido
field_login: Identificador
+field_mail: Correo electrónico
field_mail_notification: Notificaciones por correo
-field_admin: Administrador
-field_last_login_on: Última conexión
-field_language: Idioma
-field_effective_date: Fecha
-field_password: Contraseña
+field_max_length: Longitud máxima
+field_min_length: Longitud mínima
+field_name: Nombre
field_new_password: Nueva contraseña
+field_notes: Notas
+field_onthefly: Creación del usuario "al vuelo"
+field_parent: Proyecto padre
+field_parent_title: Página padre
+field_password: Contraseña
field_password_confirmation: Confirmación
-field_version: Versión
-field_type: Tipo
-field_host: Anfitrión
field_port: Puerto
-field_account: Cuenta
-field_base_dn: DN base
-field_attr_login: Cualidad del identificador
-field_attr_firstname: Cualidad del nombre
-field_attr_lastname: Cualidad del apellido
-field_attr_mail: Cualidad del Email
-field_onthefly: Creación del usuario "al vuelo"
-field_start_date: Fecha de inicio
-field_done_ratio: %% Realizado
-field_auth_source: Modo de identificación
-field_hide_mail: Ocultar mi dirección de correo
-field_comment: Comentario
-field_url: URL
+field_possible_values: Valores posibles
+field_priority: Prioridad
+field_project: Proyecto
+field_redirect_existing_links: Redireccionar enlaces existentes
+field_regexp: Expresión regular
+field_role: Perfil
+field_searchable: Incluir en las búsquedas
+field_spent_on: Fecha
+field_start_date: Fecha de inicio
field_start_page: Página principal
+field_status: Estado
+field_subject: Tema
field_subproject: Proyecto secundario
-field_hours: Horas
-field_activity: Actividad
-field_spent_on: Fecha
-field_identifier: Identificador
-field_is_filter: Usado como filtro
-field_issue_to_id: Petición Relacionada
-field_delay: Retraso
-field_default_value: Estado por defecto
-
-setting_app_title: Título de la aplicación
-setting_app_subtitle: Subtítulo de la aplicación
-setting_welcome_text: Texto de bienvenida
-setting_default_language: Idioma por defecto
-setting_login_required: Se requiere identificación
-setting_self_registration: Registro permitido
-setting_attachment_max_size: Tamaño máximo del fichero
-setting_issues_export_limit: Límite de exportación de peticiones
-setting_mail_from: Correo desde el que enviar mensajes
-setting_host_name: Nombre de host
-setting_text_formatting: Formato de texto
-setting_wiki_compression: Compresión del historial de Wiki
-setting_feeds_limit: Límite de contenido para sindicación
-setting_autofetch_changesets: Autorellenar los commits del repositorio
-setting_sys_api_enabled: Habilitar WS para la gestión del repositorio
-setting_commit_ref_keywords: Palabras clave para la referencia
-setting_commit_fix_keywords: Palabras clave para la corrección
-setting_autologin: Conexión automática
-setting_date_format: Formato de la fecha
-
-label_user: Usuario
-label_user_plural: Usuarios
-label_user_new: Nuevo usuario
-label_project: Proyecto
-label_project_new: Nuevo proyecto
-label_project_plural: Proyectos
-label_project_all: Todos los proyectos
-label_project_latest: Últimos proyectos
-label_issue: Petición
-label_issue_new: Nueva petición
-label_issue_plural: Peticiones
-label_issue_view_all: Ver todas las peticiones
+field_summary: Resumen
+field_time_zone: Zona horaria
+field_title: Título
+field_tracker: Tipo
+field_type: Tipo
+field_updated_on: Actualizado
+field_url: URL
+field_user: Usuario
+field_value: Valor
+field_version: Versión
+general_csv_decimal_separator: ','
+general_csv_encoding: ISO-8859-15
+general_csv_separator: ';'
+general_day_names: Lunes,Martes,Miércoles,Jueves,Viernes,Sábado,Domingo
+general_first_day_of_week: '1'
+general_fmt_age: %d año
+general_fmt_age_plural: %d años
+general_fmt_date: %%d/%%m/%%Y
+general_fmt_datetime: %%d/%%m/%%Y %%H:%%M
+general_fmt_datetime_short: %%d/%%m %%H:%%M
+general_fmt_time: %%H:%%M
+general_lang_name: 'Español'
+general_pdf_encoding: ISO-8859-15
+general_text_No: 'No'
+general_text_Yes: 'Sí'
+general_text_no: 'no'
+general_text_yes: 'sí'
+gui_validation_error: 1 error
+gui_validation_error_plural: %d errores
+label_activity: Actividad
+label_add_another_file: Añadir otro fichero
+label_add_note: Añadir una nota
+label_added: añadido
+label_added_time_by: Añadido por %s hace %s
+label_administration: Administración
+label_age: Edad
+label_ago: hace
+label_all: todos
+label_all_time: todo el tiempo
+label_all_words: Todas las palabras
+label_and_its_subprojects: %s y proyectos secundarios
+label_applied_status: Aplicar estado
+label_assigned_to_me_issues: Peticiones que me están asignadas
+label_associated_revisions: Revisiones asociadas
+label_attachment: Fichero
+label_attachment_delete: Borrar el fichero
+label_attachment_new: Nuevo fichero
+label_attachment_plural: Ficheros
+label_attribute: Cualidad
+label_attribute_plural: Cualidades
+label_auth_source: Modo de autenticación
+label_auth_source_new: Nuevo modo de autenticación
+label_auth_source_plural: Modos de autenticación
+label_authentication: Autenticación
+label_blocked_by: bloqueado por
+label_blocks: bloquea a
+label_board: Foro
+label_board_new: Nuevo foro
+label_board_plural: Foros
+label_boolean: Booleano
+label_browse: Hojear
+label_bulk_edit_selected_issues: Editar las peticiones seleccionadas
+label_calendar: Calendario
+label_change_log: Cambios
+label_change_plural: Cambios
+label_change_properties: Cambiar propiedades
+label_change_status: Cambiar el estado
+label_change_view_all: Ver todos los cambios
+label_changes_details: Detalles de todos los cambios
+label_changeset_plural: Cambios
+label_chronological_order: En orden cronológico
+label_closed_issues: cerrada
+label_closed_issues_plural: cerradas
+label_comment: Comentario
+label_comment_add: Añadir un comentario
+label_comment_added: Comentario añadido
+label_comment_delete: Borrar comentarios
+label_comment_plural: Comentarios
+label_commits_per_author: Commits por autor
+label_commits_per_month: Commits por mes
+label_confirmation: Confirmación
+label_contains: contiene
+label_copied: copiado
+label_copy_workflow_from: Copiar flujo de trabajo desde
+label_current_status: Estado actual
+label_current_version: Versión actual
+label_custom_field: Campo personalizado
+label_custom_field_new: Nuevo campo personalizado
+label_custom_field_plural: Campos personalizados
+label_date: Fecha
+label_date_from: Desde
+label_date_range: Rango de fechas
+label_date_to: Hasta
+label_day_plural: días
+label_default: Por defecto
+label_default_columns: Columnas por defecto
+label_deleted: suprimido
+label_details: Detalles
+label_diff_inline: en línea
+label_diff_side_by_side: cara a cara
+label_disabled: deshabilitado
+label_display_per_page: 'Por página: %s'
label_document: Documento
+label_document_added: Documento añadido
label_document_new: Nuevo documento
label_document_plural: Documentos
-label_role: Perfil
-label_role_plural: Perfiles
-label_role_new: Nuevo perfil
-label_role_and_permissions: Perfiles y permisos
-label_member: Miembro
-label_member_new: Nuevo miembro
-label_member_plural: Miembros
-label_tracker: Tracker
-label_tracker_plural: Trackers
-label_tracker_new: Nuevo tracker
-label_workflow: Flujo de trabajo
-label_issue_status: Estado de petición
-label_issue_status_plural: Estados de las peticiones
-label_issue_status_new: Nuevo estado
-label_issue_category: Categoría de las peticiones
-label_issue_category_plural: Categorías de las peticiones
-label_issue_category_new: Nueva categoría
-label_custom_field: Campo personalizado
-label_custom_field_plural: Campos personalizados
-label_custom_field_new: Nuevo campo personalizado
-label_enumerations: Listas de valores
+label_download: %d Descarga
+label_download_plural: %d Descargas
+label_downloads_abbr: D/L
+label_duplicated_by: duplicada por
+label_duplicates: duplicada de
+label_end_to_end: fin a fin
+label_end_to_start: fin a principio
label_enumeration_new: Nuevo valor
+label_enumerations: Listas de valores
+label_environment: Entorno
+label_equals: igual
+label_example: Ejemplo
+label_export_to: 'Exportar a:'
+label_f_hour: %.2f hora
+label_f_hour_plural: %.2f horas
+label_feed_plural: Feeds
+label_feeds_access_key_created_on: Clave de acceso por RSS creada hace %s
+label_file_added: Fichero añadido
+label_file_plural: Archivos
+label_filter_add: Añadir el filtro
+label_filter_plural: Filtros
+label_float: Flotante
+label_follows: posterior a
+label_gantt: Gantt
+label_general: General
+label_generate_key: Generar clave
+label_help: Ayuda
+label_history: Histórico
+label_home: Inicio
+label_in: en
+label_in_less_than: en menos que
+label_in_more_than: en más que
+label_incoming_emails: Correos entrantes
+label_index_by_date: Ãndice por fecha
+label_index_by_title: Ãndice por título
label_information: Información
label_information_plural: Información
-label_please_login: Conexión
-label_register: Registrar
-label_password_lost: ¿Olvidaste la contraseña?
-label_home: Inicio
-label_my_page: Mi página
-label_my_account: Mi cuenta
-label_my_projects: Mis proyectos
-label_administration: Administración
-label_login: Conexión
-label_logout: Desconexión
-label_help: Ayuda
-label_reported_issues: Peticiones registradas por mí
-label_assigned_to_me_issues: Peticiones que me están asignadas
+label_integer: Número
+label_internal: Interno
+label_issue: Petición
+label_issue_added: Petición añadida
+label_issue_category: Categoría de las peticiones
+label_issue_category_new: Nueva categoría
+label_issue_category_plural: Categorías de las peticiones
+label_issue_new: Nueva petición
+label_issue_plural: Peticiones
+label_issue_status: Estado de la petición
+label_issue_status_new: Nuevo estado
+label_issue_status_plural: Estados de las peticiones
+label_issue_tracking: Peticiones
+label_issue_updated: Petición actualizada
+label_issue_view_all: Ver todas las peticiones
+label_issue_watchers: Seguidores
+label_issues_by: Peticiones por %s
+label_jump_to_a_project: Ir al proyecto...
+label_language_based: Basado en el idioma
+label_last_changes: últimos %d cambios
label_last_login: Última conexión
+label_last_month: último mes
+label_last_n_days: últimos %d días
label_last_updates: Actualizado
label_last_updates_plural: %d Actualizados
-label_registered_on: Inscrito el
-label_activity: Actividad
-label_new: Nuevo
+label_last_week: última semana
+label_latest_revision: Última revisión
+label_latest_revision_plural: Últimas revisiones
+label_ldap_authentication: Autenticación LDAP
+label_less_than_ago: hace menos de
+label_list: Lista
+label_loading: Cargando...
label_logged_as: Conectado como
-label_environment: Entorno
-label_authentication: Autenticación
-label_auth_source: Modo de autenticación
-label_auth_source_new: Nuevo modo de autenticación
-label_auth_source_plural: Modos de autenticación
-label_subproject_plural: Proyectos secundarios
+label_login: Conexión
+label_logout: Desconexión
+label_max_size: Tamaño máximo
+label_me: yo mismo
+label_member: Miembro
+label_member_new: Nuevo miembro
+label_member_plural: Miembros
+label_message_last: Último mensaje
+label_message_new: Nuevo mensaje
+label_message_plural: Mensajes
+label_message_posted: Mensaje añadido
label_min_max_length: Longitud mín - máx
-label_list: Lista
-label_date: Fecha
-label_integer: Número
-label_boolean: Boleano
-label_string: Texto
-label_text: Texto largo
-label_attribute: Cualidad
-label_attribute_plural: Cualidades
-label_download: %d Descarga
-label_download_plural: %d Descargas
-label_no_data: Ningun dato a mostrar
-label_change_status: Cambiar el estado
-label_history: Histórico
-label_attachment: Fichero
-label_attachment_new: Nuevo fichero
-label_attachment_delete: Borrar el fichero
-label_attachment_plural: Ficheros
-label_report: Informe
-label_report_plural: Informes
+label_modification: %d modificación
+label_modification_plural: %d modificaciones
+label_modified: modificado
+label_module_plural: Módulos
+label_month: Mes
+label_months_from: meses de
+label_more: Más
+label_more_than_ago: hace más de
+label_my_account: Mi cuenta
+label_my_page: Mi página
+label_my_projects: Mis proyectos
+label_new: Nuevo
+label_new_statuses_allowed: Nuevos estados autorizados
label_news: Noticia
+label_news_added: Noticia añadida
+label_news_latest: Últimas noticias
label_news_new: Nueva noticia
label_news_plural: Noticias
-label_news_latest: Últimas noticias
label_news_view_all: Ver todas las noticias
-label_change_log: Cambios
-label_settings: Configuración
-label_overview: Vistazo
-label_version: Versión
-label_version_new: Nueva versión
-label_version_plural: Versiones
-label_confirmation: Confirmación
-label_export_to: Exportar a
-label_read: Leer...
-label_public_projects: Proyectos públicos
+label_next: Siguiente
+label_no_change_option: (Sin cambios)
+label_no_data: Ningún dato a mostrar
+label_nobody: nadie
+label_none: ninguno
+label_not_contains: no contiene
+label_not_equals: no igual
+label_on: de
label_open_issues: abierta
label_open_issues_plural: abiertas
-label_closed_issues: cerrada
-label_closed_issues_plural: cerradas
-label_total: Total
+label_optional_description: Descripción opcional
+label_options: Opciones
+label_overall_activity: Actividad global
+label_overview: Vistazo
+label_password_lost: ¿Olvidaste la contraseña?
+label_per_page: Por página
label_permissions: Permisos
-label_current_status: Estado actual
-label_new_statuses_allowed: Nuevos estados autorizados
-label_all: todos
-label_none: ninguno
-label_next: Próximo
-label_previous: Anterior
-label_used_by: Utilizado por
-label_details: Detalles
-label_add_note: Añadir una nota
-label_per_page: Por la página
-label_calendar: Calendario
-label_months_from: meses de
-label_gantt: Gantt
-label_internal: Interno
-label_last_changes: %d cambios del último
-label_change_view_all: Ver todos los cambios
+label_permissions_report: Informe de permisos
label_personalize_page: Personalizar esta página
-label_comment: Comentario
-label_comment_plural: Comentarios
-label_comment_add: Añadir un comentario
-label_comment_added: Comentario añadido
-label_comment_delete: Borrar comentarios
+label_planning: Planificación
+label_please_login: Conexión
+label_plugins: Extensiones
+label_precedes: anterior a
+label_preferences: Preferencias
+label_preview: Previsualizar
+label_previous: Anterior
+label_project: Proyecto
+label_project_all: Todos los proyectos
+label_project_latest: Últimos proyectos
+label_project_new: Nuevo proyecto
+label_project_plural: Proyectos
+label_public_projects: Proyectos públicos
label_query: Consulta personalizada
-label_query_plural: Consultas personalizadas
label_query_new: Nueva consulta
-label_filter_add: Añadir el filtro
-label_filter_plural: Filtros
-label_equals: igual
-label_not_equals: no igual
-label_in_less_than: en menos que
-label_in_more_than: en más que
-label_in: en
-label_today: hoy
-label_less_than_ago: hace menos de
-label_more_than_ago: hace más de
-label_ago: hace
-label_contains: contiene
-label_not_contains: no contiene
-label_day_plural: días
+label_query_plural: Consultas personalizadas
+label_read: Leer...
+label_register: Registrar
+label_registered_on: Inscrito el
+label_registration_activation_by_email: activación de cuenta por correo
+label_registration_automatic_activation: activación automática de cuenta
+label_registration_manual_activation: activación manual de cuenta
+label_related_issues: Peticiones relacionadas
+label_relates_to: relacionada con
+label_relation_delete: Eliminar relación
+label_relation_new: Nueva relación
+label_renamed: renombrado
+label_reply_plural: Respuestas
+label_report: Informe
+label_report_plural: Informes
+label_reported_issues: Peticiones registradas por mí
label_repository: Repositorio
-label_browse: Hojear
-label_modification: %d modificación
-label_modification_plural: %d modificaciones
+label_repository_plural: Repositorios
+label_result_plural: Resultados
+label_reverse_chronological_order: En orden cronológico inverso
label_revision: Revisión
label_revision_plural: Revisiones
-label_added: añadido
-label_modified: modificado
-label_deleted: suprimido
-label_latest_revision: Última revisión
-label_latest_revision_plural: Últimas revisiones
-label_view_revisions: Ver las revisiones
-label_max_size: Tamaño máximo
-label_on: de
-label_sort_highest: Primero
-label_sort_higher: Subir
-label_sort_lower: Bajar
-label_sort_lowest: Último
-label_roadmap: Roadmap
+label_roadmap: Planificación
label_roadmap_due_in: Finaliza en %s
label_roadmap_no_issues: No hay peticiones para esta versión
+label_roadmap_overdue: %s tarde
+label_role: Perfil
+label_role_and_permissions: Perfiles y permisos
+label_role_new: Nuevo perfil
+label_role_plural: Perfiles
+label_scm: SCM
label_search: Búsqueda
-label_result: %d resultado
-label_result_plural: Resultados
-label_all_words: Todas las palabras
+label_search_titles_only: Buscar sólo en títulos
+label_send_information: Enviar información de la cuenta al usuario
+label_send_test_email: Enviar un correo de prueba
+label_settings: Configuración
+label_show_completed_versions: Muestra las versiones terminadas
+label_sort_by: Ordenar por %s
+label_sort_higher: Subir
+label_sort_highest: Primero
+label_sort_lower: Bajar
+label_sort_lowest: Último
+label_spent_time: Tiempo dedicado
+label_start_to_end: principio a fin
+label_start_to_start: principio a principio
+label_statistics: Estadísticas
+label_stay_logged_in: Recordar conexión
+label_string: Texto
+label_subproject_plural: Proyectos secundarios
+label_text: Texto largo
+label_theme: Tema
+label_this_month: este mes
+label_this_week: esta semana
+label_this_year: este año
+label_time_tracking: Control de tiempo
+label_today: hoy
+label_topic_plural: Temas
+label_total: Total
+label_tracker: Tipo
+label_tracker_new: Nuevo tipo
+label_tracker_plural: Tipos de peticiones
+label_updated_time: Actualizado hace %s
+label_updated_time_by: Actualizado por %s hace %s
+label_used_by: Utilizado por
+label_user: Usuario
+label_user_activity: "Actividad de %s"
+label_user_mail_no_self_notified: "No quiero ser avisado de cambios hechos por mí"
+label_user_mail_option_all: "Para cualquier evento en todos mis proyectos"
+label_user_mail_option_none: "Sólo para elementos monitorizados o relacionados conmigo"
+label_user_mail_option_selected: "Para cualquier evento de los proyectos seleccionados..."
+label_user_new: Nuevo usuario
+label_user_plural: Usuarios
+label_version: Versión
+label_version_new: Nueva versión
+label_version_plural: Versiones
+label_view_diff: Ver diferencias
+label_view_revisions: Ver las revisiones
+label_watched_issues: Peticiones monitorizadas
+label_week: Semana
label_wiki: Wiki
label_wiki_edit: Wiki edicción
label_wiki_edit_plural: Wiki edicciones
label_wiki_page: Wiki página
label_wiki_page_plural: Wiki páginas
-label_page_index: Ãndice
-label_current_version: Versión actual
-label_preview: Previsualizar
-label_feed_plural: Feeds
-label_changes_details: Detalles de todos los cambios
-label_issue_tracking: Peticiones
-label_spent_time: Tiempo dedicado
-label_f_hour: %.2f hora
-label_f_hour_plural: %.2f horas
-label_time_tracking: Tiempo tracking
-label_change_plural: Cambios
-label_statistics: Estadísticas
-label_commits_per_month: Commits por mes
-label_commits_per_author: Commits por autor
-label_view_diff: Ver diferencias
-label_diff_inline: en línea
-label_diff_side_by_side: cara a cara
-label_options: Opciones
-label_copy_workflow_from: Copiar flujo de trabajo desde
-label_permissions_report: Informe de permisos
-label_watched_issues: Peticiones monitorizadas
-label_related_issues: Peticiones relacionadas
-label_applied_status: Aplicar estado
-label_loading: Cargando...
-label_relation_new: Nueva relación
-label_relation_delete: Eliminar relación
-label_relates_to: relacionada con
-label_duplicates: duplicada de
-label_blocks: bloquea a
-label_blocked_by: bloqueado por
-label_precedes: anterior a
-label_follows: posterior a
-label_end_to_start: fin a principio
-label_end_to_end: fin a fin
-label_start_to_start: principio a principio
-label_start_to_end: principio a fin
-label_stay_logged_in: Recordar conexión
-label_disabled: deshabilitado
-label_show_completed_versions: Muestra las versiones completas
-label_me: yo mismo
-label_board: Foro
-label_board_new: Nuevo foro
-label_board_plural: Foros
-label_topic_plural: Temas
-label_message_plural: Mensajes
-label_message_last: Último mensaje
-label_message_new: Nuevo mensaje
-label_reply_plural: Respuestas
-label_send_information: Enviar información de la cuenta al usuario
+label_workflow: Flujo de trabajo
label_year: Año
-label_month: Mes
-label_week: Semana
-label_date_from: Desde
-label_date_to: Hasta
-label_language_based: Badado en el idioma
-
-button_login: Conexión
-button_submit: Aceptar
-button_save: Guardar
-button_check_all: Seleccionar todo
-button_uncheck_all: No seleccionar nada
-button_delete: Borrar
-button_create: Crear
-button_test: Probar
-button_edit: Modificar
-button_add: Añadir
-button_change: Cambiar
-button_apply: Aceptar
-button_clear: Anular
-button_lock: Bloquear
-button_unlock: Desbloquear
-button_download: Descargar
-button_list: Listar
-button_view: Ver
-button_move: Mover
-button_back: Atrás
-button_cancel: Cancelar
-button_activate: Activar
-button_sort: Ordenar
-button_log_time: Tiempo dedicado
-button_rollback: Volver a esta versión
-button_watch: Monitorizar
-button_unwatch: No monitorizar
-button_reply: Responder
-button_archive: Archivar
-button_unarchive: Desarchivar
-
-status_active: activo
-status_registered: registrado
-status_locked: bloqueado
-
-text_select_mail_notifications: Seleccionar los eventos a notificar
-text_regexp_info: eg. ^[A-Z0-9]+$
-text_min_max_length_info: 0 para ninguna restricción
-text_project_destroy_confirmation: ¿Estás seguro de querer eliminar el proyecto?
-text_workflow_edit: Seleccionar un flujo de trabajo para actualizar
-text_are_you_sure: ¿ Estás seguro ?
-text_journal_changed: cambiado de %s a %s
-text_journal_set_to: fijado a %s
-text_journal_deleted: suprimido
-text_tip_task_begin_day: tarea que comienza este día
-text_tip_task_end_day: tarea que termina este día
-text_tip_task_begin_end_day: tarea que comienza y termina este día
-text_project_identifier_info: 'Letras minúsculas (a-z), números y signos de puntuación permitidos.<br />Una vez guardado, el identificador no puede modificarse.'
-text_caracters_maximum: %d carácteres como máximo.
-text_length_between: Longitud entre %d y %d carácteres.
-text_tracker_no_workflow: No hay ningún flujo de trabajo definido para este tracker
-text_unallowed_characters: Carácteres no permitidos
-text_comma_separated: Múltiples valores permitidos (separados por coma).
-text_issues_ref_in_commit_messages: Referencia y petición de corrección en los mensajes
-
-default_role_manager: Jefe de proyecto
-default_role_developper: Desarrollador
-default_role_reporter: Informador
-default_tracker_bug: Errores
-default_tracker_feature: Tareas
-default_tracker_support: Soporte
-default_issue_status_new: Nueva
-default_issue_status_assigned: Asignada
-default_issue_status_resolved: Resuelta
-default_issue_status_feedback: Comentarios
-default_issue_status_closed: Cerrada
-default_issue_status_rejected: Rechazada
-default_doc_category_user: Documentación de usuario
-default_doc_category_tech: Documentación técnica
-default_priority_low: Baja
-default_priority_normal: Normal
-default_priority_high: Alta
-default_priority_urgent: Urgente
-default_priority_immediate: Inmediata
-default_activity_design: Diseño
-default_activity_development: Desarrollo
-
-enumeration_issue_priorities: Prioridad de las peticiones
-enumeration_doc_categories: Categorías del documento
-enumeration_activities: Actividades (tiempo dedicado)
-label_index_by_date: Ãndice por fecha
-field_column_names: Columnas
-button_rename: Renombrar
-text_issue_category_destroy_question: Algunas peticiones (%d) están asignadas a esta categoría. ¿Qué desea hacer?
-label_feeds_access_key_created_on: Clave de acceso por RSS creada hace %s
-label_default_columns: Columnas por defecto
-setting_cross_project_issue_relations: Permitir relacionar peticiones de distintos proyectos
-label_roadmap_overdue: %s tarde
-label_module_plural: Módulos
-label_this_week: esta semana
-label_index_by_title: Ãndice por título
-label_jump_to_a_project: Ir al proyecto...
-field_assignable: Se pueden asignar peticiones a este perfil
-label_sort_by: Ordenar por %s
-setting_issue_list_default_columns: Columnas por defecto para la lista de peticiones
-text_issue_updated: La petición %s ha sido actualizada por %s.
-notice_feeds_access_key_reseted: Su clave de acceso para RSS ha sido reiniciada
-field_redirect_existing_links: Redireccionar enlaces existentes
-text_issue_category_reassign_to: Reasignar las peticiones a la categoría
-notice_email_sent: Se ha enviado un correo a %s
-text_issue_added: Petición añadida por %s.
-field_comments: Comentario
-label_file_plural: Archivos
-text_wiki_destroy_confirmation: ¿Seguro que quiere borrar el wiki y todo su contenido?
-notice_email_error: Ha ocurrido un error mientras enviando el correo (%s)
-label_updated_time: Actualizado hace %s
-text_issue_category_destroy_assignments: Dejar las peticiones sin categoría
-label_send_test_email: Enviar un correo de prueba
-button_reset: Reestablecer
-label_added_time_by: Añadido por %s hace %s
-field_estimated_hours: Tiempo estimado
-label_changeset_plural: Cambios
-setting_repositories_encodings: Codificaciones del repositorio
-notice_no_issue_selected: "Ninguna petición seleccionada. Por favor, compruebe la petición que quiere modificar"
-label_bulk_edit_selected_issues: Editar las peticiones seleccionadas
-label_no_change_option: (Sin cambios)
-notice_failed_to_save_issues: "Imposible salvar %s peticion(es) en %d seleccionado: %s."
-label_theme: Tema
-label_default: Por defecto
-label_search_titles_only: Buscar sólo en títulos
-label_nobody: nadie
-button_change_password: Cambiar contraseña
-text_user_mail_option: "En los proyectos no seleccionados, sólo recibirá notificaciones sobre elementos monitorizados o elementos en los que esté involucrado (por ejemplo, peticiones de las que usted sea autor o asignadas a usted)."
-label_user_mail_option_selected: "Para cualquier evento del proyecto seleccionado..."
-label_user_mail_option_all: "Para cualquier evento en todos mis proyectos"
-label_user_mail_option_none: "Sólo para elementos monitorizados o relacionados conmigo"
-setting_emails_footer: Pie de mensajes
-label_float: Flotante
-button_copy: Copiar
-mail_body_account_information_external: Puede usar su cuenta "%s" para conectarse.
+label_yesterday: ayer
+mail_body_account_activation_request: 'Se ha inscrito un nuevo usuario (%s). La cuenta está pendiende de aprobación:'
mail_body_account_information: Información sobre su cuenta
-setting_protocol: Protocolo
-text_caracters_minimum: %d carácteres como mínimo
-field_time_zone: Zona horaria
-label_registration_activation_by_email: activación de cuenta por correo
-label_user_mail_no_self_notified: "No quiero ser avisado de cambios hechos por mí"
+mail_body_account_information_external: Puede usar su cuenta "%s" para conectarse.
+mail_body_lost_password: 'Para cambiar su contraseña, haga clic en el siguiente enlace:'
+mail_body_register: 'Para activar su cuenta, haga clic en el siguiente enlace:'
+mail_body_reminder: "%d peticion(es) asignadas a tí finalizan en los próximos %d días:"
mail_subject_account_activation_request: Petición de activación de cuenta %s
-mail_body_account_activation_request: "Un nuevo usuario (%s) ha sido registrado. Esta cuenta está pendiende de aprobación"
-label_registration_automatic_activation: activación automática de cuenta
-label_registration_manual_activation: activación manual de cuenta
-notice_account_pending: "Su cuenta ha sido creada y está pendiende de la aprobación por parte de administrador"
-setting_time_format: Formato de hora
-setting_bcc_recipients: Ocultar las copias de carbon (bcc)
-button_annotate: Anotar
-label_issues_by: Peticiones por %s
-field_searchable: Incluir en las búsquedas
-label_display_per_page: 'Por página: %s'
-setting_per_page_options: Objetos por página
-label_age: Edad
+mail_subject_lost_password: Tu contraseña del %s
+mail_subject_register: Activación de la cuenta del %s
+mail_subject_reminder: "%d peticion(es) finalizan en los próximos días"
+notice_account_activated: Su cuenta ha sido activada. Ya puede conectarse.
+notice_account_invalid_creditentials: Usuario o contraseña inválido.
+notice_account_lost_email_sent: Se le ha enviado un correo con instrucciones para elegir una nueva contraseña.
+notice_account_password_updated: Contraseña modificada correctamente.
+notice_account_pending: "Su cuenta ha sido creada y está pendiende de la aprobación por parte del administrador."
+notice_account_register_done: Cuenta creada correctamente. Para activarla, haga clic sobre el enlace que le ha sido enviado por correo.
+notice_account_unknown_email: Usuario desconocido.
+notice_account_updated: Cuenta actualizada correctamente.
+notice_account_wrong_password: Contraseña incorrecta.
+notice_can_t_change_password: Esta cuenta utiliza una fuente de autenticación externa. No es posible cambiar la contraseña.
notice_default_data_loaded: Configuración por defecto cargada correctamente.
-text_load_default_configuration: Cargar la configuración por defecto
-text_no_configuration_data: "Todavía no se han configurado roles, ni trackers, ni estados y flujo de trabajo asociado a peticiones. Se recomiendo encarecidamente cargar la configuración por defecto. Una vez cargada, podrá modificarla."
-error_can_t_load_default_data: "No se ha podido cargar la configuración por defecto: %s"
-button_update: Actualizar
-label_change_properties: Cambiar propiedades
-label_general: General
-label_repository_plural: Repositorios
-label_associated_revisions: Revisiones asociadas
-setting_user_format: Formato de nombre de usuario
-text_status_changed_by_changeset: Aplicado en los cambios %s
-label_more: Más
-text_issues_destroy_confirmation: '¿Seguro que quiere borrar las peticiones seleccionadas?'
-label_scm: SCM
-text_select_project_modules: 'Seleccione los módulos a activar para este proyecto:'
-label_issue_added: Petición añadida
-label_issue_updated: Petición actualizada
-label_document_added: Documento añadido
-label_message_posted: Mensaje añadido
-label_file_added: Fichero añadido
-label_news_added: Noticia añadida
+notice_email_error: Ha ocurrido un error mientras enviando el correo (%s)
+notice_email_sent: Se ha enviado un correo a %s
+notice_failed_to_save_issues: "Imposible grabar %s peticion(es) en %d seleccionado: %s."
+notice_feeds_access_key_reseted: Su clave de acceso para RSS ha sido reiniciada.
+notice_file_not_found: La página a la que intenta acceder no existe.
+notice_locking_conflict: Los datos han sido modificados por otro usuario.
+notice_no_issue_selected: "Ninguna petición seleccionada. Por favor, compruebe la petición que quiere modificar"
+notice_not_authorized: No tiene autorización para acceder a esta página.
+notice_successful_connection: Conexión correcta.
+notice_successful_create: Creación correcta.
+notice_successful_delete: Borrado correcto.
+notice_successful_update: Modificación correcta.
+notice_unable_delete_version: No se puede borrar la versión
+permission_add_issue_notes: Añadir notas
+permission_add_issue_watchers: Añadir seguidores
+permission_add_issues: Añadir peticiones
+permission_add_messages: Enviar mensajes
+permission_browse_repository: Hojear repositiorio
+permission_comment_news: Comentar noticias
+permission_commit_access: Acceso de escritura
+permission_delete_issues: Borrar peticiones
+permission_delete_messages: Borrar mensajes
+permission_delete_own_messages: Borrar mensajes propios
+permission_delete_wiki_pages: Borrar páginas wiki
+permission_delete_wiki_pages_attachments: Borrar ficheros
+permission_edit_issue_notes: Modificar notas
+permission_edit_issues: Modificar peticiones
+permission_edit_messages: Modificar mensajes
+permission_edit_own_issue_notes: Modificar notas propias
+permission_edit_own_messages: Editar mensajes propios
+permission_edit_own_time_entries: Modificar tiempos dedicados propios
+permission_edit_project: Modificar proyecto
+permission_edit_time_entries: Modificar tiempos dedicados
+permission_edit_wiki_pages: Modificar páginas wiki
+permission_log_time: Anotar tiempo dedicado
+permission_manage_boards: Administrar foros
+permission_manage_categories: Administrar categorías de peticiones
+permission_manage_documents: Administrar documentos
+permission_manage_files: Administrar ficheros
+permission_manage_issue_relations: Administrar relación con otras peticiones
+permission_manage_members: Administrar miembros
+permission_manage_news: Administrar noticias
+permission_manage_public_queries: Administrar consultas públicas
+permission_manage_repository: Administrar repositorio
+permission_manage_versions: Administrar versiones
+permission_manage_wiki: Administrar wiki
+permission_move_issues: Mover peticiones
+permission_protect_wiki_pages: Proteger páginas wiki
+permission_rename_wiki_pages: Renombrar páginas wiki
+permission_save_queries: Grabar consultas
+permission_select_project_modules: Seleccionar módulos del proyecto
+permission_view_calendar: Ver calendario
+permission_view_changesets: Ver cambios
+permission_view_documents: Ver documentos
+permission_view_files: Ver ficheros
+permission_view_gantt: Ver diagrama de Gantt
+permission_view_issue_watchers: Ver lista de seguidores
+permission_view_messages: Ver mensajes
+permission_view_time_entries: Ver tiempo dedicado
+permission_view_wiki_edits: Ver histórico del wiki
+permission_view_wiki_pages: Ver wiki
project_module_boards: Foros
-project_module_issue_tracking: Peticiones
-project_module_wiki: Wiki
-project_module_files: Ficheros
project_module_documents: Documentos
-project_module_repository: Repositorio
+project_module_files: Ficheros
+project_module_issue_tracking: Peticiones
project_module_news: Noticias
+project_module_repository: Repositorio
project_module_time_tracking: Control de tiempo
-text_file_repository_writable: Se puede escribir en el repositorio
-text_default_administrator_account_changed: Cuenta de administrador por defecto modificada
-text_rmagick_available: RMagick disponible (opcional)
-button_configure: Configurar
-label_plugins: Plugins
-label_ldap_authentication: Autenticación LDAP
-label_downloads_abbr: D/L
-label_this_month: este mes
-label_last_n_days: últimos %d días
-label_all_time: todo el tiempo
-label_this_year: este año
-label_date_range: Rango de fechas
-label_last_week: última semana
-label_yesterday: ayer
-label_last_month: último mes
-label_add_another_file: Añadir otro fichero
-label_optional_description: Descripción opcional
-text_destroy_time_entries_question: Existen %.02f horas asignadas a la petición que quiere borrar. ¿Qué quiere hacer ?
-error_issue_not_found_in_project: 'La petición no se encuentra o no está asociada a este proyecto'
-text_assign_time_entries_to_project: Asignar las horas al proyecto
-text_destroy_time_entries: Borrar las horas
-text_reassign_time_entries: 'Reasignar las horas a esta petición:'
+project_module_wiki: Wiki
setting_activity_days_default: Días a mostrar en la actividad de proyecto
-label_chronological_order: En orden cronológico
-field_comments_sorting: Mostrar comentarios
-label_reverse_chronological_order: En orden cronológico inverso
-label_preferences: Preferencias
-setting_display_subprojects_issues: Mostrar peticiones de un subproyecto en el proyecto padre por defecto
-label_overall_activity: Actividad global
-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 y sus subproyectos
-mail_body_reminder: "la petición %d asignada a tí finaliza en los próximos %d días:"
-mail_subject_reminder: "%d peticion(es) finaliza en los próximos días"
-text_user_wrote: '%s escribió:'
-label_duplicated_by: duplicada por
+setting_app_subtitle: Subtítulo de la aplicación
+setting_app_title: Título de la aplicación
+setting_attachment_max_size: Tamaño máximo del fichero
+setting_autofetch_changesets: Autorellenar los commits del repositorio
+setting_autologin: Conexión automática
+setting_bcc_recipients: Ocultar las copias de carbón (bcc)
+setting_commit_fix_keywords: Palabras clave para la corrección
+setting_commit_logs_encoding: Codificación de los mensajes de commit
+setting_commit_ref_keywords: Palabras clave para la referencia
+setting_cross_project_issue_relations: Permitir relacionar peticiones de distintos proyectos
+setting_date_format: Formato de fecha
+setting_default_language: Idioma por defecto
+setting_default_projects_public: Los proyectos nuevos son públicos por defecto
+setting_diff_max_lines_displayed: Número máximo de diferencias mostradas
+setting_display_subprojects_issues: Mostrar por defecto peticiones de proy. secundarios en el principal
+setting_emails_footer: Pie de mensajes
setting_enabled_scm: Activar SCM
-text_enumeration_category_reassign_to: 'Reasignar el siguiente valor:'
-text_enumeration_destroy_question: '%d objetos con este valor asignado.'
-label_incoming_emails: Correos entrantes
-label_generate_key: Generar clave
+setting_feeds_limit: Límite de contenido para sindicación
+setting_gravatar_enabled: Usar iconos de usuario (Gravatar)
+setting_host_name: Nombre y ruta del servidor
+setting_issue_list_default_columns: Columnas por defecto para la lista de peticiones
+setting_issues_export_limit: Límite de exportación de peticiones
+setting_login_required: Se requiere identificación
+setting_mail_from: Correo desde el que enviar mensajes
setting_mail_handler_api_enabled: Activar SW para mensajes entrantes
-setting_mail_handler_api_key: clave de la API
+setting_mail_handler_api_key: Clave de la API
+setting_per_page_options: Objetos por página
+setting_plain_text_mail: sólo texto plano (no HTML)
+setting_protocol: Protocolo
+setting_repositories_encodings: Codificaciones del repositorio
+setting_self_registration: Registro permitido
+setting_sequential_project_identifiers: Generar identificadores de proyecto
+setting_sys_api_enabled: Habilitar SW para la gestión del repositorio
+setting_text_formatting: Formato de texto
+setting_time_format: Formato de hora
+setting_user_format: Formato de nombre de usuario
+setting_welcome_text: Texto de bienvenida
+setting_wiki_compression: Compresión del historial del Wiki
+status_active: activo
+status_locked: bloqueado
+status_registered: registrado
+text_are_you_sure: ¿Está seguro?
+text_assign_time_entries_to_project: Asignar las horas al proyecto
+text_caracters_maximum: %d caracteres como máximo.
+text_caracters_minimum: %d caracteres como mínimo
+text_comma_separated: Múltiples valores permitidos (separados por coma).
+text_default_administrator_account_changed: Cuenta de administrador por defecto modificada
+text_destroy_time_entries: Borrar las horas
+text_destroy_time_entries_question: Existen %.02f horas asignadas a la petición que quiere borrar. ¿Qué quiere hacer ?
+text_diff_truncated: '... Diferencia truncada por exceder el máximo tamaño visualizable.'
text_email_delivery_not_configured: "El envío de correos no está configurado, y las notificaciones se han desactivado. \n Configure el servidor de SMTP en config/email.yml y reinicie la aplicación para activar los cambios."
-field_parent_title: Página padre
-label_issue_watchers: Seguidores
-setting_commit_logs_encoding: Codificación de los mensajes de commit
-button_quote: Quote
-setting_sequential_project_identifiers: Generate sequential project identifiers
-notice_unable_delete_version: Unable to delete version
-label_renamed: renamed
-label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+text_enumeration_category_reassign_to: 'Reasignar al siguiente valor:'
+text_enumeration_destroy_question: '%d objetos con este valor asignado.'
+text_file_repository_writable: Se puede escribir en el repositorio
+text_issue_added: Petición %s añadida por %s.
+text_issue_category_destroy_assignments: Dejar las peticiones sin categoría
+text_issue_category_destroy_question: Algunas peticiones (%d) están asignadas a esta categoría. ¿Qué desea hacer?
+text_issue_category_reassign_to: Reasignar las peticiones a la categoría
+text_issue_updated: La petición %s ha sido actualizada por %s.
+text_issues_destroy_confirmation: '¿Seguro que quiere borrar las peticiones seleccionadas?'
+text_issues_ref_in_commit_messages: Referencia y petición de corrección en los mensajes
+text_journal_changed: cambiado de %s a %s
+text_journal_deleted: suprimido
+text_journal_set_to: fijado a %s
+text_length_between: Longitud entre %d y %d caracteres.
+text_load_default_configuration: Cargar la configuración por defecto
+text_min_max_length_info: 0 para ninguna restricción
+text_no_configuration_data: "Todavía no se han configurado perfiles, ni tipos, estados y flujo de trabajo asociado a peticiones. Se recomiendo encarecidamente cargar la configuración por defecto. Una vez cargada, podrá modificarla."
+text_project_destroy_confirmation: ¿Estás seguro de querer eliminar el proyecto?
+text_project_identifier_info: 'Letras minúsculas (a-z), números y signos de puntuación permitidos.<br />Una vez guardado, el identificador no puede modificarse.'
+text_reassign_time_entries: 'Reasignar las horas a esta petición:'
+text_regexp_info: ej. ^[A-Z0-9]+$
+text_repository_usernames_mapping: "Establezca la correspondencia entre los usuarios de Redmine y los presentes en el log del repositorio.\nLos usuarios con el mismo nombre o correo en Redmine y en el repositorio serán asociados automáticamente."
+text_rmagick_available: RMagick disponible (opcional)
+text_select_mail_notifications: Seleccionar los eventos a notificar
+text_select_project_modules: 'Seleccione los módulos a activar para este proyecto:'
+text_status_changed_by_changeset: Aplicado en los cambios %s
+text_subprojects_destroy_warning: 'Los proyectos secundarios: %s también se eliminarán'
+text_tip_task_begin_day: tarea que comienza este día
+text_tip_task_begin_end_day: tarea que comienza y termina este día
+text_tip_task_end_day: tarea que termina este día
+text_tracker_no_workflow: No hay ningún flujo de trabajo definido para este tipo de petición
+text_unallowed_characters: Caracteres no permitidos
+text_user_mail_option: "De los proyectos no seleccionados, sólo recibirá notificaciones sobre elementos monitorizados o elementos en los que esté involucrado (por ejemplo, peticiones de las que usted sea autor o asignadas a usted)."
+text_user_wrote: '%s escribió:'
+text_wiki_destroy_confirmation: ¿Seguro que quiere borrar el wiki y todo su contenido?
+text_workflow_edit: Seleccionar un flujo de trabajo para actualizar
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/fi.yml b/lang/fi.yml
index d674568ef..65478776d 100644
--- a/lang/fi.yml
+++ b/lang/fi.yml
@@ -634,11 +634,76 @@ setting_mail_handler_api_key: API avain
text_email_delivery_not_configured: "Sähköpostin jakelu ei ole määritelty ja sähköpostimuistutukset eivät ole käytössä.\nKonfiguroi sähköpostipalvelinasetukset (SMTP) config/email.yml tiedostosta ja uudelleenkäynnistä sovellus jotta asetukset astuvat voimaan."
field_parent_title: Aloitussivu
label_issue_watchers: Tapahtuman seuraajat
-button_quote: Quote
-setting_sequential_project_identifiers: Generate sequential project identifiers
-setting_commit_logs_encoding: Commit messages encoding
-notice_unable_delete_version: Unable to delete version
-label_renamed: renamed
-label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+button_quote: Vastaa
+setting_sequential_project_identifiers: Luo peräkkäiset projektien tunnisteet
+setting_commit_logs_encoding: Tee viestien koodaus
+notice_unable_delete_version: Version poisto epäonnistui
+label_renamed: uudelleennimetty
+label_copied: kopioitu
+setting_plain_text_mail: vain muotoilematonta tekstiä (ei HTML)
+permission_view_files: Näytä tiedostot
+permission_edit_issues: Muokkaa tapahtumia
+permission_edit_own_time_entries: Muokka omia aikamerkintöjä
+permission_manage_public_queries: Hallinnoi julkisia hakuja
+permission_add_issues: Lisää tapahtumia
+permission_log_time: Lokita käytettyä aikaa
+permission_view_changesets: Näytä muutosryhmät
+permission_view_time_entries: Näytä käytetty aika
+permission_manage_versions: Hallinnoi versioita
+permission_manage_wiki: Hallinnoi wikiä
+permission_manage_categories: Hallinnoi tapahtumien luokkia
+permission_protect_wiki_pages: Suojaa wiki sivut
+permission_comment_news: Kommentoi uutisia
+permission_delete_messages: Poista viestit
+permission_select_project_modules: Valitse projektin modulit
+permission_manage_documents: Hallinnoi dokumentteja
+permission_edit_wiki_pages: Muokkaa wiki sivuja
+permission_add_issue_watchers: Lisää seuraajia
+permission_view_gantt: Näytä gantt kaavio
+permission_move_issues: Siirrä tapahtuma
+permission_manage_issue_relations: Hallinoi tapahtuman suhteita
+permission_delete_wiki_pages: Poista wiki sivuja
+permission_manage_boards: Hallinnoi keskustelupalstaa
+permission_delete_wiki_pages_attachments: Poista liitteitä
+permission_view_wiki_edits: Näytä wiki historia
+permission_add_messages: Jätä viesti
+permission_view_messages: Näytä viestejä
+permission_manage_files: Hallinnoi tiedostoja
+permission_edit_issue_notes: Muokkaa muistiinpanoja
+permission_manage_news: Hallinnoi uutisia
+permission_view_calendar: Näytä kalenteri
+permission_manage_members: Hallinnoi jäseniä
+permission_edit_messages: Muokkaa viestejä
+permission_delete_issues: Poista tapahtumia
+permission_view_issue_watchers: Näytä seuraaja lista
+permission_manage_repository: Hallinnoi tietovarastoa
+permission_commit_access: Tee pääsyoikeus
+permission_browse_repository: Selaa tietovarastoa
+permission_view_documents: Näytä dokumentit
+permission_edit_project: Muokkaa projektia
+permission_add_issue_notes: Lisää muistiinpanoja
+permission_save_queries: Tallenna hakuja
+permission_view_wiki_pages: Näytä wiki
+permission_rename_wiki_pages: Uudelleennimeä wiki sivuja
+permission_edit_time_entries: Muokkaa aika lokeja
+permission_edit_own_issue_notes: Muokkaa omia muistiinpanoja
+setting_gravatar_enabled: Käytä Gravatar käyttäjä ikoneita
+label_example: Esimerkki
+text_repository_usernames_mapping: "Valitse päivittääksesi Redmine käyttäjä jokaiseen käyttäjään joka löytyy tietovaraston lokista.\nKäyttäjät joilla on sama Redmine ja tietovaraston käyttäjänimi tai sähköpostiosoite, yhdistetään automaattisesti."
+permission_edit_own_messages: Muokkaa omia viestejä
+permission_delete_own_messages: Poista omia viestejä
+label_user_activity: "Käyttäjän %s historia"
+label_updated_time_by: Updated by %s %s ago
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/fr.yml b/lang/fr.yml
index 77c358a9b..4a7c51708 100644
--- a/lang/fr.yml
+++ b/lang/fr.yml
@@ -77,6 +77,7 @@ notice_failed_to_save_issues: "%d demande(s) sur les %d sélectionnées n'ont pa
notice_no_issue_selected: "Aucune demande sélectionnée ! Cochez les demandes que vous voulez mettre à jour."
notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur."
notice_default_data_loaded: Paramétrage par défaut chargé avec succès.
+notice_unable_delete_version: Impossible de supprimer cette version.
error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramétrage: %s"
error_scm_not_found: "L'entrée et/ou la révision demandée n'existe pas dans le dépôt."
@@ -84,6 +85,8 @@ error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt
error_scm_annotate: "L'entrée n'existe pas ou ne peut pas être annotée."
error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas à ce projet"
+warning_attachments_not_saved: "%d fichier(s) n'ont pas pu être sauvegardés."
+
mail_subject_lost_password: Votre mot de passe %s
mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant:'
mail_subject_register: Activation de votre compte %s
@@ -169,7 +172,6 @@ field_start_page: Page de démarrage
field_subproject: Sous-projet
field_hours: Heures
field_activity: Activité
-label_overall_activity: Activité globale
field_spent_on: Date
field_identifier: Identifiant
field_is_filter: Utilisé comme filtre
@@ -184,6 +186,9 @@ field_searchable: Utilisé pour les recherches
field_default_value: Valeur par défaut
field_comments_sorting: Afficher les commentaires
field_parent_title: Page parent
+field_editable: Modifiable
+field_identity_url: URL OpenID
+field_watcher: Observateur
setting_app_title: Titre de l'application
setting_app_subtitle: Sous-titre de l'application
@@ -195,7 +200,8 @@ setting_attachment_max_size: Taille max des fichiers
setting_issues_export_limit: Limite export demandes
setting_mail_from: Adresse d'émission
setting_bcc_recipients: Destinataires en copie cachée (cci)
-setting_host_name: Nom d'hôte
+setting_plain_text_mail: Mail texte brut (non HTML)
+setting_host_name: Nom d'hôte et chemin
setting_text_formatting: Formatage du texte
setting_wiki_compression: Compression historique wiki
setting_feeds_limit: Limite du contenu des flux RSS
@@ -221,7 +227,59 @@ 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
setting_sequential_project_identifiers: Générer des identifiants de projet séquentiels
-setting_repositories_cache_directory: Répertoire du cache pour les dépôts
+setting_gravatar_enabled: Afficher les Gravatar des utilisateurs
+setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichées
+setting_repository_log_display_limit: "Nombre maximum de revisions affichées sur l'historique d'un fichier"
+setting_openid: "Autoriser l'authentification et l'enregistrement OpenID"
+
+permission_edit_project: Modifier le projet
+permission_select_project_modules: Choisir les modules
+permission_manage_members: Gérer les members
+permission_manage_versions: Gérer les versions
+permission_manage_categories: Gérer les catégories de demandes
+permission_add_issues: Créer des demandes
+permission_edit_issues: Modifier les demandes
+permission_manage_issue_relations: Gérer les relations
+permission_add_issue_notes: Ajouter des notes
+permission_edit_issue_notes: Modifier les notes
+permission_edit_own_issue_notes: Modifier ses propres notes
+permission_move_issues: Déplacer les demandes
+permission_delete_issues: Supprimer les demandes
+permission_manage_public_queries: Gérer les requêtes publiques
+permission_save_queries: Sauvegarder les requêtes
+permission_view_gantt: Voir le gantt
+permission_view_calendar: Voir le calendrier
+permission_view_issue_watchers: Voir la liste des observateurs
+permission_add_issue_watchers: Ajouter des observateurs
+permission_log_time: Saisir le temps passé
+permission_view_time_entries: Voir le temps passé
+permission_edit_time_entries: Modifier les temps passés
+permission_edit_own_time_entries: Modifier son propre temps passé
+permission_manage_news: Gérer les annonces
+permission_comment_news: Commenter les annonces
+permission_manage_documents: Gérer les documents
+permission_view_documents: Voir les documents
+permission_manage_files: Gérer les fichiers
+permission_view_files: Voir les fichiers
+permission_manage_wiki: Gérer le wiki
+permission_rename_wiki_pages: Renommer les pages
+permission_delete_wiki_pages: Supprimer les pages
+permission_view_wiki_pages: Voir le wiki
+permission_view_wiki_edits: "Voir l'historique des modifications"
+permission_edit_wiki_pages: Modifier les pages
+permission_delete_wiki_pages_attachments: Supprimer les fichiers joints
+permission_protect_wiki_pages: Protéger les pages
+permission_manage_repository: Gérer le dépôt de sources
+permission_browse_repository: Parcourir les sources
+permission_view_changesets: Voir les révisions
+permission_commit_access: Droit de commit
+permission_manage_boards: Gérer les forums
+permission_view_messages: Voir les messages
+permission_add_messages: Poster un message
+permission_edit_messages: Modifier les messages
+permission_edit_own_messages: Modifier ses propres messages
+permission_delete_messages: Supprimer les messages
+permission_delete_own_messages: Supprimer ses propres messages
project_module_issue_tracking: Suivi des demandes
project_module_time_tracking: Suivi du temps passé
@@ -293,6 +351,8 @@ label_last_updates: Dernière mise à jour
label_last_updates_plural: %d dernières mises à jour
label_registered_on: Inscrit le
label_activity: Activité
+label_overall_activity: Activité globale
+label_user_activity: "Activité de %s"
label_new: Nouveau
label_logged_as: Connecté en tant que
label_environment: Environnement
@@ -492,6 +552,7 @@ label_send_test_email: Envoyer un email de test
label_feeds_access_key_created_on: Clé d'accès RSS créée il y a %s
label_module_plural: Modules
label_added_time_by: Ajouté par %s il y a %s
+label_updated_time_by: Mis à jour par %s il y a %s
label_updated_time: Mis à jour il y a %s
label_jump_to_a_project: Aller à un projet...
label_file_plural: Fichiers
@@ -526,7 +587,10 @@ 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
+label_issue_watchers: Observateurs
+label_example: Exemple
+label_display: Affichage
+label_login_with_open_id_option: Authentification OpenID
button_login: Connexion
button_submit: Soumettre
@@ -535,6 +599,7 @@ button_check_all: Tout cocher
button_uncheck_all: Tout décocher
button_delete: Supprimer
button_create: Créer
+button_create_and_continue: Créer et continuer
button_test: Tester
button_edit: Modifier
button_add: Ajouter
@@ -606,6 +671,7 @@ text_issues_destroy_confirmation: 'Etes-vous sûr de vouloir supprimer le(s) dem
text_select_project_modules: 'Selectionner les modules à activer pour ce project:'
text_default_administrator_account_changed: Compte administrateur par défaut changé
text_file_repository_writable: Répertoire de stockage des fichiers accessible en écriture
+text_plugin_assets_writable: Répertoire public des plugins accessible en écriture
text_rmagick_available: Bibliothèque RMagick présente (optionnelle)
text_destroy_time_entries_question: %.02f heures ont été enregistrées sur les demandes à supprimer. Que voulez-vous faire ?
text_destroy_time_entries: Supprimer les heures
@@ -615,6 +681,9 @@ 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."
+text_repository_usernames_mapping: "Vous pouvez sélectionner ou modifier l'utilisateur Redmine associé à chaque nom d'utilisateur figurant dans l'historique du dépôt.\nLes utilisateurs avec le même identifiant ou la même adresse mail seront automatiquement associés."
+text_diff_truncated: '... Ce différentiel a été tronqué car il excède la taille maximale pouvant être affichée.'
+text_custom_field_possible_values_info: 'Une ligne par valeur'
default_role_manager: Manager
default_role_developper: Développeur
@@ -641,5 +710,3 @@ default_activity_development: Développement
enumeration_issue_priorities: Priorités des demandes
enumeration_doc_categories: Catégories des documents
enumeration_activities: Activités (suivi du temps)
-notice_unable_delete_version: Unable to delete version
-field_cache: Cache local
diff --git a/lang/gl.yml b/lang/gl.yml
new file mode 100644
index 000000000..701fe22c7
--- /dev/null
+++ b/lang/gl.yml
@@ -0,0 +1,694 @@
+_gloc_rule_default: '|n| n==1 ? "" : "_plural" '
+actionview_datehelper_select_day_prefix:
+actionview_datehelper_select_month_names: Xaneiro,Febreiro,Marzo,Abril,Maio,Xuño,Xullo,Agosto,Setembro,Outubro,Novembro,Decembro
+actionview_datehelper_select_month_names_abbr: Xan,Feb,Mar,Abr,Mai,Xun,Xul,Ago,Set,Out,Nov,Dec
+actionview_datehelper_select_month_prefix:
+actionview_datehelper_select_year_prefix:
+actionview_datehelper_time_in_words_day: 1 día
+actionview_datehelper_time_in_words_day_plural: %d días
+actionview_datehelper_time_in_words_hour_about: unha hora aproximadamente
+actionview_datehelper_time_in_words_hour_about_plural: aproximadamente %d horas
+actionview_datehelper_time_in_words_hour_about_single: unha hora aproximadamente
+actionview_datehelper_time_in_words_minute: 1 minuto
+actionview_datehelper_time_in_words_minute_half: medio minuto
+actionview_datehelper_time_in_words_minute_less_than: menos dun 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 dun segundo
+actionview_datehelper_time_in_words_second_less_than_plural: menos de %d segundos
+actionview_instancetag_blank_option: Por favor seleccione
+activerecord_error_accepted: debe ser aceptado
+activerecord_error_blank: non pode estar en branco
+activerecord_error_circular_dependency: Esta relación podería crear unha dependencia circular
+activerecord_error_confirmation: a confirmación non coincide
+activerecord_error_empty: non pode estar baleiro
+activerecord_error_exclusion: está reservado
+activerecord_error_greater_than_start_date: debe ser posterior á data de comezo
+activerecord_error_inclusion: non está incluído nas lista
+activerecord_error_invalid: non é válido
+activerecord_error_not_a_date: non é una data válida
+activerecord_error_not_a_number: non é un número
+activerecord_error_not_same_project: non pertence ao mesmo proxecto
+activerecord_error_taken: xa está sendo usado
+activerecord_error_too_long: é demasiado longo
+activerecord_error_too_short: é demasiado curto
+activerecord_error_wrong_length: a lonxitude é incorrecta
+button_activate: Activar
+button_add: Engadir
+button_annotate: Anotar
+button_apply: Aceptar
+button_archive: Arquivar
+button_back: Atrás
+button_cancel: Cancelar
+button_change: Cambiar
+button_change_password: Cambiar contrasinal
+button_check_all: Seleccionar todo
+button_clear: Anular
+button_configure: Configurar
+button_copy: Copiar
+button_create: Crear
+button_delete: Borrar
+button_download: Descargar
+button_edit: Modificar
+button_list: Listar
+button_lock: Bloquear
+button_log_time: Tempo dedicado
+button_login: Conexión
+button_move: Mover
+button_quote: Citar
+button_rename: Renomear
+button_reply: Respostar
+button_reset: Restablecer
+button_rollback: Volver a esta versión
+button_save: Gardar
+button_sort: Ordenar
+button_submit: Aceptar
+button_test: Probar
+button_unarchive: Desarquivar
+button_uncheck_all: Non seleccionar nada
+button_unlock: Desbloquear
+button_unwatch: Non monitorizar
+button_update: Actualizar
+button_view: Ver
+button_watch: Monitorizar
+default_activity_design: Deseño
+default_activity_development: Desenvolvemento
+default_doc_category_tech: Documentación técnica
+default_doc_category_user: Documentación de usuario
+default_issue_status_assigned: Asignada
+default_issue_status_closed: Pechada
+default_issue_status_feedback: Comentarios
+default_issue_status_new: Nova
+default_issue_status_rejected: Rexeitada
+default_issue_status_resolved: Resolta
+default_priority_high: Alta
+default_priority_immediate: Inmediata
+default_priority_low: Baixa
+default_priority_normal: Normal
+default_priority_urgent: Urxente
+default_role_developper: Desenvolvedor
+default_role_manager: Xefe de proxecto
+default_role_reporter: Informador
+default_tracker_bug: Erros
+default_tracker_feature: Tarefas
+default_tracker_support: Soporte
+enumeration_activities: Actividades (tempo dedicado)
+enumeration_doc_categories: Categorías do documento
+enumeration_issue_priorities: Prioridade das peticións
+error_can_t_load_default_data: "Non se puido cargar a configuración por defecto: %s"
+error_issue_not_found_in_project: 'A petición non se atopa ou non está asociada a este proxecto'
+error_scm_annotate: "Non existe a entrada ou non se puido anotar"
+error_scm_command_failed: "Aconteceu un erro ao acceder ó repositorio: %s"
+error_scm_not_found: "A entrada e/ou revisión non existe no repositorio."
+field_account: Conta
+field_activity: Actividade
+field_admin: Administrador
+field_assignable: Pódense asignar peticións a este perfil
+field_assigned_to: Asignado a
+field_attr_firstname: Atributo do nome
+field_attr_lastname: Atributo do apelido
+field_attr_login: Atributo do identificador
+field_attr_mail: Atributo do Email
+field_auth_source: Modo de identificación
+field_author: Autor
+field_base_dn: DN base
+field_category: Categoría
+field_column_names: Columnas
+field_comments: Comentario
+field_comments_sorting: Mostrar comentarios
+field_created_on: Creado
+field_default_value: Estado por defecto
+field_delay: Retraso
+field_description: Descrición
+field_done_ratio: %% Realizado
+field_downloads: Descargas
+field_due_date: Data fin
+field_effective_date: Data
+field_estimated_hours: Tempo estimado
+field_field_format: Formato
+field_filename: Arquivo
+field_filesize: Tamaño
+field_firstname: Nome
+field_fixed_version: Versión prevista
+field_hide_mail: Ocultar a miña dirección de correo
+field_homepage: Sitio web
+field_host: Anfitrión
+field_hours: Horas
+field_identifier: Identificador
+field_is_closed: Petición resolta
+field_is_default: Estado por defecto
+field_is_filter: Usado como filtro
+field_is_for_all: Para todos os proxectos
+field_is_in_chlog: Consultar as peticións no histórico
+field_is_in_roadmap: Consultar as peticións na planificación
+field_is_public: Público
+field_is_required: Obrigatorio
+field_issue: Petición
+field_issue_to_id: Petición relacionada
+field_language: Idioma
+field_last_login_on: Última conexión
+field_lastname: Apelido
+field_login: Identificador
+field_mail: Correo electrónico
+field_mail_notification: Notificacións por correo
+field_max_length: Lonxitude máxima
+field_min_length: Lonxitude mínima
+field_name: Nome
+field_new_password: Novo contrasinal
+field_notes: Notas
+field_onthefly: Creación do usuario "ao voo"
+field_parent: Proxecto pai
+field_parent_title: Páxina pai
+field_password: Contrasinal
+field_password_confirmation: Confirmación
+field_port: Porto
+field_possible_values: Valores posibles
+field_priority: Prioridade
+field_project: Proxecto
+field_redirect_existing_links: Redireccionar enlaces existentes
+field_regexp: Expresión regular
+field_role: Perfil
+field_searchable: Incluír nas búsquedas
+field_spent_on: Data
+field_start_date: Data de inicio
+field_start_page: Páxina principal
+field_status: Estado
+field_subject: Tema
+field_subproject: Proxecto secundario
+field_summary: Resumo
+field_time_zone: Zona horaria
+field_title: Título
+field_tracker: Tipo
+field_type: Tipo
+field_updated_on: Actualizado
+field_url: URL
+field_user: Usuario
+field_value: Valor
+field_version: Versión
+general_csv_decimal_separator: ','
+general_csv_encoding: ISO-8859-15
+general_csv_separator: ';'
+general_day_names: Luns,Martes,Mércores,Xoves,Venres,Sábado,Domingo
+general_first_day_of_week: '1'
+general_fmt_age: %d ano
+general_fmt_age_plural: %d anos
+general_fmt_date: %%d/%%m/%%Y
+general_fmt_datetime: %%d/%%m/%%Y %%H:%%M
+general_fmt_datetime_short: %%d/%%m %%H:%%M
+general_fmt_time: %%H:%%M
+general_lang_name: 'Galego'
+general_pdf_encoding: ISO-8859-15
+general_text_No: 'Non'
+general_text_Yes: 'Si'
+general_text_no: 'non'
+general_text_yes: 'si'
+gui_validation_error: 1 erro
+gui_validation_error_plural: %d erros
+label_activity: Actividade
+label_add_another_file: Engadir outro arquivo
+label_add_note: Engadir unha nota
+label_added: engadido
+label_added_time_by: Engadido por %s fai %s
+label_administration: Administración
+label_age: Idade
+label_ago: fai
+label_all: todos
+label_all_time: todo o tempo
+label_all_words: Tódalas palabras
+label_and_its_subprojects: %s e proxectos secundarios
+label_applied_status: Aplicar estado
+label_assigned_to_me_issues: Peticións asignadas a min
+label_associated_revisions: Revisións asociadas
+label_attachment: Arquivo
+label_attachment_delete: Borrar o arquivo
+label_attachment_new: Novo arquivo
+label_attachment_plural: Arquivos
+label_attribute: Atributo
+label_attribute_plural: Atributos
+label_auth_source: Modo de autenticación
+label_auth_source_new: Novo modo de autenticación
+label_auth_source_plural: Modos de autenticación
+label_authentication: Autenticación
+label_blocked_by: bloqueado por
+label_blocks: bloquea a
+label_board: Foro
+label_board_new: Novo foro
+label_board_plural: Foros
+label_boolean: Booleano
+label_browse: Ollar
+label_bulk_edit_selected_issues: Editar as peticións seleccionadas
+label_calendar: Calendario
+label_change_log: Cambios
+label_change_plural: Cambios
+label_change_properties: Cambiar propiedades
+label_change_status: Cambiar o estado
+label_change_view_all: Ver tódolos cambios
+label_changes_details: Detalles de tódolos cambios
+label_changeset_plural: Cambios
+label_chronological_order: En orde cronolóxica
+label_closed_issues: pechada
+label_closed_issues_plural: pechadas
+label_comment: Comentario
+label_comment_add: Engadir un comentario
+label_comment_added: Comentario engadido
+label_comment_delete: Borrar comentarios
+label_comment_plural: Comentarios
+label_commits_per_author: Commits por autor
+label_commits_per_month: Commits por mes
+label_confirmation: Confirmación
+label_contains: conten
+label_copied: copiado
+label_copy_workflow_from: Copiar fluxo de traballo dende
+label_current_status: Estado actual
+label_current_version: Versión actual
+label_custom_field: Campo personalizado
+label_custom_field_new: Novo campo personalizado
+label_custom_field_plural: Campos personalizados
+label_date: Data
+label_date_from: Dende
+label_date_range: Rango de datas
+label_date_to: Ata
+label_day_plural: días
+label_default: Por defecto
+label_default_columns: Columnas por defecto
+label_deleted: suprimido
+label_details: Detalles
+label_diff_inline: en liña
+label_diff_side_by_side: cara a cara
+label_disabled: deshabilitado
+label_display_per_page: 'Por páxina: %s'
+label_document: Documento
+label_document_added: Documento engadido
+label_document_new: Novo documento
+label_document_plural: Documentos
+label_download: %d Descarga
+label_download_plural: %d Descargas
+label_downloads_abbr: D/L
+label_duplicated_by: duplicada por
+label_duplicates: duplicada de
+label_end_to_end: fin a fin
+label_end_to_start: fin a principio
+label_enumeration_new: Novo valor
+label_enumerations: Listas de valores
+label_environment: Entorno
+label_equals: igual
+label_example: Exemplo
+label_export_to: 'Exportar a:'
+label_f_hour: %.2f hora
+label_f_hour_plural: %.2f horas
+label_feed_plural: Feeds
+label_feeds_access_key_created_on: Clave de acceso por RSS creada fai %s
+label_file_added: Arquivo engadido
+label_file_plural: Arquivos
+label_filter_add: Engadir o filtro
+label_filter_plural: Filtros
+label_float: Flotante
+label_follows: posterior a
+label_gantt: Gantt
+label_general: Xeral
+label_generate_key: Xerar clave
+label_help: Axuda
+label_history: Histórico
+label_home: Inicio
+label_in: en
+label_in_less_than: en menos que
+label_in_more_than: en mais que
+label_incoming_emails: Correos entrantes
+label_index_by_date: Ãndice por data
+label_index_by_title: Ãndice por título
+label_information: Información
+label_information_plural: Información
+label_integer: Número
+label_internal: Interno
+label_issue: Petición
+label_issue_added: Petición engadida
+label_issue_category: Categoría das peticións
+label_issue_category_new: Nova categoría
+label_issue_category_plural: Categorías das peticións
+label_issue_new: Nova petición
+label_issue_plural: Peticións
+label_issue_status: Estado da petición
+label_issue_status_new: Novo estado
+label_issue_status_plural: Estados das peticións
+label_issue_tracking: Peticións
+label_issue_updated: Petición actualizada
+label_issue_view_all: Ver tódalas peticións
+label_issue_watchers: Seguidores
+label_issues_by: Peticións por %s
+label_jump_to_a_project: Ir ao proxecto...
+label_language_based: Baseado no idioma
+label_last_changes: últimos %d cambios
+label_last_login: Última conexión
+label_last_month: último mes
+label_last_n_days: últimos %d días
+label_last_updates: Actualizado
+label_last_updates_plural: %d Actualizados
+label_last_week: última semana
+label_latest_revision: Última revisión
+label_latest_revision_plural: Últimas revisións
+label_ldap_authentication: Autenticación LDAP
+label_less_than_ago: fai menos de
+label_list: Lista
+label_loading: Cargando...
+label_logged_as: Conectado como
+label_login: Conexión
+label_logout: Desconexión
+label_max_size: Tamaño máximo
+label_me: eu mesmo
+label_member: Membro
+label_member_new: Novo membro
+label_member_plural: Membros
+label_message_last: Última mensaxe
+label_message_new: Nova mensaxe
+label_message_plural: Mensaxes
+label_message_posted: Mensaxe engadida
+label_min_max_length: Lonxitude mín - máx
+label_modification: %d modificación
+label_modification_plural: %d modificacións
+label_modified: modificado
+label_module_plural: Módulos
+label_month: Mes
+label_months_from: meses de
+label_more: Mais
+label_more_than_ago: fai mais de
+label_my_account: A miña conta
+label_my_page: A miña páxina
+label_my_projects: Os meus proxectos
+label_new: Novo
+label_new_statuses_allowed: Novos estados autorizados
+label_news: Noticia
+label_news_added: Noticia engadida
+label_news_latest: Últimas noticias
+label_news_new: Nova noticia
+label_news_plural: Noticias
+label_news_view_all: Ver tódalas noticias
+label_next: Seguinte
+label_no_change_option: (Sen cambios)
+label_no_data: Ningún dato a mostrar
+label_nobody: ninguén
+label_none: ningún
+label_not_contains: non conten
+label_not_equals: non igual
+label_on: de
+label_open_issues: aberta
+label_open_issues_plural: abertas
+label_optional_description: Descrición opcional
+label_options: Opcións
+label_overall_activity: Actividade global
+label_overview: Vistazo
+label_password_lost: ¿Esqueciches o contrasinal?
+label_per_page: Por páxina
+label_permissions: Permisos
+label_permissions_report: Informe de permisos
+label_personalize_page: Personalizar esta páxina
+label_planning: Planificación
+label_please_login: Conexión
+label_plugins: Extensións
+label_precedes: anterior a
+label_preferences: Preferencias
+label_preview: Previsualizar
+label_previous: Anterior
+label_project: Proxecto
+label_project_all: Tódolos proxectos
+label_project_latest: Últimos proxectos
+label_project_new: Novo proxecto
+label_project_plural: Proxectos
+label_public_projects: Proxectos públicos
+label_query: Consulta personalizada
+label_query_new: Nova consulta
+label_query_plural: Consultas personalizadas
+label_read: Ler...
+label_register: Rexistrar
+label_registered_on: Inscrito o
+label_registration_activation_by_email: activación de conta por correo
+label_registration_automatic_activation: activación automática de conta
+label_registration_manual_activation: activación manual de conta
+label_related_issues: Peticións relacionadas
+label_relates_to: relacionada con
+label_relation_delete: Eliminar relación
+label_relation_new: Nova relación
+label_renamed: renomeado
+label_reply_plural: Respostas
+label_report: Informe
+label_report_plural: Informes
+label_reported_issues: Peticións rexistradas por min
+label_repository: Repositorio
+label_repository_plural: Repositorios
+label_result_plural: Resultados
+label_reverse_chronological_order: En orde cronolóxica inversa
+label_revision: Revisión
+label_revision_plural: Revisións
+label_roadmap: Planificación
+label_roadmap_due_in: Remata en %s
+label_roadmap_no_issues: Non hai peticións para esta versión
+label_roadmap_overdue: %s tarde
+label_role: Perfil
+label_role_and_permissions: Perfiles e permisos
+label_role_new: Novo perfil
+label_role_plural: Perfiles
+label_scm: SCM
+label_search: Búsqueda
+label_search_titles_only: Buscar só en títulos
+label_send_information: Enviar información da conta ó usuario
+label_send_test_email: Enviar un correo de proba
+label_settings: Configuración
+label_show_completed_versions: Mostra as versións rematadas
+label_sort_by: Ordenar por %s
+label_sort_higher: Subir
+label_sort_highest: Primeiro
+label_sort_lower: Baixar
+label_sort_lowest: Último
+label_spent_time: Tempo dedicado
+label_start_to_end: comezo a fin
+label_start_to_start: comezo a comezo
+label_statistics: Estatísticas
+label_stay_logged_in: Lembrar contrasinal
+label_string: Texto
+label_subproject_plural: Proxectos secundarios
+label_text: Texto largo
+label_theme: Tema
+label_this_month: este mes
+label_this_week: esta semana
+label_this_year: este ano
+label_time_tracking: Control de tempo
+label_today: hoxe
+label_topic_plural: Temas
+label_total: Total
+label_tracker: Tipo
+label_tracker_new: Novo tipo
+label_tracker_plural: Tipos de peticións
+label_updated_time: Actualizado fai %s
+label_updated_time_by: Actualizado por %s fai %s
+label_used_by: Utilizado por
+label_user: Usuario
+label_user_activity: "Actividade de %s"
+label_user_mail_no_self_notified: "Non quero ser avisado de cambios feitos por min"
+label_user_mail_option_all: "Para calquera evento en tódolos proxectos"
+label_user_mail_option_none: "Só para elementos monitorizados ou relacionados comigo"
+label_user_mail_option_selected: "Para calquera evento dos proxectos seleccionados..."
+label_user_new: Novo usuario
+label_user_plural: Usuarios
+label_version: Versión
+label_version_new: Nova versión
+label_version_plural: Versións
+label_view_diff: Ver diferencias
+label_view_revisions: Ver as revisións
+label_watched_issues: Peticións monitorizadas
+label_week: Semana
+label_wiki: Wiki
+label_wiki_edit: Wiki edición
+label_wiki_edit_plural: Wiki edicións
+label_wiki_page: Wiki páxina
+label_wiki_page_plural: Wiki páxinas
+label_workflow: Fluxo de traballo
+label_year: Ano
+label_yesterday: onte
+mail_body_account_activation_request: 'Inscribiuse un novo usuario (%s). A conta está pendente de aprobación:'
+mail_body_account_information: Información sobre a súa conta
+mail_body_account_information_external: Pode usar a súa conta "%s" para conectarse.
+mail_body_lost_password: 'Para cambiar o seu contrasinal, faga clic no seguinte enlace:'
+mail_body_register: 'Para activar a súa conta, faga clic no seguinte enlace:'
+mail_body_reminder: "%d petición(s) asignadas a ti rematan nos próximos %d días:"
+mail_subject_account_activation_request: Petición de activación de conta %s
+mail_subject_lost_password: O teu contrasinal de %s
+mail_subject_register: Activación da conta de %s
+mail_subject_reminder: "%d petición(s) rematarán nos próximos días"
+notice_account_activated: A súa conta foi activada. Xa pode conectarse.
+notice_account_invalid_creditentials: Usuario ou contrasinal inválido.
+notice_account_lost_email_sent: Enviouse un correo con instrucións para elixir un novo contrasinal.
+notice_account_password_updated: Contrasinal modificado correctamente.
+notice_account_pending: "A súa conta creouse e está pendente da aprobación por parte do administrador."
+notice_account_register_done: Conta creada correctamente. Para activala, faga clic sobre o enlace que se lle enviou por correo.
+notice_account_unknown_email: Usuario descoñecido.
+notice_account_updated: Conta actualizada correctamente.
+notice_account_wrong_password: Contrasinal incorrecto.
+notice_can_t_change_password: Esta conta utiliza unha fonte de autenticación externa. Non é posible cambiar o contrasinal.
+notice_default_data_loaded: Configuración por defecto cargada correctamente.
+notice_email_error: Ocorreu un error enviando o correo (%s)
+notice_email_sent: Enviouse un correo a %s
+notice_failed_to_save_issues: "Imposible gravar %s petición(s) en %d seleccionado: %s."
+notice_feeds_access_key_reseted: A súa clave de acceso para RSS reiniciouse.
+notice_file_not_found: A páxina á que tenta acceder non existe.
+notice_locking_conflict: Os datos modificáronse por outro usuario.
+notice_no_issue_selected: "Ningunha petición seleccionada. Por favor, comprobe a petición que quere modificar"
+notice_not_authorized: Non ten autorización para acceder a esta páxina.
+notice_successful_connection: Conexión correcta.
+notice_successful_create: Creación correcta.
+notice_successful_delete: Borrado correcto.
+notice_successful_update: Modificación correcta.
+notice_unable_delete_version: Non se pode borrar a versión
+permission_add_issue_notes: Engadir notas
+permission_add_issue_watchers: Engadir seguidores
+permission_add_issues: Engadir peticións
+permission_add_messages: Enviar mensaxes
+permission_browse_repository: Ollar repositorio
+permission_comment_news: Comentar noticias
+permission_commit_access: Acceso de escritura
+permission_delete_issues: Borrar peticións
+permission_delete_messages: Borrar mensaxes
+permission_delete_own_messages: Borrar mensaxes propios
+permission_delete_wiki_pages: Borrar páxinas wiki
+permission_delete_wiki_pages_attachments: Borrar arquivos
+permission_edit_issue_notes: Modificar notas
+permission_edit_issues: Modificar peticións
+permission_edit_messages: Modificar mensaxes
+permission_edit_own_issue_notes: Modificar notas propias
+permission_edit_own_messages: Editar mensaxes propios
+permission_edit_own_time_entries: Modificar tempos dedicados propios
+permission_edit_project: Modificar proxecto
+permission_edit_time_entries: Modificar tempos dedicados
+permission_edit_wiki_pages: Modificar páxinas wiki
+permission_log_time: Anotar tempo dedicado
+permission_manage_boards: Administrar foros
+permission_manage_categories: Administrar categorías de peticións
+permission_manage_documents: Administrar documentos
+permission_manage_files: Administrar arquivos
+permission_manage_issue_relations: Administrar relación con outras peticións
+permission_manage_members: Administrar membros
+permission_manage_news: Administrar noticias
+permission_manage_public_queries: Administrar consultas públicas
+permission_manage_repository: Administrar repositorio
+permission_manage_versions: Administrar versións
+permission_manage_wiki: Administrar wiki
+permission_move_issues: Mover peticións
+permission_protect_wiki_pages: Protexer páxinas wiki
+permission_rename_wiki_pages: Renomear páxinas wiki
+permission_save_queries: Gravar consultas
+permission_select_project_modules: Seleccionar módulos do proxecto
+permission_view_calendar: Ver calendario
+permission_view_changesets: Ver cambios
+permission_view_documents: Ver documentos
+permission_view_files: Ver arquivos
+permission_view_gantt: Ver diagrama de Gantt
+permission_view_issue_watchers: Ver lista de seguidores
+permission_view_messages: Ver mensaxes
+permission_view_time_entries: Ver tempo dedicado
+permission_view_wiki_edits: Ver histórico do wiki
+permission_view_wiki_pages: Ver wiki
+project_module_boards: Foros
+project_module_documents: Documentos
+project_module_files: Arquivos
+project_module_issue_tracking: Peticións
+project_module_news: Noticias
+project_module_repository: Repositorio
+project_module_time_tracking: Control de tempo
+project_module_wiki: Wiki
+setting_activity_days_default: Días a mostrar na actividade do proxecto
+setting_app_subtitle: Subtítulo da aplicación
+setting_app_title: Título da aplicación
+setting_attachment_max_size: Tamaño máximo do arquivo
+setting_autofetch_changesets: Autorechear os commits do repositorio
+setting_autologin: Conexión automática
+setting_bcc_recipients: Ocultar as copias de carbón (bcc)
+setting_commit_fix_keywords: Palabras clave para a corrección
+setting_commit_logs_encoding: Codificación das mensaxes de commit
+setting_commit_ref_keywords: Palabras clave para a referencia
+setting_cross_project_issue_relations: Permitir relacionar peticións de distintos proxectos
+setting_date_format: Formato da data
+setting_default_language: Idioma por defecto
+setting_default_projects_public: Os proxectos novos son públicos por defecto
+setting_diff_max_lines_displayed: Número máximo de diferencias mostradas
+setting_display_subprojects_issues: Mostrar por defecto peticións de prox. secundarios no principal
+setting_emails_footer: Pe de mensaxes
+setting_enabled_scm: Activar SCM
+setting_feeds_limit: Límite de contido para sindicación
+setting_gravatar_enabled: Usar iconas de usuario (Gravatar)
+setting_host_name: Nome e ruta do servidor
+setting_issue_list_default_columns: Columnas por defecto para a lista de peticións
+setting_issues_export_limit: Límite de exportación de peticións
+setting_login_required: Requírese identificación
+setting_mail_from: Correo dende o que enviar mensaxes
+setting_mail_handler_api_enabled: Activar SW para mensaxes entrantes
+setting_mail_handler_api_key: Clave da API
+setting_per_page_options: Obxectos por páxina
+setting_plain_text_mail: só texto plano (non HTML)
+setting_protocol: Protocolo
+setting_repositories_encodings: Codificacións do repositorio
+setting_self_registration: Rexistro permitido
+setting_sequential_project_identifiers: Xerar identificadores de proxecto
+setting_sys_api_enabled: Habilitar SW para a xestión do repositorio
+setting_text_formatting: Formato de texto
+setting_time_format: Formato de hora
+setting_user_format: Formato de nome de usuario
+setting_welcome_text: Texto de benvida
+setting_wiki_compression: Compresión do historial do Wiki
+status_active: activo
+status_locked: bloqueado
+status_registered: rexistrado
+text_are_you_sure: ¿Está seguro?
+text_assign_time_entries_to_project: Asignar as horas ó proxecto
+text_caracters_maximum: %d caracteres como máximo.
+text_caracters_minimum: %d caracteres como mínimo
+text_comma_separated: Múltiples valores permitidos (separados por coma).
+text_default_administrator_account_changed: Conta de administrador por defecto modificada
+text_destroy_time_entries: Borrar as horas
+text_destroy_time_entries_question: Existen %.02f horas asignadas á petición que quere borrar. ¿Que quere facer ?
+text_diff_truncated: '... Diferencia truncada por exceder o máximo tamaño visualizable.'
+text_email_delivery_not_configured: "O envío de correos non está configurado, e as notificacións desactiváronse. \n Configure o servidor de SMTP en config/email.yml e reinicie a aplicación para activar os cambios."
+text_enumeration_category_reassign_to: 'Reasignar ó seguinte valor:'
+text_enumeration_destroy_question: '%d obxectos con este valor asignado.'
+text_file_repository_writable: Pódese escribir no repositorio
+text_issue_added: Petición %s engadida por %s.
+text_issue_category_destroy_assignments: Deixar as peticións sen categoría
+text_issue_category_destroy_question: Algunhas peticións (%d) están asignadas a esta categoría. ¿Que desexa facer?
+text_issue_category_reassign_to: Reasignar as peticións á categoría
+text_issue_updated: A petición %s actualizouse por %s.
+text_issues_destroy_confirmation: '¿Seguro que quere borrar as peticións seleccionadas?'
+text_issues_ref_in_commit_messages: Referencia e petición de corrección nas mensaxes
+text_journal_changed: cambiado de %s a %s
+text_journal_deleted: suprimido
+text_journal_set_to: fixado a %s
+text_length_between: Lonxitude entre %d e %d caracteres.
+text_load_default_configuration: Cargar a configuración por defecto
+text_min_max_length_info: 0 para ningunha restrición
+text_no_configuration_data: "Inda non se configuraron perfiles, nin tipos, estados e fluxo de traballo asociado a peticións. Recoméndase encarecidamente cargar a configuración por defecto. Unha vez cargada, poderá modificala."
+text_project_destroy_confirmation: ¿Estás seguro de querer eliminar o proxecto?
+text_project_identifier_info: 'Letras minúsculas (a-z), números e signos de puntuación permitidos.<br />Unha vez gardado, o identificador non pode modificarse.'
+text_reassign_time_entries: 'Reasignar as horas a esta petición:'
+text_regexp_info: ex. ^[A-Z0-9]+$
+text_repository_usernames_mapping: "Estableza a correspondencia entre os usuarios de Redmine e os presentes no log do repositorio.\nOs usuarios co mesmo nome ou correo en Redmine e no repositorio serán asociados automaticamente."
+text_rmagick_available: RMagick dispoñible (opcional)
+text_select_mail_notifications: Seleccionar os eventos a notificar
+text_select_project_modules: 'Seleccione os módulos a activar para este proxecto:'
+text_status_changed_by_changeset: Aplicado nos cambios %s
+text_subprojects_destroy_warning: 'Os proxectos secundarios: %s tamén se eliminarán'
+text_tip_task_begin_day: tarefa que comeza este día
+text_tip_task_begin_end_day: tarefa que comeza e remata este día
+text_tip_task_end_day: tarefa que remata este día
+text_tracker_no_workflow: Non hai ningún fluxo de traballo definido para este tipo de petición
+text_unallowed_characters: Caracteres non permitidos
+text_user_mail_option: "Dos proxectos non seleccionados, só recibirá notificacións sobre elementos monitorizados ou elementos nos que estea involucrado (por exemplo, peticións das que vostede sexa autor ou asignadas a vostede)."
+text_user_wrote: '%s escribiu:'
+text_wiki_destroy_confirmation: ¿Seguro que quere borrar o wiki e todo o seu contido?
+text_workflow_edit: Seleccionar un fluxo de traballo para actualizar
+warning_attachments_not_saved: "%d file(s) could not be saved."
+field_editable: Editable
+text_plugin_assets_writable: Plugin assets directory writable
+label_display: Display
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/he.yml b/lang/he.yml
index f95843b85..7a1519ae0 100644
--- a/lang/he.yml
+++ b/lang/he.yml
@@ -236,7 +236,7 @@ label_register: הרשמה
label_password_lost: ×בדה הסיסמה?
label_home: דף הבית
label_my_page: הדף שלי
-label_my_account: השבון שלי
+label_my_account: החשבון שלי
label_my_projects: ×”×¤×¨×•×™×§×˜×™× ×©×œ×™
label_administration: ×דמיניסטרציה
label_login: התחבר
@@ -292,7 +292,7 @@ label_confirmation: ×ישור
label_export_to: ×™×¦× ×œ
label_read: קר×...
label_public_projects: ×¤×¨×•×™×§×˜×™× ×¤×•×ž×‘×™×™×
-label_open_issues: פותח
+label_open_issues: פתוח
label_open_issues_plural: פתוחי×
label_closed_issues: סגור
label_closed_issues_plural: סגורי×
@@ -308,7 +308,7 @@ label_used_by: בשימוש ע"י
label_details: פרטי×
label_add_note: הוסף הערה
label_per_page: לכל דף
-label_calendar: לו"ח שנה
+label_calendar: לוח שנה
label_months_from: ×—×•×“×©×™× ×ž
label_gantt: ×’×נט
label_internal: פנימי
@@ -357,7 +357,7 @@ label_sort_higher: הזז למעלה
label_sort_lower: הזז למטה
label_sort_lowest: הזז לתחתית
label_roadmap: מפת הדרכי×
-label_roadmap_due_in: %s נגמר בעוד
+label_roadmap_due_in: נגמר בעוד %s
label_roadmap_overdue: %s מ×חר
label_roadmap_no_issues: ×ין נוש××™× ×œ×’×™×¨×¡× ×–×•
label_search: חפש
@@ -421,8 +421,8 @@ label_send_information: שלח מידע על חשבון למשתמש
label_year: שנה
label_month: חודש
label_week: שבוע
-label_date_from: מ×ת
-label_date_to: ×ל
+label_date_from: מת×ריך
+label_date_to: עד
label_language_based: מבוסס שפה
label_sort_by: מין לפי %s
label_send_test_email: שלח דו"ל בדיקה
@@ -487,7 +487,7 @@ 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_tip_task_begin_end_day: מטלה המתחילה ומסתיימת היו×
text_project_identifier_info: '×ותיות לטיניות (a-z), ×ž×¡×¤×¨×™× ×•×ž×§×¤×™×.<br />ברגע שנשמר, ×œ× × ×™×ª×Ÿ לשנות ×ת המזהה.'
text_caracters_maximum: ×ž×§×¡×™×ž×•× %d תווי×.
text_length_between: ×ורך בין %d ל %d תווי×.
@@ -618,27 +618,92 @@ label_overall_activity: פעילות כוללת
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
+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: Enable WS for incoming emails
-setting_mail_handler_api_key: API key
+setting_mail_handler_api_key: מפתח API
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
+field_parent_title: דף ×ב
+label_issue_watchers: צופי×
setting_commit_logs_encoding: Commit messages encoding
-button_quote: Quote
+button_quote: צטט
setting_sequential_project_identifiers: Generate sequential project identifiers
-notice_unable_delete_version: Unable to delete version
-label_renamed: renamed
-label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+notice_unable_delete_version: ×œ× × ×™×ª×Ÿ למחוק גירס×
+label_renamed: ×”×©× ×©×•× ×”
+label_copied: הועתק
+setting_plain_text_mail: טקסט פשוט בלבד (×œ×œ× HTML)
+permission_view_files: צפה בקבצי×
+permission_edit_issues: ערוך נוש××™×
+permission_edit_own_time_entries: ערוך ×ת לוג הזמן של עצמך
+permission_manage_public_queries: נהל ש×ילתות פומביות
+permission_add_issues: הוסף נוש×
+permission_log_time: תעד זמן שבוזבז
+permission_view_changesets: צפה בקבוצות שינויי×
+permission_view_time_entries: צפה בזמן שבוזבז
+permission_manage_versions: נהל גירס×ות
+permission_manage_wiki: נהל wiki
+permission_manage_categories: נהל קטגוריות נוש××™×
+permission_protect_wiki_pages: הגן כל דפי wiki
+permission_comment_news: הגב על החדשות
+permission_delete_messages: מחק הודעות
+permission_select_project_modules: בחר מודולי פרויקט
+permission_manage_documents: נהל מסמכי×
+permission_edit_wiki_pages: ערוך דפי wiki
+permission_add_issue_watchers: הוסף צופי×
+permission_view_gantt: צפה בג×נט
+permission_move_issues: ×”×–×– נוש××™×
+permission_manage_issue_relations: נהל יחס בין נוש××™×
+permission_delete_wiki_pages: מחק דפי wiki
+permission_manage_boards: נהל לוחות
+permission_delete_wiki_pages_attachments: מחק דבוקות
+permission_view_wiki_edits: צפה בהיסטורית wiki
+permission_add_messages: הצב הודעות
+permission_view_messages: צפה בהודעות
+permission_manage_files: נהל קבצי×
+permission_edit_issue_notes: ערוך רשימות
+permission_manage_news: נהל חדשות
+permission_view_calendar: צפה בלוח השנה
+permission_manage_members: נהל חברי×
+permission_edit_messages: ערוך הודעות
+permission_delete_issues: מחק נוש××™×
+permission_view_issue_watchers: צפה ברשימה צופי×
+permission_manage_repository: נהל מ×גר
+permission_commit_access: Commit access
+permission_browse_repository: סייר במ×גר
+permission_view_documents: צפה במסמכי×
+permission_edit_project: ערוך פרויקט
+permission_add_issue_notes: Add notes
+permission_save_queries: שמור ש×ילתות
+permission_view_wiki_pages: צפה ב-wiki
+permission_rename_wiki_pages: שנה ×©× ×©×œ דפי wiki
+permission_edit_time_entries: ערוך ×¨×™×©×•× ×–×ž× ×™×
+permission_edit_own_issue_notes: Edit own notes
+setting_gravatar_enabled: Use Gravatar user icons
+label_example: דוגמ×
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+permission_edit_own_messages: ערוך הודעות של עצמך
+permission_delete_own_messages: מחק הודעות של עצמך
+label_user_activity: "הפעילות של %s"
+label_updated_time_by: עודכן ע"י %s לפני %s
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+text_plugin_assets_writable: Plugin assets directory writable
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/hu.yml b/lang/hu.yml
index 28008aabb..697b26a1c 100644
--- a/lang/hu.yml
+++ b/lang/hu.yml
@@ -641,5 +641,70 @@ setting_sequential_project_identifiers: Szekvenciális projekt azonosítók gene
notice_unable_delete_version: A verziót nem lehet törölni
label_renamed: átnevezve
label_copied: lemásolva
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+setting_plain_text_mail: csak szöveg (nem HTML)
+permission_view_files: Fájlok megtekintése
+permission_edit_issues: Feladatok szerkesztése
+permission_edit_own_time_entries: Saját időnapló szerkesztése
+permission_manage_public_queries: Nyilvános kérések kezelése
+permission_add_issues: Feladat felvétele
+permission_log_time: Idő rögzítése
+permission_view_changesets: Változáskötegek megtekintése
+permission_view_time_entries: Időrögzítések megtekintése
+permission_manage_versions: Verziók kezelése
+permission_manage_wiki: Wiki kezelése
+permission_manage_categories: Feladat kategóriák kezelése
+permission_protect_wiki_pages: Wiki oldalak védelme
+permission_comment_news: Hírek kommentelése
+permission_delete_messages: Üzenetek törlése
+permission_select_project_modules: Projekt modulok kezelése
+permission_manage_documents: Dokumentumok kezelése
+permission_edit_wiki_pages: Wiki oldalak szerkesztése
+permission_add_issue_watchers: Megfigyelők felvétele
+permission_view_gantt: Gannt diagramm megtekintése
+permission_move_issues: Feladatok mozgatása
+permission_manage_issue_relations: Feladat kapcsolatok kezelése
+permission_delete_wiki_pages: Wiki oldalak törlése
+permission_manage_boards: Fórumok kezelése
+permission_delete_wiki_pages_attachments: Csatolmányok törlése
+permission_view_wiki_edits: Wiki történet megtekintése
+permission_add_messages: Üzenet beküldése
+permission_view_messages: Üzenetek megtekintése
+permission_manage_files: Fájlok kezelése
+permission_edit_issue_notes: Jegyzetek szerkesztése
+permission_manage_news: Hírek kezelése
+permission_view_calendar: Naptár megtekintése
+permission_manage_members: Tagok kezelése
+permission_edit_messages: Üzenetek szerkesztése
+permission_delete_issues: Feladatok törlése
+permission_view_issue_watchers: Megfigyelők listázása
+permission_manage_repository: Tárolók kezelése
+permission_commit_access: Commit hozzáférés
+permission_browse_repository: Tároló böngészése
+permission_view_documents: Dokumetumok megtekintése
+permission_edit_project: Projekt szerkesztése
+permission_add_issue_notes: Jegyzet rögzítése
+permission_save_queries: Kérések mentése
+permission_view_wiki_pages: Wiki megtekintése
+permission_rename_wiki_pages: Wiki oldalak átnevezése
+permission_edit_time_entries: Időnaplók szerkesztése
+permission_edit_own_issue_notes: Saját jegyzetek szerkesztése
+setting_gravatar_enabled: Felhasználói fényképek engedélyezése
+label_example: Példa
+text_repository_usernames_mapping: "Ãllítsd be a felhasználó összerendeléseket a Redmine, és a tároló logban található felhasználók között.\nAz azonos felhasználó nevek összerendelése automatikusan megtörténik."
+permission_edit_own_messages: Saját üzenetek szerkesztése
+permission_delete_own_messages: Saját üzenetek törlése
+label_user_activity: "%s tevékenységei"
+label_updated_time_by: "Módosította %s ennyivel ezelőtt: %s"
+text_diff_truncated: '... A diff fájl vége nem jelenik meg, mert hosszab, mint a megjeleníthető sorok száma.'
+setting_diff_max_lines_displayed: A megjelenítendő sorok száma (maximum) a diff fájloknál
+text_plugin_assets_writable: Plugin eszközök könyvtár írható
+warning_attachments_not_saved: "%d fájl mentése nem sikerült."
+button_create_and_continue: Létrehozás és folytatás
+text_custom_field_possible_values_info: 'Értékenként egy sor'
+label_display: Megmutat
+field_editable: Szerkeszthető
+setting_repository_log_display_limit: Maximum hány revízió jelenjen meg a fájl logban
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/it.yml b/lang/it.yml
index 20e53472c..418f3529d 100644
--- a/lang/it.yml
+++ b/lang/it.yml
@@ -24,8 +24,8 @@ 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_empty: non può essere vuoto
+activerecord_error_blank: non può essere lasciato in bianco
activerecord_error_too_long: è troppo lungo/a
activerecord_error_too_short: è troppo corto/a
activerecord_error_wrong_length: è della lunghezza sbagliata
@@ -108,7 +108,7 @@ field_value: Valore
field_category: Categoria
field_title: Titolo
field_project: Progetto
-field_issue: Issue
+field_issue: Segnalazione
field_status: Stato
field_notes: Note
field_is_closed: Chiude la segnalazione
@@ -169,7 +169,7 @@ field_default_value: Stato predefinito
setting_app_title: Titolo applicazione
setting_app_subtitle: Sottotitolo applicazione
setting_welcome_text: Testo di benvenuto
-setting_default_language: Lingua di default
+setting_default_language: Lingua predefinita
setting_login_required: Autenticazione richiesta
setting_self_registration: Auto-registrazione abilitata
setting_attachment_max_size: Massima dimensione allegati
@@ -177,12 +177,12 @@ setting_issues_export_limit: Limite esportazione segnalazioni
setting_mail_from: Indirizzo sorgente e-mail
setting_host_name: Nome host
setting_text_formatting: Formattazione testo
-setting_wiki_compression: Compressione di storia di Wiki
+setting_wiki_compression: Comprimi cronologia wiki
setting_feeds_limit: Limite contenuti del feed
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_commit_ref_keywords: Parole chiave riferimento
+setting_commit_fix_keywords: Parole chiave chiusura
setting_autologin: Login automatico
setting_date_format: Formato data
setting_cross_project_issue_relations: Consenti la creazione di relazioni tra segnalazioni in progetti differenti
@@ -353,7 +353,7 @@ label_sort_lower: Giù
label_sort_lowest: Sposta in fondo
label_roadmap: Roadmap
label_roadmap_due_in: Da ultimare in %s
-label_roadmap_overdue: %s late
+label_roadmap_overdue: %s di ritardo
label_roadmap_no_issues: Nessuna segnalazione per questa versione
label_search: Ricerca
label_result_plural: Risultati
@@ -482,7 +482,7 @@ text_length_between: Lunghezza compresa tra %d e %d caratteri.
text_tracker_no_workflow: Nessun workflow definito per questo tracker
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_issues_ref_in_commit_messages: Segnalazioni di riferimento e chiusura nei messaggi di commit
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: Sicuro di voler cancellare questo wiki e tutti i suoi contenuti?
@@ -530,7 +530,7 @@ 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)."
+text_user_mail_option: "Per i progetti non selezionati, riceverai solo le notifiche riguardanti le cose che osservi o nelle quali sei coinvolto (per esempio segnalazioni che hai creato o che ti sono state assegnate)."
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"
@@ -548,7 +548,7 @@ mail_body_account_activation_request: 'Un nuovo utente (%s) ha effettuato la reg
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
+field_time_zone: Fuso orario
text_caracters_minimum: Deve essere lungo almeno %d caratteri.
setting_bcc_recipients: Destinatari in copia nascosta (bcc)
button_annotate: Annota
@@ -557,10 +557,10 @@ 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
+notice_default_data_loaded: Configurazione predefinita caricata con successo.
+text_load_default_configuration: Carica la configurazione predefinita
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"
+error_can_t_load_default_data: "Non è stato possibile caricare la configurazione predefinita : %s"
button_update: Aggiorna
label_change_properties: Modifica le proprietà
label_general: Generale
@@ -578,7 +578,7 @@ 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_boards: Forum
project_module_issue_tracking: Tracking delle segnalazioni
project_module_wiki: Wiki
project_module_files: File
@@ -587,7 +587,7 @@ project_module_repository: Repository
project_module_news: Notizie
project_module_time_tracking: Time tracking
text_file_repository_writable: Repository dei file scrivibile
-text_default_administrator_account_changed: L'account amministrativo di default è stato modificato
+text_default_administrator_account_changed: L'account amministrativo predefinito è stato modificato
text_rmagick_available: RMagick disponibile (opzionale)
button_configure: Configura
label_plugins: Plugin
@@ -630,7 +630,7 @@ text_enumeration_destroy_question: '%d oggetti hanno un assegnamento su questo v
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
+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: Osservatori
@@ -640,5 +640,70 @@ setting_sequential_project_identifiers: Genera progetti con identificativi in se
notice_unable_delete_version: Impossibile cancellare la versione
label_renamed: rinominato
label_copied: copiato
-field_cache: Local cache
-setting_repositories_cache_directory: Cache directory for repositories
+setting_plain_text_mail: Solo testo (non HTML)
+permission_view_files: Vedi files
+permission_edit_issues: Modifica segnalazioni
+permission_edit_own_time_entries: Modifica propri time logs
+permission_manage_public_queries: Gestisci query pubbliche
+permission_add_issues: Aggiungi segnalazioni
+permission_log_time: Segna tempo impiegato
+permission_view_changesets: Vedi changesets
+permission_view_time_entries: Vedi tempi impiegati
+permission_manage_versions: Gestisci versioni
+permission_manage_wiki: Gestisci wiki
+permission_manage_categories: Gestisci categorie segnalazione
+permission_protect_wiki_pages: Proteggi pagine wiki
+permission_comment_news: Commenta notizie
+permission_delete_messages: Elimina messaggi
+permission_select_project_modules: Seleziona moduli progetto
+permission_manage_documents: Gestisci documenti
+permission_edit_wiki_pages: Modifica pagine wiki
+permission_add_issue_watchers: Aggiungi osservatori
+permission_view_gantt: Vedi diagrammi gantt
+permission_move_issues: Muovi segnalazioni
+permission_manage_issue_relations: Gestisci relazioni tra segnalazioni
+permission_delete_wiki_pages: Elimina pagine wiki
+permission_manage_boards: Gestisci forum
+permission_delete_wiki_pages_attachments: Elimina allegati
+permission_view_wiki_edits: Vedi cronologia wiki
+permission_add_messages: Aggiungi messaggi
+permission_view_messages: Vedi messaggi
+permission_manage_files: Gestisci files
+permission_edit_issue_notes: Modifica note
+permission_manage_news: Gestisci notizie
+permission_view_calendar: Vedi calendario
+permission_manage_members: Gestisci membri
+permission_edit_messages: Modifica messaggi
+permission_delete_issues: Elimina segnalazioni
+permission_view_issue_watchers: Vedi lista osservatori
+permission_manage_repository: Gestisci repository
+permission_commit_access: Permesso di commit
+permission_browse_repository: Sfoglia repository
+permission_view_documents: Vedi documenti
+permission_edit_project: Modifica progetti
+permission_add_issue_notes: Aggiungi note
+permission_save_queries: Salva query
+permission_view_wiki_pages: Vedi pagine wiki
+permission_rename_wiki_pages: Rinomina pagine wiki
+permission_edit_time_entries: Modifica time logs
+permission_edit_own_issue_notes: Modifica proprie note
+setting_gravatar_enabled: Usa icone utente Gravatar
+label_example: Esempio
+text_repository_usernames_mapping: "Seleziona per aggiornare la corrispondenza tra gli utenti Redmine e quelli presenti nel log del repository.\nGli utenti Redmine e repository con lo stesso username o email sono mappati automaticamente."
+permission_edit_own_messages: Modifica propri messaggi
+permission_delete_own_messages: Elimina propri messaggi
+label_user_activity: "attività di %s"
+label_updated_time_by: Aggiornato da %s %s fa
+text_diff_truncated: '... Le differenze sono state troncate perchè superano il limite massimo visualizzabile.'
+setting_diff_max_lines_displayed: Limite massimo di differenze (linee) mostrate
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/ja.yml b/lang/ja.yml
index 95c632b8d..b92287678 100644
--- a/lang/ja.yml
+++ b/lang/ja.yml
@@ -181,7 +181,7 @@ setting_text_formatting: ãƒ†ã‚­ã‚¹ãƒˆã®æ›¸å¼
setting_wiki_compression: Wiki履歴を圧縮ã™ã‚‹
setting_feeds_limit: フィード内容ã®ä¸Šé™
setting_autofetch_changesets: コミットを自動å–å¾—ã™ã‚‹
-setting_sys_api_enabled: リãƒã‚¸ãƒˆãƒªç®¡ç†ç”¨ã®Web Serviceを有効化ã™ã‚‹
+setting_sys_api_enabled: リãƒã‚¸ãƒˆãƒªç®¡ç†ç”¨ã®Web Serviceを有効ã«ã™ã‚‹
setting_commit_ref_keywords: å‚照用キーワード
setting_commit_fix_keywords: 修正用キーワード
setting_autologin: 自動ログイン
@@ -354,7 +354,7 @@ label_sort_lower: 下ã¸
label_sort_lowest: 一番下ã¸
label_roadmap: ロードマップ
label_roadmap_due_in: 期日ã¾ã§ %s
-label_roadmap_overdue: %s late
+label_roadmap_overdue: %sé…れ
label_roadmap_no_issues: ã“ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«å‘ã‘ã¦ã®ãƒã‚±ãƒƒãƒˆã¯ã‚りã¾ã›ã‚“
label_search: 検索
label_result_plural: çµæžœ
@@ -510,7 +510,7 @@ default_priority_normal: 通常
default_priority_high: 高ã‚
default_priority_urgent: 急ã„ã§
default_priority_immediate: 今ã™ã
-default_activity_design: デザイン作業
+default_activity_design: 設計作業
default_activity_development: 開発作業
enumeration_issue_priorities: ãƒã‚±ãƒƒãƒˆã®å„ªå…ˆåº¦
@@ -552,6 +552,7 @@ notice_account_pending: アカウントã¯ä½œæˆæ¸ˆã¿ã§ã€ç®¡ç†è€…ã®æ‰¿èªå¾
field_time_zone: タイムゾーン
text_caracters_minimum: 最低%d文字ã®é•·ã•ãŒå¿…è¦ã§ã™
setting_bcc_recipients: ブラインドカーボンコピーã§å—ä¿¡(bcc)
+setting_plain_text_mail: プレインテキストã®ã¿(HTMLãªã—)
button_annotate: 注釈
label_issues_by: %s別ã®ãƒã‚±ãƒƒãƒˆ
field_searchable: Searchable
@@ -619,27 +620,91 @@ label_overall_activity: å…¨ã¦ã®æ´»å‹•
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_subprojects_destroy_warning: 'サブプロジェクト %s も削除ã•れã¾ã™ã€‚'
+label_and_its_subprojects: %s ã¨ã‚µãƒ–プロジェクト
+mail_body_reminder: "%dä»¶ã®æ‹…当ãƒã‚±ãƒƒãƒˆã®æœŸé™ãŒ%d日以内ã«åˆ°æ¥ã—ã¾ã™:"
+mail_subject_reminder: "%dä»¶ã®ãƒã‚±ãƒƒãƒˆãŒæœŸé™é–“è¿‘ã§ã™"
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
+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: å—信メール用ã®Web Serviceを有効ã«ã™ã‚‹
+setting_mail_handler_api_key: APIキー
+text_email_delivery_not_configured: "メールをé€ä¿¡ã™ã‚‹ãŸã‚ã«å¿…è¦ãªè¨­å®šãŒè¡Œã‚れã¦ã„ãªã„ãŸã‚ã€ãƒ¡ãƒ¼ãƒ«é€šçŸ¥ã¯åˆ©ç”¨ã§ãã¾ã›ã‚“。\nconfig/email.ymlã§SMTPサーãƒã®è¨­å®šã‚’行ã„ã€ã‚¢ãƒ—リケーションをå†èµ·å‹•ã—ã¦ãã ã•ã„。"
+field_parent_title: 親ページ
label_issue_watchers: Watchers
-setting_commit_logs_encoding: Commit messages encoding
-button_quote: Quote
-setting_sequential_project_identifiers: Generate sequential project identifiers
-notice_unable_delete_version: Unable to delete version
+setting_commit_logs_encoding: コミットメッセージã®ã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚°
+button_quote: 引用
+setting_sequential_project_identifiers: プロジェクト識別å­ã‚’連番ã§ç”Ÿæˆã™ã‚‹
+notice_unable_delete_version: ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’削除ã§ãã¾ã›ã‚“
label_renamed: renamed
label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+permission_view_files: View files
+permission_edit_issues: Edit issues
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_public_queries: Manage public queries
+permission_add_issues: Add issues
+permission_log_time: Log spent time
+permission_view_changesets: View changesets
+permission_view_time_entries: View spent time
+permission_manage_versions: Manage versions
+permission_manage_wiki: Manage wiki
+permission_manage_categories: Manage issue categories
+permission_protect_wiki_pages: Protect wiki pages
+permission_comment_news: Comment news
+permission_delete_messages: Delete messages
+permission_select_project_modules: Select project modules
+permission_manage_documents: Manage documents
+permission_edit_wiki_pages: Edit wiki pages
+permission_add_issue_watchers: Add watchers
+permission_view_gantt: View gantt chart
+permission_move_issues: Move issues
+permission_manage_issue_relations: Manage issue relations
+permission_delete_wiki_pages: Delete wiki pages
+permission_manage_boards: Manage boards
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_view_wiki_edits: View wiki history
+permission_add_messages: Post messages
+permission_view_messages: View messages
+permission_manage_files: Manage files
+permission_edit_issue_notes: Edit notes
+permission_manage_news: Manage news
+permission_view_calendar: View calendrier
+permission_manage_members: Manage members
+permission_edit_messages: Edit messages
+permission_delete_issues: Delete issues
+permission_view_issue_watchers: View watchers list
+permission_manage_repository: Manage repository
+permission_commit_access: Commit access
+permission_browse_repository: Browse repository
+permission_view_documents: View documents
+permission_edit_project: Edit project
+permission_add_issue_notes: Add notes
+permission_save_queries: Save queries
+permission_view_wiki_pages: View wiki
+permission_rename_wiki_pages: Rename wiki pages
+permission_edit_time_entries: Edit time logs
+permission_edit_own_issue_notes: Edit own notes
+setting_gravatar_enabled: Gravatarユーザーアイコンを使用ã™ã‚‹
+label_example: 例
+text_repository_usernames_mapping: "リãƒã‚¸ãƒˆãƒªã®ãƒ­ã‚°ã‹ã‚‰æ¤œå‡ºã•れãŸãƒ¦ãƒ¼ã‚¶ãƒ¼åã‚’ã©ã®Redmineユーザーã«é–¢é€£ã¥ã‘ã‚‹ã®ã‹é¸æŠžã—ã¦ãã ã•ã„。\nログ上ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼åã¾ãŸã¯ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒRedmineã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¨ä¸€è‡´ã™ã‚‹å ´åˆã¯è‡ªå‹•çš„ã«é–¢é€£ã¥ã‘られã¾ã™ã€‚"
+permission_edit_own_messages: Edit own messages
+permission_delete_own_messages: Delete own messages
+label_user_activity: "%sã®æ´»å‹•"
+label_updated_time_by: %sãŒ%så‰ã«æ›´æ–°
+text_diff_truncated: '... 差分ã®è¡Œæ•°ãŒè¡¨ç¤ºå¯èƒ½ãªä¸Šé™ã‚’è¶…ãˆã¾ã—ãŸã€‚è¶…éŽåˆ†ã¯è¡¨ç¤ºã—ã¾ã›ã‚“。'
+setting_diff_max_lines_displayed: 差分ã®è¡¨ç¤ºè¡Œæ•°ã®ä¸Šé™
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d個ã®ãƒ•ァイルãŒä¿å­˜ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
+button_create_and_continue: 連続作æˆ
+text_custom_field_possible_values_info: 'é¸æŠžè‚¢ã®å€¤ã¯1行ã«1個ãšã¤è¨˜è¿°ã—ã¦ãã ã•ã„。'
+label_display: 表示
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/ko.yml b/lang/ko.yml
index 0d96274f2..008fe866c 100644
--- a/lang/ko.yml
+++ b/lang/ko.yml
@@ -55,13 +55,13 @@ general_day_names: 월요ì¼,화요ì¼,수요ì¼,목요ì¼,금요ì¼,토요ì¼,ì
general_first_day_of_week: '7'
notice_account_updated: ê³„ì •ì´ ì„±ê³µì ìœ¼ë¡œ 변경 ë˜ì—ˆìŠµë‹ˆë‹¤.
-notice_account_invalid_creditentials: ìž˜ëª»ëœ ê³„ì • ë˜ëŠ” 패스워드
+notice_account_invalid_creditentials: ìž˜ëª»ëœ ê³„ì • ë˜ëŠ” 비밀번호
notice_account_password_updated: 비밀번호가 잘 변경ë˜ì—ˆìŠµë‹ˆë‹¤.
-notice_account_wrong_password: ìž˜ëª»ëœ íŒ¨ìŠ¤ì›Œë“œ
-notice_account_register_done: ê³„ì •ì´ ì„±ê³µì ìœ¼ë¡œ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. ê³„ì •ì„ í™œì„±í™” 하기 위해서 수신한 Emailì˜ ë§í¬ë¥¼ í´ë¦­í•´ì£¼ì„¸ìš”.
+notice_account_wrong_password: ìž˜ëª»ëœ ë¹„ë°€ë²ˆí˜¸
+notice_account_register_done: ê³„ì •ì´ ìž˜ 만들어졌습니다. ê³„ì •ì„ í™œì„±í™”í•˜ì‹œë ¤ë©´ ë°›ì€ ë©”ì¼ì˜ ë§í¬ë¥¼ í´ë¦­í•´ì£¼ì„¸ìš”.
notice_account_unknown_email: 알려지지 ì•Šì€ ì‚¬ìš©ìž.
-notice_can_t_change_password: ì´ ê³„ì •ì€ ì™¸ë¶€ ì¸ì¦ì„ ì´ìš©í•©ë‹ˆë‹¤. 비밀번호 ë³€ê²½ì´ ë¶ˆê°€ëŠ¥ 합니다.
-notice_account_lost_email_sent: 새로운 패스워드를 위한 Emailì´ ë°œì†¡ë˜ì—ˆìŠµë‹ˆë‹¤.
+notice_can_t_change_password: ì´ ê³„ì •ì€ ì™¸ë¶€ ì¸ì¦ì„ ì´ìš©í•©ë‹ˆë‹¤. 비밀번호를 변경할 수 없습니다.
+notice_account_lost_email_sent: 새로운 비밀번호를 위한 ë©”ì¼ì´ 발송ë˜ì—ˆìŠµë‹ˆë‹¤.
notice_account_activated: ê³„ì •ì´ í™œì„±í™” ë˜ì—ˆìŠµë‹ˆë‹¤. ì´ì œ ë¡œê·¸ì¸ í•˜ì‹¤ìˆ˜ 있습니다.
notice_successful_create: ìƒì„± 성공.
notice_successful_update: 변경 성공.
@@ -70,14 +70,14 @@ notice_successful_connection: 연결 성공.
notice_file_not_found: 요청하신 페ì´ì§€ëŠ” ì‚­ì œë˜ì—ˆê±°ë‚˜ 옮겨졌습니다.
notice_locking_conflict: 다른 사용ìžì— ì˜í•´ì„œ ë°ì´í„°ê°€ 변경ë˜ì—ˆìŠµë‹ˆë‹¤.
notice_not_authorized: ì´ íŽ˜ì´ì§€ì— 접근할 ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤.
-notice_email_sent: %s 님ì—게 Emailì´ ë°œì†¡ë˜ì—ˆìŠµë‹ˆë‹¤.
+notice_email_sent: %s님ì—게 ë©”ì¼ì´ 발송ë˜ì—ˆìŠµë‹ˆë‹¤.
notice_email_error: ë©”ì¼ì„ 전송하는 ê³¼ì •ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. (%s)
notice_feeds_access_key_reseted: RSSì— ì ‘ê·¼ê°€ëŠ¥í•œ 열쇠(key)ê°€ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤.
-notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s."
-notice_no_issue_selected: "ì´ìŠˆê°€ ì„ íƒë˜ì§€ 않았습니다. 수정하기 ì›í•˜ëŠ” ì´ìŠˆë¥¼ ì„ íƒí•˜ì„¸ìš”"
+notice_failed_to_save_issues: "ì €ìž¥ì— ì‹¤íŒ¨í•˜ì˜€ìŠµë‹ˆë‹¤: 실패 %d(ì„ íƒ %d): %s."
+notice_no_issue_selected: "ì¼ê°ì´ ì„ íƒë˜ì§€ 않았습니다. 수정하기 ì›í•˜ëŠ” ì¼ê°ì„ ì„ íƒí•˜ì„¸ìš”"
error_scm_not_found: 소스 ì €ìž¥ì†Œì— í•´ë‹¹ ë‚´ìš©ì´ ì¡´ìž¬í•˜ì§€ 않습니다.
-error_scm_command_failed: "An error occurred when trying to access the repository: %s"
+error_scm_command_failed: "ì €ìž¥ì†Œì— ì ‘ê·¼í•˜ëŠ” ë„ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤.: %s"
mail_subject_lost_password: ë‹¹ì‹ ì˜ ë¹„ë°€ë²ˆí˜¸ (%s)
mail_body_lost_password: '비밀번호를 변경하기 위해서 ë§í¬ë¥¼ ì´ìš©í•˜ì„¸ìš”'
@@ -97,7 +97,7 @@ field_mail: ë©”ì¼
field_filename: 파ì¼
field_filesize: í¬ê¸°
field_downloads: 다운로드
-field_author: ë³´ê³ ìž
+field_author: ì €ìž
field_created_on: 보고시간
field_updated_on: 변경시간
field_field_format: í¬ë§·
@@ -110,44 +110,44 @@ field_value: ê°’
field_category: 카테고리
field_title: 제목
field_project: 프로ì íЏ
-field_issue: ì´ìŠˆ
+field_issue: ì¼ê°
field_status: ìƒíƒœ
-field_notes: 노트
-field_is_closed: ì™„ë£Œëœ ì´ìŠˆ
+field_notes: ë§ê¸€
+field_is_closed: ì™„ë£Œëœ ì¼ê°
field_is_default: 기본값
field_tracker: 구분
field_subject: 제목
field_due_date: 완료 기한
field_assigned_to: 담당ìž
field_priority: 우선순위
-field_fixed_version: Target version
-field_user: 유저
+field_fixed_version: 목표버전
+field_user: 사용ìž
field_role: ì—­í• 
field_homepage: 홈페ì´ì§€
field_is_public: 공개
field_parent: ìƒìœ„ 프로ì íЏ
-field_is_in_chlog: 변경ì´ë ¥(changelog)ì—서 보여지는 ì´ìŠˆë“¤
-field_is_in_roadmap: 로드맵ì—서 보여지는 ì´ìŠˆë“¤
+field_is_in_chlog: 변경ì´ë ¥(changelog)ì—서 표시할 ì¼ê°ë“¤
+field_is_in_roadmap: 로드맵ì—서표시할 ì¼ê°ë“¤
field_login: 로그ì¸
field_mail_notification: ë©”ì¼ ì•Œë¦¼
field_admin: 관리ìž
-field_last_login_on: 최종 ì ‘ì†
+field_last_login_on: 마지막 로그ì¸
field_language: 언어
field_effective_date: ì¼ìž
field_password: 비밀번호
-field_new_password: 신규 비밀번호
+field_new_password: 새 비밀번호
field_password_confirmation: 비밀번호 확ì¸
field_version: 버전
field_type: 타입
field_host: 호스트
field_port: í¬íЏ
field_account: 계정
-field_base_dn: Base DN
+field_base_dn: 기본 DN
field_attr_login: ë¡œê·¸ì¸ ì†ì„±
field_attr_firstname: ì´ë¦„ ì†ì„±
field_attr_lastname: 성 ì†ì„±
field_attr_mail: ë©”ì¼ ì†ì„±
-field_onthefly: On-the-fly user creation
+field_onthefly: 빠른 ì‚¬ìš©ìž ìƒì„±
field_start_date: 시작시간
field_done_ratio: 완료 %%
field_auth_source: ì¸ì¦ 방법
@@ -161,10 +161,10 @@ field_activity: 작업종류
field_spent_on: 작업시간
field_identifier: ì‹ë³„ìž
field_is_filter: 필터로 사용ë¨
-field_issue_to_id: ì—°ê´€ëœ ì´ìŠˆ
+field_issue_to_id: ì—°ê´€ëœ ì¼ê°
field_delay: 지연
-field_assignable: ì´ ì—­í• ì— í• ë‹¹ë ìˆ˜ 있는 ì´ìŠˆ
-field_redirect_existing_links: Redirect existing links
+field_assignable: ì´ ì—­í• ì— í• ë‹¹ë ìˆ˜ 있는 ì¼ê°
+field_redirect_existing_links: ê¸°ì¡´ì˜ ë§í¬ë¡œ ëŒë ¤ë³´ëƒ„(redirect)
field_estimated_hours: 추정시간
field_column_names: 컬럼
field_default_value: 기본값
@@ -174,62 +174,62 @@ setting_app_subtitle: ë ˆë“œë§ˆì¸ ë¶€ì œëª©
setting_welcome_text: í™˜ì˜ ë©”ì‹œì§€
setting_default_language: 기본 언어
setting_login_required: ì¸ì¦ì´ 필요함.
-setting_self_registration: Self-registration
+setting_self_registration: ì‚¬ìš©ìž ì§ì ‘등ë¡
setting_attachment_max_size: 최대 ì²¨ë¶€íŒŒì¼ í¬ê¸°
-setting_issues_export_limit: Issues export limit
-setting_mail_from: Emission mail address
+setting_issues_export_limit: ì¼ê° 내보내기 제한 개수
+setting_mail_from: 발신 ë©”ì¼ ì£¼ì†Œ
setting_host_name: 호스트 ì´ë¦„
-setting_text_formatting: í…스트 형ì‹
-setting_wiki_compression: 위키 기ë¡(history) ì••ì¶•
-setting_feeds_limit: Feed content limit
-setting_autofetch_changesets: Autofetch commits
-setting_sys_api_enabled: Enable WS for repository management
-setting_commit_ref_keywords: ì´ìŠˆ ì°¸ì¡°ì— ì‚¬ìš©í•  키워드들
-setting_commit_fix_keywords: ì´ìŠˆ í•´ê²°ì— ì‚¬ìš©í•  키워드들
+setting_text_formatting: 본문 형ì‹
+setting_wiki_compression: 위키 ì´ë ¥ ì••ì¶•
+setting_feeds_limit: 내용 피드(RSS Feed) 제한 개수
+setting_autofetch_changesets: ì»¤ë°‹ëœ ë³€ê²½ë¬¶ìŒì„ ìžë™ìœ¼ë¡œ 가져오기
+setting_sys_api_enabled: 저장소 관리ìžì— WS 를 허용
+setting_commit_ref_keywords: ì¼ê° ì°¸ì¡°ì— ì‚¬ìš©í•  키워드들
+setting_commit_fix_keywords: ì¼ê° í•´ê²°ì— ì‚¬ìš©í•  키워드들
setting_autologin: ìžë™ 로그ì¸
setting_date_format: ë‚ ì§œ 형ì‹
-setting_cross_project_issue_relations: 프로ì íŠ¸ê°„ ì´ìŠˆì— ê´€ë ¨ì„ ë§ºëŠ” ê²ƒì„ í—ˆìš©
-setting_issue_list_default_columns: ì´ìŠˆ 목ë¡ì— 보여줄 기본 컬럼들
+setting_cross_project_issue_relations: 프로ì íŠ¸ê°„ ì¼ê°ì— ê´€ê³„ì„ ë§ºëŠ” ê²ƒì„ í—ˆìš©
+setting_issue_list_default_columns: ì¼ê° 목ë¡ì— 보여줄 기본 컬럼들
setting_repositories_encodings: 저장소 ì¸ì½”딩
setting_emails_footer: ë©”ì¼ ê¼¬ë¦¬
label_user: 사용ìž
label_user_plural: 사용ìžê´€ë¦¬
-label_user_new: 신규 유저
+label_user_new: 새 사용ìž
label_project: 프로ì íЏ
-label_project_new: ì‹ ê·œ 프로ì íЏ
+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_issue: ì¼ê°
+label_issue_new: 새 ì¼ê°ë§Œë“¤ê¸°
+label_issue_plural: ì¼ê°
+label_issue_view_all: 모든 ì¼ê° 보기
label_document: 문서
-label_document_new: 새로운 문서
+label_document_new: 새 문서
label_document_plural: 문서
label_role: ì—­í• 
label_role_plural: ì—­í• 
-label_role_new: 새로운 역할
+label_role_new: 새 역할
label_role_and_permissions: 권한관리
label_member: 담당ìž
-label_member_new: 새로운 담당ìž
+label_member_new: 새 담당ìž
label_member_plural: 담당ìž
-label_tracker: ì´ìŠˆ 유형
-label_tracker_plural: ì´ìŠˆ 유형
-label_tracker_new: 새로운 ì´ìŠˆ 유형
-label_workflow: 워í¬í”Œë¡œ(Workflow)
-label_issue_status: ì´ìŠˆ ìƒíƒœ
-label_issue_status_plural: ì´ìŠˆ ìƒíƒœ
-label_issue_status_new: 새로운 ì´ìŠˆ ìƒíƒœ
+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_custom_field_new: 새 ì‚¬ìš©ìž ì •ì˜ í•­ëª©
label_enumerations: 코드값 설정
-label_enumeration_new: 새로운 코드값
+label_enumeration_new: 새 코드값
label_information: ì •ë³´
label_information_plural: ì •ë³´
label_please_login: 로그ì¸í•˜ì„¸ìš”.
@@ -243,15 +243,15 @@ label_administration: 관리ìž
label_login: 로그ì¸
label_logout: 로그아웃
label_help: ë„움ë§
-label_reported_issues: ë³´ê³ ëœ ì´ìŠˆ
-label_assigned_to_me_issues: 나ì—게 í• ë‹¹ëœ ì´ìŠˆ
+label_reported_issues: 보고한 ì¼ê°
+label_assigned_to_me_issues: 나ì—게 í• ë‹¹ëœ ì¼ê°
label_last_login: 최종 ì ‘ì†
label_last_updates: 최종 변경 내역
label_last_updates_plural: 최종변경 %d
-label_registered_on: Registered on
-label_activity: ì§„í–‰ì¤‘ì¸ ìž‘ì—…
-label_new: 신규
-label_logged_as: â–¶
+label_registered_on: 등ë¡ì‹œê°
+label_activity: 작업내역
+label_new: 새로 만들기
+label_logged_as: '로그ì¸ê³„ì •:'
label_environment: 환경
label_authentication: ì¸ì¦ì„¤ì •
label_auth_source: ì¸ì¦ 모드
@@ -272,7 +272,7 @@ label_download: %d 다운로드
label_download_plural: %d 다운로드
label_no_data: ë°ì´í„°ê°€ 없습니다.
label_change_status: ìƒíƒœ 변경
-label_history: 히스토리
+label_history: ì´ë ¥
label_attachment: 파ì¼
label_attachment_new: 파ì¼ì¶”ê°€
label_attachment_delete: 파ì¼ì‚­ì œ
@@ -280,7 +280,7 @@ label_attachment_plural: 관련파ì¼
label_report: 보고서
label_report_plural: 보고서
label_news: 뉴스
-label_news_new: 뉴스추가
+label_news_new: 새 뉴스
label_news_plural: 뉴스
label_news_latest: 최근 뉴스
label_news_view_all: 모든 뉴스
@@ -288,7 +288,7 @@ label_change_log: 변경 로그
label_settings: 설정
label_overview: 개요
label_version: 버전
-label_version_new: 새로운 버전
+label_version_new: 새 버전
label_version_plural: 버전
label_confirmation: 확ì¸
label_export_to: 내보내기
@@ -298,25 +298,25 @@ label_open_issues: 진행중
label_open_issues_plural: 진행중
label_closed_issues: 완료ë¨
label_closed_issues_plural: 완료ë¨
-label_total: Total
+label_total: 합계
label_permissions: 허가권한
-label_current_status: ì´ìŠˆ ìƒíƒœ
-label_new_statuses_allowed: 허용ë˜ëŠ” ì´ìŠˆ ìƒíƒœ
+label_current_status: ì¼ê° ìƒíƒœ
+label_new_statuses_allowed: 허용ë˜ëŠ” ì¼ê° ìƒíƒœ
label_all: 모ë‘
label_none: ì—†ìŒ
label_next: 다ìŒ
label_previous: ì´ì „
label_used_by: 사용ë¨
-label_details: ìƒì„¸
-label_add_note: ì´ìŠˆë…¸íŠ¸ 추가
+label_details: ìžì„¸ížˆ
+label_add_note: ì¼ê°ë§ê¸€ 추가
label_per_page: 페ì´ì§€ë³„
label_calendar: 달력
label_months_from: 개월 ë™ì•ˆ | 다ìŒë¶€í„°
label_gantt: Gantt 챠트
-label_internal: Internal
+label_internal: ë‚´ë¶€
label_last_changes: 지난 변경사항 %d 건
label_change_view_all: 모든 변경 내역 보기
-label_personalize_page: 입맛대로 구성하기(Drag & Drop)
+label_personalize_page: 입맛대로 구성하기
label_comment: 댓글
label_comment_plural: 댓글
label_comment_add: 댓글 추가
@@ -324,7 +324,7 @@ label_comment_added: ëŒ“ê¸€ì´ ì¶”ê°€ë˜ì—ˆìŠµë‹ˆë‹¤.
label_comment_delete: 댓글 삭제
label_query: ì‚¬ìš©ìž ê²€ìƒ‰ì¡°ê±´
label_query_plural: ì‚¬ìš©ìž ê²€ìƒ‰ì¡°ê±´
-label_query_new: 새로운 ì‚¬ìš©ìž ê²€ìƒ‰ì¡°ê±´
+label_query_new: 새 ì‚¬ìš©ìž ê²€ìƒ‰ì¡°ê±´
label_filter_add: 필터 추가
label_filter_plural: í•„í„°
label_equals: ì´ë‹¤
@@ -344,16 +344,16 @@ label_repository: 저장소
label_browse: 저장소 살피기
label_modification: %d 변경
label_modification_plural: %d 변경
-label_revision: 개정íŒ(Revision)
-label_revision_plural: 개정íŒ(Revisions)
-label_added: added
-label_modified: modified
-label_deleted: deleted
+label_revision: 개정íŒ
+label_revision_plural: 개정íŒ
+label_added: 추가ë¨
+label_modified: 변경ë¨
+label_deleted: ì‚­ì œë¨
label_latest_revision: 최근 개정íŒ
label_latest_revision_plural: 최근 개정íŒ
label_view_revisions: ê°œì •íŒ ë³´ê¸°
label_max_size: 최대 í¬ê¸°
-label_on: 'on'
+label_on: 'ì „ì²´: '
label_sort_highest: 최ìƒë‹¨ìœ¼ë¡œ
label_sort_higher: 위로
label_sort_lower: 아래로
@@ -361,7 +361,7 @@ label_sort_lowest: 최하단으로
label_roadmap: 로드맵
label_roadmap_due_in: 기한 %s
label_roadmap_overdue: %s 지연
-label_roadmap_no_issues: ì´ë²„ì „ì— í•´ë‹¹í•˜ëŠ” ì´ìŠˆ ì—†ìŒ
+label_roadmap_no_issues: ì´ ë²„ì „ì— í•´ë‹¹í•˜ëŠ” ì¼ê° ì—†ìŒ
label_search: 검색
label_result_plural: ê²°ê³¼
label_all_words: 모든 단어
@@ -376,7 +376,7 @@ label_current_version: 현재 버전
label_preview: 미리보기
label_feed_plural: 피드(Feeds)
label_changes_details: 모든 ìƒì„¸ 변경 ë‚´ì—­
-label_issue_tracking: ì´ìŠˆ ì¶”ì 
+label_issue_tracking: ì¼ê° ì¶”ì 
label_spent_time: 작업 시간
label_f_hour: %.2f 시간
label_f_hour_plural: %.2f 시간
@@ -385,24 +385,24 @@ label_change_plural: 변경사항들
label_statistics: 통계
label_commits_per_month: 월별 커밋 내역
label_commits_per_author: ì•„ì´ë””별 커밋 ë‚´ì—­
-label_view_diff: diff 보기
+label_view_diff: ì°¨ì´ì  보기
label_diff_inline: 한줄로
label_diff_side_by_side: ë‘줄로
-label_options: Options
-label_copy_workflow_from: Copy workflow from
+label_options: 옵션
+label_copy_workflow_from: 워í¬í”Œë¡œìš°ë¥¼ 복사해올 ì¼ê°ìœ í˜•
label_permissions_report: 권한 보고서
-label_watched_issues: ê°ì‹œì¤‘ì¸ ì´ìŠˆ
-label_related_issues: ì—°ê²°ëœ ì´ìŠˆ
-label_applied_status: Applied status
+label_watched_issues: 지켜보고 있는 ì¼ê°
+label_related_issues: ì—°ê²°ëœ ì¼ê°
+label_applied_status: ì ìš©ëœ ìƒíƒœ
label_loading: ì½ëŠ” 중...
-label_relation_new: New relation
-label_relation_delete: Delete relation
-label_relates_to: ë‹¤ìŒ ì´ìŠˆì™€ 관련ë˜ì–´ 있ìŒ
-label_duplicates: ë‹¤ìŒ ì´ìŠˆì™€ 중복ë¨.
-label_blocks: ë‹¤ìŒ ì´ìŠˆê°€ í•´ê²°ì„ ë§‰ê³  있ìŒ.
-label_blocked_by: 막고 있는 ì´ìŠˆ
-label_precedes: ë‹¤ìŒ ì´ìŠˆë³´ë‹¤ 앞서서 처리해야 함.
-label_follows: 선처리 ì´ìŠˆ
+label_relation_new: 새 관계
+label_relation_delete: 관계 지우기
+label_relates_to: ë‹¤ìŒ ì¼ê°ê³¼ 관련ë˜ì–´ 있ìŒ
+label_duplicates: ë‹¤ìŒ ì¼ê°ê³¼ 중복ë¨.
+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
@@ -412,32 +412,32 @@ label_disabled: 비활성화
label_show_completed_versions: ì™„ë£Œëœ ë²„ì „ 보기
label_me: 나
label_board: 게시íŒ
-label_board_new: ì‹ ê·œ 게시íŒ
+label_board_new: 새 게시íŒ
label_board_plural: 게시íŒ
label_topic_plural: 주제
label_message_plural: 관련글
-label_message_last: 최종 글
+label_message_last: 마지막 글
label_message_new: 새글쓰기
label_reply_plural: 답글
label_send_information: 사용ìžì—게 계정정보를 보냄
label_year: ë…„
label_month: ì›”
label_week: 주
-label_date_from: ì—서
-label_date_to: (으)로
-label_language_based: Language based
+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 created %s ago
+label_feeds_access_key_created_on: RSSì— ì ‘ê·¼ê°€ëŠ¥í•œ 열쇠(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_changeset_plural: 변경묶ìŒ
label_default_columns: 기본 컬럼
label_no_change_option: (수정 안함)
-label_bulk_edit_selected_issues: ì„ íƒëœ ì´ìŠˆë“¤ì„ í•œêº¼ë²ˆì— ìˆ˜ì •í•˜ê¸°
+label_bulk_edit_selected_issues: ì„ íƒëœ ì¼ê°ë“¤ì„ í•œêº¼ë²ˆì— ìˆ˜ì •í•˜ê¸°
label_theme: 테마
label_default: 기본
label_search_titles_only: 제목ì—서만 찾기
@@ -470,13 +470,13 @@ button_activate: 활성화
button_sort: ì •ë ¬
button_log_time: 작업시간 기ë¡
button_rollback: ì´ ë²„ì „ìœ¼ë¡œ 롤백
-button_watch: ê°ì‹œí•˜ê¸°
-button_unwatch: ê°ì‹œí•´ì œ
+button_watch: 지켜보기
+button_unwatch: 관심ë„기
button_reply: 답글
button_archive: 잠금보관
button_unarchive: 잠금보관해제
button_reset: 리셋
-button_rename: ì´ë¦„ 변경
+button_rename: ì´ë¦„변경
status_active: 사용중
status_registered: 등ë¡ëŒ€ê¸°
@@ -486,7 +486,7 @@ text_select_mail_notifications: 알림메ì¼ì´ 필요한 ìž‘ì—…ì„ ì„ íƒí•˜ì„¸
text_regexp_info: 예) ^[A-Z0-9]+$
text_min_max_length_info: 0 는 ì œí•œì´ ì—†ìŒì„ ì˜ë¯¸í•¨
text_project_destroy_confirmation: ì´ í”„ë¡œì íŠ¸ë¥¼ 삭제하고 모든 ë°ì´í„°ë¥¼ 지우시겠습니까?
-text_workflow_edit: 워í¬í”Œë¡œë¥¼ 수정하기 위해서 ì—­í• ê³¼ ì´ìŠˆìœ í˜•ì„ ì„ íƒí•˜ì„¸ìš”.
+text_workflow_edit: 워í¬í”Œë¡œë¥¼ 수정하기 위해서 ì—­í• ê³¼ ì¼ê°ìœ í˜•ì„ ì„ íƒí•˜ì„¸ìš”.
text_are_you_sure: ê³„ì† ì§„í–‰ 하시겠습니까?
text_journal_changed: %sì—서 %s(으)로 변경
text_journal_set_to: %s로 설정
@@ -500,14 +500,14 @@ text_length_between: %d ì—서 %d 글ìž
text_tracker_no_workflow: ì´ ì¶”ì íƒ€ìž…(tracker)ì— ì›Œí¬í”Œë¡œìš°ê°€ ì •ì˜ë˜ì§€ 않았습니다.
text_unallowed_characters: 허용ë˜ì§€ 않는 문ìžì—´
text_comma_separated: ë³µìˆ˜ì˜ ê°’ë“¤ì´ í—ˆìš©ë©ë‹ˆë‹¤.(êµ¬ë¶„ìž ,)
-text_issues_ref_in_commit_messages: 커밋메시지ì—서 ì´ìŠˆë¥¼ 참조하거나 해결하기
-text_issue_added: ì´ìŠˆ[%s]ê°€ ë³´ê³ ë˜ì—ˆìŠµë‹ˆë‹¤.
-text_issue_updated: ì´ìŠˆ[%s]ê°€ 수정ë˜ì—ˆìŠµë‹ˆë‹¤.
+text_issues_ref_in_commit_messages: 커밋메시지ì—서 ì¼ê°ì„ 참조하거나 해결하기
+text_issue_added: ì¼ê°[%s]ê°€ ë³´ê³ ë˜ì—ˆìŠµë‹ˆë‹¤.
+text_issue_updated: ì¼ê°[%s]ê°€ 수정ë˜ì—ˆìŠµë‹ˆë‹¤.
text_wiki_destroy_confirmation: ì´ ìœ„í‚¤ì™€ 모든 ë‚´ìš©ì„ ì§€ìš°ì‹œê² ìŠµë‹ˆê¹Œ?
-text_issue_category_destroy_question: ì¼ë¶€ ì´ìŠˆë“¤(%dê°œ)ì´ ì´ ì¹´í…Œê³ ë¦¬ì— í• ë‹¹ë˜ì–´ 있습니다. 어떻게 하시겠습니까?
+text_issue_category_destroy_question: ì¼ë¶€ ì¼ê°ë“¤(%dê°œ)ì´ ì´ ì¹´í…Œê³ ë¦¬ì— í• ë‹¹ë˜ì–´ 있습니다. 어떻게 하시겠습니까?
text_issue_category_destroy_assignments: 카테고리 할당 지우기
-text_issue_category_reassign_to: ì´ìŠˆë¥¼ ì´ ì¹´í…Œê³ ë¦¬ì— ë‹¤ì‹œ 할당하기
-text_user_mail_option: "ì„ íƒí•˜ì§€ ì•Šì€ í”„ë¡œì íЏì—서ë„, ëª¨ë‹ˆí„°ë§ ì¤‘ì´ê±°ë‚˜ ì†í•´ìžˆëŠ” 사항(ì´ìŠˆë¥¼ 발행했거나 í• ë‹¹ëœ ê²½ìš°)ì´ ìžˆìœ¼ë©´ 알림메ì¼ì„ 받게 ë©ë‹ˆë‹¤."
+text_issue_category_reassign_to: ì¼ê°ì„ ì´ ì¹´í…Œê³ ë¦¬ì— ë‹¤ì‹œ 할당하기
+text_user_mail_option: "ì„ íƒí•˜ì§€ ì•Šì€ í”„ë¡œì íЏì—서ë„, ëª¨ë‹ˆí„°ë§ ì¤‘ì´ê±°ë‚˜ ì†í•´ìžˆëŠ” 사항(ì¼ê°ì„ 발행했거나 í• ë‹¹ëœ ê²½ìš°)ì´ ìžˆìœ¼ë©´ 알림메ì¼ì„ 받게 ë©ë‹ˆë‹¤."
default_role_manager: 관리ìž
default_role_developper: 개발ìž
@@ -515,7 +515,7 @@ default_role_reporter: ë³´ê³ ìž
default_tracker_bug: 버그
default_tracker_feature: 새기능
default_tracker_support: ì§€ì›
-default_issue_status_new: 신규
+default_issue_status_new: 새로 만들기
default_issue_status_assigned: 확ì¸
default_issue_status_resolved: í•´ê²°
default_issue_status_feedback: 피드백
@@ -531,9 +531,9 @@ default_priority_immediate: 즉시
default_activity_design: 설계
default_activity_development: 개발
-enumeration_issue_priorities: ì´ìŠˆ 우선순위
+enumeration_issue_priorities: ì¼ê° 우선순위
enumeration_doc_categories: 문서 카테고리
-enumeration_activities: 진행활ë™(시간 ì¶”ì )
+enumeration_activities: 작업분류(시간추ì )
button_copy: 복사
mail_body_account_information_external: 레드마ì¸ì— 로그ì¸í•  때 "%s" ê³„ì •ì„ ì‚¬ìš©í•˜ì‹¤ 수 있습니다.
button_change_password: 비밀번호 변경
@@ -551,94 +551,159 @@ notice_account_pending: "ê³„ì •ì´ ë§Œë“¤ì–´ 졌습니다. 관리ìžì˜ 승ì¸ì
field_time_zone: 타임존
text_caracters_minimum: 최소한 %d ê¸€ìž ì´ìƒì´ì–´ì•¼ 합니다.
setting_bcc_recipients: 참조ìžë“¤ì„ bcc로 숨기기
-button_annotate: Annotate
-label_issues_by: Issues by %s
+button_annotate: 주ì„달기(annotate)
+label_issues_by: ì¼ê°ë¶„류 ë°©ì‹ %s
field_searchable: 검색가능
-label_display_per_page: 'Per page: %s'
-setting_per_page_options: Objects per page options
-label_age: Age
+label_display_per_page: '페ì´ì§€ë‹¹: %s'
+setting_per_page_options: 페ì´ì§€ë‹¹ 표시할 ê°ì²´ 수
+label_age: 마지막 수정ì¼
notice_default_data_loaded: 기본 ì„¤ì •ì„ ì„±ê³µì ìœ¼ë¡œ 로드하였습니다.
text_load_default_configuration: 기본 ì„¤ì •ì„ ë¡œë”©í•˜ê¸°
-text_no_configuration_data: "ì—­í• , ì´ìŠˆ 타입, ì´ìŠˆ ìƒíƒœë“¤ê³¼ 워í¬í”Œë¡œê°€ ì•„ì§ ì„¤ì •ë˜ì§€ 않았습니다.\n기본 ì„¤ì •ì„ ë¡œë”©í•˜ëŠ” ê²ƒì„ ê¶Œìž¥í•©ë‹ˆë‹¤. ë¡œë“œëœ í›„ì— ìˆ˜ì •í•  수 있습니다."
+text_no_configuration_data: "ì—­í• , ì¼ê° 타입, ì¼ê° ìƒíƒœë“¤ê³¼ 워í¬í”Œë¡œê°€ ì•„ì§ ì„¤ì •ë˜ì§€ 않았습니다.\n기본 ì„¤ì •ì„ ë¡œë”©í•˜ëŠ” ê²ƒì„ ê¶Œìž¥í•©ë‹ˆë‹¤. ë¡œë“œëœ í›„ì— ìˆ˜ì •í•  수 있습니다."
error_can_t_load_default_data: "기본 ì„¤ì •ì„ ë¡œë“œí•  수 없습니다.: %s"
-button_update: 변경사항기ë¡
+button_update: 수정
label_change_properties: ì†ì„± 변경
label_general: ì¼ë°˜
label_repository_plural: 저장소들
-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: 'ì„ íƒí•œ ì´ìŠˆë¥¼ ì •ë§ë¡œ 삭제하시겠습니까?'
-label_scm: SCM
+label_associated_revisions: ê´€ë ¨ëœ ê°œì •íŒë“¤
+setting_user_format: ì‚¬ìš©ìž í‘œì‹œ 형ì‹
+text_status_changed_by_changeset: ë³€ê²½ë¬¶ìŒ %sì—서 ì ìš©ë¨.
+label_more: ìžì„¸ížˆ
+text_issues_destroy_confirmation: 'ì„ íƒí•œ ì¼ê°ì„ ì •ë§ë¡œ 삭제하시겠습니까?'
+label_scm: 형ìƒê´€ë¦¬ì‹œìŠ¤í…œ(SCM)
text_select_project_modules: 'ì´ í”„ë¡œì íЏì—서 활성화시킬 ëª¨ë“ˆì„ ì„ íƒí•˜ì„¸ìš”:'
-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
+label_issue_added: ì¼ê°ì´ 추가ë¨
+label_issue_updated: ì¼ê°ì´ ê³ ì³ì§
+label_document_added: 문서가 추가ë¨
+label_message_posted: 메시지가 추가ë¨
+label_file_added: 파ì¼ì´ 추가ë¨
+label_news_added: 뉴스가 추가ë¨
project_module_boards: 게시íŒ
-project_module_issue_tracking: ì´ìŠˆê´€ë¦¬
+project_module_issue_tracking: ì¼ê°ê´€ë¦¬
project_module_wiki: 위키
project_module_files: 관련파ì¼
project_module_documents: 문서
project_module_repository: 저장소
project_module_news: 뉴스
-project_module_time_tracking: Time tracking
-text_file_repository_writable: File repository writable
+project_module_time_tracking: 시간추ì 
+text_file_repository_writable: íŒŒì¼ ì €ìž¥ì†Œ 쓰기 가능
text_default_administrator_account_changed: 기본 ê´€ë¦¬ìž ê³„ì •ì´ ë³€ê²½ë˜ì—ˆìŠµë‹ˆë‹¤.
-text_rmagick_available: RMagick available (optional)
+text_rmagick_available: RMagick 사용가능(옵션)
button_configure: 설정
label_plugins: 플러그ì¸
label_ldap_authentication: LDAP ì¸ì¦
label_downloads_abbr: D/L
-label_add_another_file: Add another file
-label_this_month: this month
-text_destroy_time_entries_question: %.02f hours were reported on the issues you are about to delete. What do you want to do ?
-label_last_n_days: last %d days
-label_all_time: all time
-error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
-label_this_year: this year
-text_assign_time_entries_to_project: Assign reported hours to the project
-label_date_range: Date range
-label_last_week: last week
-label_yesterday: yesterday
-label_optional_description: Optional description
-label_last_month: last month
-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_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
-setting_sequential_project_identifiers: Generate sequential project identifiers
-notice_unable_delete_version: Unable to delete version
-label_renamed: renamed
-label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+label_add_another_file: 다른 íŒŒì¼ ì¶”ê°€
+label_this_month: ì´ë²ˆ 달
+text_destroy_time_entries_question: 삭제하려는 ì¼ê°ì— %.02f ì‹œê°„ì´ ë³´ê³ ë˜ì–´ 있습니다. 어떻게 하시겠습니까?
+label_last_n_days: 지난 %d ì¼
+label_all_time: 모든 시간
+error_issue_not_found_in_project: 'ì¼ê°ì´ 없거나 ì´ í”„ë¡œì íŠ¸ì˜ ê²ƒì´ ì•„ë‹™ë‹ˆë‹¤.'
+label_this_year: 올해
+text_assign_time_entries_to_project: ë³´ê³ ëœ ì‹œê°„ì„ í”„ë¡œì íŠ¸ì— í• ë‹¹í•˜ê¸°
+label_date_range: 날짜 범위
+label_last_week: 지난 주
+label_yesterday: 어제
+label_optional_description: 부가ì ì¸ 설명
+label_last_month: 지난 달
+text_destroy_time_entries: ë³´ê³ ëœ ì‹œê°„ì„ ì‚­ì œí•˜ê¸°
+text_reassign_time_entries: 'ì´ ì•Œë¦¼ì— ë³´ê³ ëœ ì‹œê°„ì„ ìž¬í• ë‹¹í•˜ê¸°:'
+setting_activity_days_default: 프로ì íЏ ìž‘ì—…ë‚´ì—­ì— ë³´ì—¬ì¤„ 날수
+label_chronological_order: 시간 순으로 정렬
+field_comments_sorting: 히스토리 정렬 설정
+label_reverse_chronological_order: 시간 역순으로 정렬
+label_preferences: 설정
+setting_display_subprojects_issues: 하위 프로ì íŠ¸ì˜ ì¼ê°ì„ 최ìƒìœ„ 프로ì íЏì—서 표시
+label_overall_activity: 전체 작업내역
+setting_default_projects_public: 새 프로ì íŠ¸ë¥¼ 공개로 설정
+error_scm_annotate: "í•­ëª©ì´ ì—†ê±°ë‚˜ 주ì„ì„ ë‹¬ 수 없습니다."
+label_planning: 프로ì íŠ¸ê³„íš(Planning)
+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: 수신 ë©”ì¼ì— WS 를 허용
+setting_mail_handler_api_key: API 키
+text_email_delivery_not_configured: "ì´ë©”ì¼ ì „ë‹¬ì´ ì„¤ì •ë˜ì§€ 않았습니다. 그래서 ì•Œë¦¼ì´ ë¹„í™œì„±í™”ë˜ì—ˆìŠµë‹ˆë‹¤.\n SMTP서버를 config/email.ymlì—서 설정하고 어플리케ì´ì…˜ì„ 다시 시작하십시오. 그러면 ë™ìž‘합니다."
+field_parent_title: ìƒìœ„ 제목
+label_issue_watchers: ì¼ê°ì§€í‚´ì´ 설정
+setting_commit_logs_encoding: 저장소 커밋 메시지 ì¸ì½”딩
+button_quote: 댓글달기
+setting_sequential_project_identifiers: 프로ì íЏ ì‹ë³„ìžë¥¼ 순차ì ìœ¼ë¡œ ìƒì„±
+notice_unable_delete_version: 삭제 할 수 없는 버전 입니다.
+label_renamed: ì´ë¦„바뀜
+label_copied: 복사ë¨
+setting_plain_text_mail: 테스트만(HTMLì—†ìŒ)
+permission_view_files: 파ì¼ë³´ê¸°
+permission_edit_issues: ì¼ê° 편집
+permission_edit_own_time_entries: 내 시간로그 편집
+permission_manage_public_queries: 공용 ì§ˆì˜ ê´€ë¦¬
+permission_add_issues: ì¼ê° 추가
+permission_log_time: 소요시간 기ë¡
+permission_view_changesets: 변경묶ìŒë³´ê¸°
+permission_view_time_entries: 소요시간 보기
+permission_manage_versions: 버전 관리
+permission_manage_wiki: 위키 관리
+permission_manage_categories: ì¼ê° 카테고리 관리
+permission_protect_wiki_pages: 프로ì íЏ 위키 페ì´ì§€
+permission_comment_news: ë‰´ìŠ¤ì— ì½”ë©˜íŠ¸ë‹¬ê¸°
+permission_delete_messages: 메시지 삭제
+permission_select_project_modules: 프로ì íЏ 모듈 ì„ íƒ
+permission_manage_documents: 문서 관리
+permission_edit_wiki_pages: 위키 페ì´ì§€ 편집
+permission_add_issue_watchers: ì¼ê°ì§€í‚´ì´ 추가
+permission_view_gantt: Gantt차트 보기
+permission_move_issues: ì¼ê° ì´ë™
+permission_manage_issue_relations: ì¼ê° 관계 관리
+permission_delete_wiki_pages: 위치 페ì´ì§€ ì‚­ì œ
+permission_manage_boards: ê²Œì‹œíŒ ê´€ë¦¬
+permission_delete_wiki_pages_attachments: ì²¨ë¶€íŒŒì¼ ì‚­ì œ
+permission_view_wiki_edits: 위키 ê¸°ë¡ ë³´ê¸°
+permission_add_messages: 메시지 추가
+permission_view_messages: 메시지 보기
+permission_manage_files: 파ì¼ê´€ë¦¬
+permission_edit_issue_notes: ë§ê¸€ 편집
+permission_manage_news: 뉴스 관리
+permission_view_calendar: 달력 보기
+permission_manage_members: 멤버 관리
+permission_edit_messages: 메시지 편집
+permission_delete_issues: ì¼ê° ì‚­ì œ
+permission_view_issue_watchers: ì¼ê°ì§€í‚´ì´ 보기
+permission_manage_repository: 저장소 관리
+permission_commit_access: 변경로그 보기
+permission_browse_repository: 저장소 둘러보기
+permission_view_documents: 문서 보기
+permission_edit_project: 프로ì íЏ 편집
+permission_add_issue_notes: ë§ê¸€ 추가
+permission_save_queries: 쿼리 저장
+permission_view_wiki_pages: 위키 보기
+permission_rename_wiki_pages: 위키 페ì´ì§€ ì´ë¦„변경
+permission_edit_time_entries: ì‹œê°„ê¸°ë¡ íŽ¸ì§‘
+permission_edit_own_issue_notes: ë‚´ ë§ê¸€ 편집
+setting_gravatar_enabled: ê·¸ë¼ë°”타 ì‚¬ìš©ìž ì•„ì´ì½˜ 쓰기
+label_example: 예
+text_repository_usernames_mapping: "저장소 로그ì—서 ë°œê²¬ëœ ê° ì‚¬ìš©ìžì— ë ˆë“œë§ˆì¸ ì‚¬ìš©ìžë¥¼ ì—…ë°ì´íŠ¸í• ë•Œ ì„ íƒí•©ë‹ˆë‹¤.\n레드마ì¸ê³¼ ì €ìž¥ì†Œì˜ ì´ë¦„ì´ë‚˜ ì´ë©”ì¼ì´ ê°™ì€ ì‚¬ìš©ìžê°€ ìžë™ìœ¼ë¡œ ì—°ê²°ë©ë‹ˆë‹¤."
+permission_edit_own_messages: ìžê¸° 메시지 편집
+permission_delete_own_messages: ìžê¸° 메시지 ì‚­ì œ
+label_user_activity: "%sì˜ ìž‘ì—…ë‚´ì—­"
+label_updated_time_by: %sê°€ %s ì „ì— ë³€ê²½
+text_diff_truncated: '... ì´ ì°¨ì´ì ì€ 표시할 수 있는 최대 줄수를 초과해서 ì´ ì°¨ì´ì ì€ 잘렸습니다.'
+setting_diff_max_lines_displayed: ì°¨ì´ì ë³´ê¸°ì— 표시할 최대 줄수
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/lt.yml b/lang/lt.yml
index ffb831575..0a732a59a 100644
--- a/lang/lt.yml
+++ b/lang/lt.yml
@@ -1,85 +1,89 @@
_gloc_rule_default: '|n| n==1 ? "" : "_plural" '
-
-actionview_datehelper_select_day_prefix:
+
+actionview_datehelper_select_day_prefix:
actionview_datehelper_select_month_names: sausis,vasaris,kovas,balandis,gegužė,birželis,liepa,rugpjūtis,rugsėjis,spalis,lapkritis,gruodis
actionview_datehelper_select_month_names_abbr: Sau,Vas,Kov,Bal,Geg,Brž,Lie,Rgp,Rgs,Spl,Lap,Grd
actionview_datehelper_select_month_prefix:
-actionview_datehelper_select_year_prefix:
+actionview_datehelper_select_year_prefix:
actionview_datehelper_time_in_words_day: 1 diena
actionview_datehelper_time_in_words_day_plural: %d dienų
-actionview_datehelper_time_in_words_hour_about: apytiksliai valanda
-actionview_datehelper_time_in_words_hour_about_plural: apie %d valandas
-actionview_datehelper_time_in_words_hour_about_single: apytiksliai valanda
-actionview_datehelper_time_in_words_minute: 1 minutÄ—
-actionview_datehelper_time_in_words_minute_half: pusÄ— minutÄ—s
-actionview_datehelper_time_in_words_minute_less_than: mažiau kaip minutė
-actionview_datehelper_time_in_words_minute_plural: %d minutÄ—s
-actionview_datehelper_time_in_words_minute_single: 1 minutÄ—
-actionview_datehelper_time_in_words_second_less_than: mažiau kaip sekundė
-actionview_datehelper_time_in_words_second_less_than_plural: mažiau, negu %d sekundės
-actionview_instancetag_blank_option: prašom išrinkti
-
-activerecord_error_inclusion: nėra įtrauktas į sąrašą
-activerecord_error_exclusion: yra rezervuota(as)
+actionview_datehelper_time_in_words_hour_about: apytiksliai valanda
+actionview_datehelper_time_in_words_hour_about_plural: apie %d valandas
+actionview_datehelper_time_in_words_hour_about_single: apytiksliai valanda
+actionview_datehelper_time_in_words_minute: 1 minutÄ—
+actionview_datehelper_time_in_words_minute_half: pusÄ— minutÄ—s
+actionview_datehelper_time_in_words_minute_less_than: mažiau kaip minutė
+actionview_datehelper_time_in_words_minute_plural: %d minutÄ—s
+actionview_datehelper_time_in_words_minute_single: 1 minutÄ—
+actionview_datehelper_time_in_words_second_less_than: mažiau kaip sekundė
+actionview_datehelper_time_in_words_second_less_than_plural: mažiau, negu %d sekundės
+actionview_instancetag_blank_option: prašom išrinkti
+
+activerecord_error_inclusion: nėra įtrauktas į sąrašą
+activerecord_error_exclusion: yra rezervuota(as)
activerecord_error_invalid: yra negaliojanti(is)
-activerecord_error_confirmation: neatitinka patvirtinimo
-activerecord_error_accepted: turi būti priimtas
-activerecord_error_empty: negali bÅ«ti tuÅ¡Äiu
-activerecord_error_blank: negali bÅ«ti tuÅ¡Äiu
-activerecord_error_too_long: yra per ilgas
-activerecord_error_too_short: yra per trumpas
-activerecord_error_wrong_length: neteisingas ilgis
-activerecord_error_taken: buvo jau paimtas
-activerecord_error_not_a_number: nÄ—ra skaiÄius
-activerecord_error_not_a_date: data nÄ—ra galiojanti
-activerecord_error_greater_than_start_date: turi būti didesnė negu pradžios data
-activerecord_error_not_same_project: nepriklauso tam paÄiam projektui
-activerecord_error_circular_dependency: Šis ryšys sukurtų ciklinę priklausomybę
-
+activerecord_error_confirmation: neatitinka patvirtinimo
+activerecord_error_accepted: turi būti priimtas
+activerecord_error_empty: negali bÅ«ti tuÅ¡Äiu
+activerecord_error_blank: negali bÅ«ti tuÅ¡Äiu
+activerecord_error_too_long: yra per ilgas
+activerecord_error_too_short: yra per trumpas
+activerecord_error_wrong_length: neteisingas ilgis
+activerecord_error_taken: buvo jau paimtas
+activerecord_error_not_a_number: nÄ—ra skaiÄius
+activerecord_error_not_a_date: data nÄ—ra galiojanti
+activerecord_error_greater_than_start_date: turi būti didesnė negu pradžios data
+activerecord_error_not_same_project: nepriklauso tam paÄiam projektui
+activerecord_error_circular_dependency: Šis ryšys sukurtų ciklinę priklausomybę
+
general_fmt_age: %d m.
general_fmt_age_plural: %d metų(ai)
general_fmt_date: %%Y-%%m-%%d
-general_fmt_datetime: %%Y-%%m-%%d %%I:%%M %%p
-general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
-general_fmt_time: %%I:%%M %%p
+general_fmt_datetime: %%Y-%%m-%%d %%H:%%M
+general_fmt_datetime_short: %%b %%d, %%H:%%M
+general_fmt_time: %%H:%%M
general_text_No: 'Ne'
general_text_Yes: 'Taip'
general_text_no: 'ne'
general_text_yes: 'taip'
general_lang_name: 'Lithuanian (lietuvių)'
-general_csv_separator: ','
+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
general_first_day_of_week: '1'
-
-notice_account_updated: Paskyra buvo sÄ—kmingai atnaujinta.
+
+notice_account_updated: Paskyra buvo sÄ—kmingai atnaujinta.
notice_account_invalid_creditentials: Negaliojantis vartotojo vardas ar slaptažodis
-notice_account_password_updated: Slaptažodis buvo sėkmingai atnaujintas.
-notice_account_wrong_password: Neteisingas slaptažodis
-notice_account_register_done: Paskyra buvo sėkmingai sukurta. Kad aktyvintumėte savo paskyrą, paspauskite sąsają, kuri jums buvo siųsta elektroniniu paštu.
-notice_account_unknown_email: Nežinomas vartotojas.
-notice_can_t_change_password: Šis pranešimas naudoja išorinį autentiškumo nustatymo šaltinį. Neįmanoma pakeisti slaptažodį.
-notice_account_lost_email_sent: Į Jūsų pašą išsiūstas laiškas su naujo slaptažodžio pasirinkimo instrukcija.
+notice_account_password_updated: Slaptažodis buvo sėkmingai atnaujintas.
+notice_account_wrong_password: Neteisingas slaptažodis
+notice_account_register_done: Paskyra buvo sėkmingai sukurta. Kad aktyvintumėte savo paskyrą, paspauskite sąsają, kuri jums buvo siųsta elektroniniu paštu.
+notice_account_unknown_email: Nežinomas vartotojas.
+notice_can_t_change_password: Šis pranešimas naudoja išorinį autentiškumo nustatymo šaltinį. Neįmanoma pakeisti slaptažodį.
+notice_account_lost_email_sent: Į Jūsų pašą išsiūstas laiškas su naujo slaptažodžio pasirinkimo instrukcija.
notice_account_activated: Jūsų paskyra aktyvuota. Galite prisijungti.
-notice_successful_create: Sėkmingas sukūrimas.
-notice_successful_update: SÄ—kmingas atnaujinimas.
-notice_successful_delete: SÄ—kmingas panaikinimas.
-notice_successful_connection: SÄ—kmingas susijungimas.
-notice_file_not_found: Puslapis, į kurį ketinate įeiti, neegzistuoja arba pašalintas.
-notice_locking_conflict: Duomenys atnaujinti kito vartotojo.
-notice_scm_error: Duomenys ir/ar pakeitimai saugykloje(repozitorojoje) neegzistuoja.
-notice_not_authorized: Jūs neturite teisių gauti prieigą prie šio puslapio.
+notice_successful_create: Sėkmingas sukūrimas.
+notice_successful_update: SÄ—kmingas atnaujinimas.
+notice_successful_delete: SÄ—kmingas panaikinimas.
+notice_successful_connection: SÄ—kmingas susijungimas.
+notice_file_not_found: Puslapis, į kurį ketinate įeiti, neegzistuoja arba pašalintas.
+notice_locking_conflict: Duomenys atnaujinti kito vartotojo.
+notice_not_authorized: Jūs neturite teisių gauti prieigą prie šio puslapio.
notice_email_sent: Laiškas išsiųstas %s
notice_email_error: Laiško siųntimo metu įvyko klaida (%s)
notice_feeds_access_key_reseted: Jūsų RSS raktas buvo atnaujintas.
notice_failed_to_save_issues: "Nepavyko išsaugoti %d problemos(ų) iš %d pasirinkto: %s."
notice_no_issue_selected: "Nepasirinkta nė viena problema! Prašom pažymėti problemą, kurią norite redaguoti."
notice_account_pending: "Jūsų paskyra buvo sukūrta ir dabar laukiama administratoriaus patvirtinimo."
+notice_default_data_loaded: Numatytoji konfiguracija sėkmingai užkrauta.
+notice_unable_delete_version: Neimanoma panaikinti versijÄ…
+error_can_t_load_default_data: "Numatytoji konfiguracija negali būti užkrauta: %s"
error_scm_not_found: "Duomenys ir/ar pakeitimai saugykloje(repozitorojoje) neegzistuoja."
error_scm_command_failed: "Įvyko klaida jungiantis prie saugyklos: %s"
+error_scm_annotate: "Įrašas neegzituoja arba negalima jo atvaizduoti."
+error_issue_not_found_in_project: 'Darbas nerastas arba nesurištas su šiuo projektu'
mail_subject_lost_password: Jūsų %s slaptažodis
mail_body_lost_password: 'Norėdami pakeisti slaptažodį, spauskite nuorodą:'
@@ -89,10 +93,12 @@ mail_body_account_information_external: Jūs galite naudoti Jūsų "%s" paskyrą
mail_body_account_information: Informacija apie Jūsų paskyrą
mail_subject_account_activation_request: %s paskyros aktyvavimo prašymas
mail_body_account_activation_request: 'Užsiregistravo naujas vartotojas (%s). Jo paskyra laukia jūsų patvirtinimo:'
-
+mail_subject_reminder: "%d darbas(ai) po kelių dienų"
+mail_body_reminder: "%d darbas(ai), kurie yra jums priskirti, baigiasi po %d dienų(os):"
+
gui_validation_error: 1 klaida
gui_validation_error_plural: %d klaidų(os)
-
+
field_name: Pavadinimas
field_description: Aprašas
field_summary: Santrauka
@@ -111,536 +117,595 @@ field_is_for_all: Visiems projektams
field_possible_values: Galimos reikšmės
field_regexp: Pastovi išraiška
field_min_length: Minimalus ilgis
-field_max_length: Maksimalus ilgis
-field_value: VertÄ—
-field_category: Kategorija
-field_title: Pavadinimas
-field_project: Projektas
-field_issue: Darbas
-field_status: Būsena
-field_notes: Pastabos
-field_is_closed: Darbas uždarytas
-field_is_default: Numatytoji vertÄ—
-field_tracker: PÄ—dsekys
-field_subject: Tema
-field_due_date: Užbaigimo data
-field_assigned_to: Paskirtas
-field_priority: Prioritetas
-field_fixed_version: Target version
-field_user: Vartotojas
-field_role: Vaidmuo
-field_homepage: Pagrindinis puslapis
-field_is_public: Viešas
+field_max_length: Maksimalus ilgis
+field_value: VertÄ—
+field_category: Kategorija
+field_title: Pavadinimas
+field_project: Projektas
+field_issue: Darbas
+field_status: Būsena
+field_notes: Pastabos
+field_is_closed: Darbas uždarytas
+field_is_default: Numatytoji vertÄ—
+field_tracker: PÄ—dsekys
+field_subject: Tema
+field_due_date: Užbaigimo data
+field_assigned_to: Paskirtas
+field_priority: Prioritetas
+field_fixed_version: TikslinÄ— versija
+field_user: Vartotojas
+field_role: Vaidmuo
+field_homepage: Pagrindinis puslapis
+field_is_public: Viešas
field_parent: Priklauso projektui
-field_is_in_chlog: Darbai rodomi pokyÄių žurnale
-field_is_in_roadmap: Darbai rodomi veiklos grafike
-field_login: Registracijos vardas
-field_mail_notification: Elektroninio pašto pranešimai
-field_admin: Administratorius
-field_last_login_on: Paskutinis ryšys
-field_language: Kalba
-field_effective_date: Data
+field_is_in_chlog: Darbai rodomi pokyÄių žurnale
+field_is_in_roadmap: Darbai rodomi veiklos grafike
+field_login: Registracijos vardas
+field_mail_notification: Elektroninio pašto pranešimai
+field_admin: Administratorius
+field_last_login_on: Paskutinis ryšys
+field_language: Kalba
+field_effective_date: Data
field_password: Slaptažodis
field_new_password: Naujas slaptažodis
-field_password_confirmation: Patvirtinimas
+field_password_confirmation: Patvirtinimas
field_version: Versija
-field_type: Tipas
-field_host: Pagrindinis kompiuteris
-field_port: Jungtis
-field_account: Paskyra
-field_base_dn: Bazinis skiriamasis vardas
-field_attr_login: Registracijos vardo požymis
-field_attr_firstname: Vardo priskiria
-field_attr_lastname: PavardÄ—s priskiria
-field_attr_mail: Elektroninio pašto požymis
-field_onthefly: Vartotojų sukūrimas paskubomis
-field_start_date: PradÄ—ti
-field_done_ratio: %% Atlikta
-field_auth_source: Autentiškumo nustatymo būdas
-field_hide_mail: Paslėpkite mano elektroninio pašto adresą
+field_type: Tipas
+field_host: Pagrindinis kompiuteris
+field_port: Portas
+field_account: Paskyra
+field_base_dn: Bazinis skiriamasis vardas
+field_attr_login: Registracijos vardo požymis
+field_attr_firstname: Vardo priskiria
+field_attr_lastname: PavardÄ—s priskiria
+field_attr_mail: Elektroninio pašto požymis
+field_onthefly: Automatinis vartotojų registravimas
+field_start_date: PradÄ—ti
+field_done_ratio: %% Atlikta
+field_auth_source: Autentiškumo nustatymo būdas
+field_hide_mail: Paslėpkite mano elektroninio pašto adresą
field_comments: Komentaras
-field_url: URL
+field_url: URL
field_start_page: Pradžios puslapis
-field_subproject: Subprojektas
-field_hours: Valandos
-field_activity: Veikla
-field_spent_on: Data
-field_identifier: Identifikuotojas
-field_is_filter: Panaudotas kaip filtras
+field_subproject: Subprojektas
+field_hours: Valandos
+field_activity: Veikla
+field_spent_on: Data
+field_identifier: Identifikuotojas
+field_is_filter: Panaudotas kaip filtras
field_issue_to_id: Susijęs darbas
-field_delay: Užlaikymas
-field_assignable: Darbai gali būti paskirti šiam vaidmeniui
-field_redirect_existing_links: Peradresuokite egzistuojanÄias sÄ…sajas
-field_estimated_hours: Numatyta trukmÄ—
-field_column_names: Skiltys
-field_time_zone: Laiko juosta
-field_searchable: Randamas
-field_default_value: Numatytoji vertÄ—
-setting_app_title: Programos pavadinimas
-setting_app_subtitle: Programos paantraštė
-setting_welcome_text: Pasveikinimas
-setting_default_language: Numatytoji kalba
-setting_login_required: Reikalingas autentiškumo nustatymas
-setting_self_registration: Saviregistracija
-setting_attachment_max_size: Priedo maks. dydis
-setting_issues_export_limit pagal dydį: Darbų eksportavimo riba
-setting_mail_from: Emisijos elektroninio pašto adresas
+field_delay: Užlaikymas
+field_assignable: Darbai gali būti paskirti šiam vaidmeniui
+field_redirect_existing_links: Peradresuokite egzistuojanÄias sÄ…sajas
+field_estimated_hours: Numatyta trukmÄ—
+field_column_names: Skiltys
+field_time_zone: Laiko juosta
+field_searchable: Randamas
+field_default_value: Numatytoji vertÄ—
+field_comments_sorting: rodyti komentarus
+field_parent_title: Aukštesnio lygio puslapis
+
+setting_app_title: Programos pavadinimas
+setting_app_subtitle: Programos paantraštė
+setting_welcome_text: Pasveikinimas
+setting_default_language: Numatytoji kalba
+setting_login_required: Reikalingas autentiškumo nustatymas
+setting_self_registration: Saviregistracija
+setting_attachment_max_size: Priedo maks. dydis
+setting_issues_export_limit: Darbų eksportavimo riba
+setting_mail_from: Emisijos elektroninio pašto adresas
setting_bcc_recipients: Akli tikslios kopijos gavÄ—jai (bcc)
+setting_plain_text_mail: tik grinas tekstas (be HTML)
setting_host_name: Pagrindinio kompiuterio vardas
-setting_text_formatting: Teksto apipavidalinimas
-setting_wiki_compression: Wiki istorijos suspaudimas
-setting_feeds_limit: Perdavimo turinio riba
+setting_text_formatting: Teksto apipavidalinimas
+setting_wiki_compression: Wiki istorijos suspaudimas
+setting_feeds_limit: Perdavimo turinio riba
+setting_default_projects_public: Naujas projektas viešas pagal nutylėjimą
setting_autofetch_changesets: Automatinis pakeitimų siuntimas
-setting_sys_api_enabled: Įgalinkite WS sandėlio vadybai
+setting_sys_api_enabled: Įgalinkite WS sandėlio vadybai
setting_commit_ref_keywords: Nurodymo reikšminiai žodžiai
setting_commit_fix_keywords: Fiksavimo reikšminiai žodžiai
setting_autologin: Autoregistracija
-setting_date_format: Datos formatas
-setting_time_format: Laiko formatas
+setting_date_format: Datos formatas
+setting_time_format: Laiko formatas
setting_cross_project_issue_relations: Leisti tarprojektinius darbų ryšius
setting_issue_list_default_columns: Numatytosios skiltys darbų sąraše
setting_repositories_encodings: Saugyklos koduotÄ—
+setting_commit_logs_encoding: Commit pranėšimų koduotė
setting_emails_footer: elektroninio pašto puslapinė poraštė
setting_protocol: Protokolas
-
+setting_per_page_options: Įrašų puslapyje nustatimas
+setting_user_format: Vartotojo atvaizdavimo formatas
+setting_activity_days_default: Atvaizduojamos dienos projekto veikloje
+setting_display_subprojects_issues: Pagal nutylėjimą rodyti subprojektų darbus pagrindiniame projekte
+setting_enabled_scm: Įgalintas SCM
+setting_mail_handler_api_enabled: Įgalinti WS įeinantiems laiškams
+setting_mail_handler_api_key: API raktas
+setting_sequential_project_identifiers: Generuoti nuoseklus projekto identifikatorius
+setting_gravatar_enabled: Naudoti Gravatar vartotojo ikonkÄ—s
+setting_diff_max_lines_displayed: Maksimalus rodomas eiluÄiu skaiÄius diff\'e
+
+permission_edit_project: Edit project
+permission_select_project_modules: Select project modules
+permission_manage_members: Manage members
+permission_manage_versions: Manage versions
+permission_manage_categories: Manage issue categories
+permission_add_issues: Add issues
+permission_edit_issues: Edit issues
+permission_manage_issue_relations: Manage issue relations
+permission_add_issue_notes: Add notes
+permission_edit_issue_notes: Edit notes
+permission_edit_own_issue_notes: Edit own notes
+permission_move_issues: Move issues
+permission_delete_issues: Delete issues
+permission_manage_public_queries: Manage public queries
+permission_save_queries: Save queries
+permission_view_gantt: View gantt chart
+permission_view_calendar: View calender
+permission_view_issue_watchers: View watchers list
+permission_add_issue_watchers: Add watchers
+permission_log_time: Log spent time
+permission_view_time_entries: View spent time
+permission_edit_time_entries: Edit time logs
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_news: Manage news
+permission_comment_news: Comment news
+permission_manage_documents: Manage documents
+permission_view_documents: View documents
+permission_manage_files: Manage files
+permission_view_files: View files
+permission_manage_wiki: Manage wiki
+permission_rename_wiki_pages: Rename wiki pages
+permission_delete_wiki_pages: Delete wiki pages
+permission_view_wiki_pages: View wiki
+permission_view_wiki_edits: View wiki history
+permission_edit_wiki_pages: Edit wiki pages
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_protect_wiki_pages: Protect wiki pages
+permission_manage_repository: Manage repository
+permission_browse_repository: Browse repository
+permission_view_changesets: View changesets
+permission_commit_access: Commit access
+permission_manage_boards: Manage boards
+permission_view_messages: View messages
+permission_add_messages: Post messages
+permission_edit_messages: Edit messages
+permission_edit_own_messages: Edit own messages
+permission_delete_messages: Delete messages
+permission_delete_own_messages: Delete own messages
+
+project_module_issue_tracking: Darbu pÄ—dsekys
+project_module_time_tracking: Laiko pÄ—dsekys
+project_module_news: Žinios
+project_module_documents: Dokumentai
+project_module_files: Rinkmenos
+project_module_wiki: Wiki
+project_module_repository: Saugykla
+project_module_boards: Forumai
+
label_user: Vartotojas
-label_user_plural: Vartotojai
-label_user_new: Naujas vartotojas
-label_project: Projektas
-label_project_new: Naujas projektas
-label_project_plural: Projektai
-label_project_all: Visi Projektai
-label_project_latest: Paskutiniai projektai
-label_issue: Darbas
-label_issue_new: Naujas darbas
-label_issue_plural: Darbai
+label_user_plural: Vartotojai
+label_user_new: Naujas vartotojas
+label_project: Projektas
+label_project_new: Naujas projektas
+label_project_plural: Projektai
+label_project_all: Visi Projektai
+label_project_latest: Paskutiniai projektai
+label_issue: Darbas
+label_issue_new: Naujas darbas
+label_issue_plural: Darbai
label_issue_view_all: Peržiūrėti visus darbus
-label_issues_by: Darbai pagal %s
+label_issues_by: Darbai pagal %s
+label_issue_added: Darbas pridÄ—tas
+label_issue_updated: Darbas atnaujintas
label_document: Dokumentas
label_document_new: Naujas dokumentas
-label_document_plural: Dokumentai
-label_role: Vaidmuo
-label_role_plural: Vaidmenys
-label_role_new: Naujas vaidmuo
-label_role_and_permissions: Vaidmenys ir leidimai
-label_member: Narys
-label_member_new: Naujas narys
-label_member_plural: Nariai
-label_tracker: PÄ—dsekys
-label_tracker_plural: PÄ—dsekiai
-label_tracker_new: Naujas pÄ—dsekys
-label_workflow: Darbų eiga
-label_issue_status: Darbo padÄ—tis
-label_issue_status_plural: Darbų padėtys
-label_issue_status_new: Nauja padÄ—tis
-label_issue_category: Darbo kategorija
-label_issue_category_plural: Darbo kategorijos
-label_issue_category_new: Nauja kategorija
-label_custom_field: Kliento laukas
-label_custom_field_plural: Kliento laukai
-label_custom_field_new: Naujas kliento laukas
-label_enumerations: Išvardinimai
-label_enumeration_new: Nauja vertÄ—
-label_information: Informacija
-label_information_plural: Informacija
-label_please_login: Prašom prisijungti
-label_register: Užsiregistruoti
+label_document_plural: Dokumentai
+label_document_added: Dokumentas pridÄ—tas
+label_role: Vaidmuo
+label_role_plural: Vaidmenys
+label_role_new: Naujas vaidmuo
+label_role_and_permissions: Vaidmenys ir leidimai
+label_member: Narys
+label_member_new: Naujas narys
+label_member_plural: Nariai
+label_tracker: PÄ—dsekys
+label_tracker_plural: PÄ—dsekiai
+label_tracker_new: Naujas pÄ—dsekys
+label_workflow: Darbų eiga
+label_issue_status: Darbo padÄ—tis
+label_issue_status_plural: Darbų padėtys
+label_issue_status_new: Nauja padÄ—tis
+label_issue_category: Darbo kategorija
+label_issue_category_plural: Darbo kategorijos
+label_issue_category_new: Nauja kategorija
+label_custom_field: Kliento laukas
+label_custom_field_plural: Kliento laukai
+label_custom_field_new: Naujas kliento laukas
+label_enumerations: Išvardinimai
+label_enumeration_new: Nauja vertÄ—
+label_information: Informacija
+label_information_plural: Informacija
+label_please_login: Prašom prisijungti
+label_register: Užsiregistruoti
label_password_lost: Prarastas slaptažodis
-label_home: Pagrindinis
+label_home: Pagrindinis
label_my_page: Mano puslapis
-label_my_account: Mano paskyra
-label_my_projects: Mano projektai
-label_administration: Administravimas
-label_login: Prisijungti
-label_logout: Atsijungti
-label_help: Pagalba
-label_reported_issues: Pranešti darbai
-label_assigned_to_me_issues: Darbai, priskirti man
-label_last_login: Paskutinis ryšys
+label_my_account: Mano paskyra
+label_my_projects: Mano projektai
+label_administration: Administravimas
+label_login: Prisijungti
+label_logout: Atsijungti
+label_help: Pagalba
+label_reported_issues: Pranešti darbai
+label_assigned_to_me_issues: Darbai, priskirti man
+label_last_login: Paskutinis ryšys
label_last_updates: Paskutinis atnaujinimas
-label_last_updates_plural: %d paskutinis atnaujinimas
-label_registered_on: Užregistruota
-label_activity: Veikla
-label_new: Naujas
-label_logged_as: Prisijungęs kaip
-label_environment: Aplinka
-label_authentication: Autentiškumo nustatymas
-label_auth_source: Autentiškumo nustatymo būdas
-label_auth_source_new: Naujas autentiškumo nustatymo būdas
-label_auth_source_plural: Autentiškumo nustatymo būdai
-label_subproject_plural: Subprojektai
-label_min_max_length: Min - Maks ilgis
-label_list: Sąrašas
-label_date: Data
-label_integer: Sveikasis skaiÄius
-label_float: Float
-label_boolean: Boolean
-label_string: Tekstas
-label_text: Ilgas tekstas
-label_attribute: Požymis
-label_attribute_plural: Požymiai
-label_download: %d Persiuntimas
-label_download_plural: %d Persiuntimai
-label_no_data: NÄ—ra kÄ… atvaizduoti
-label_change_status: Pakeitimo padÄ—tis
-label_history: Istorija
+label_last_updates_plural: %d paskutinis atnaujinimas
+label_registered_on: Užregistruota
+label_activity: Veikla
+label_overall_activity: Visa veikla
+label_user_activity: "%so veiksmai"
+label_new: Naujas
+label_logged_as: Prisijungęs kaip
+label_environment: Aplinka
+label_authentication: Autentiškumo nustatymas
+label_auth_source: Autentiškumo nustatymo būdas
+label_auth_source_new: Naujas autentiškumo nustatymo būdas
+label_auth_source_plural: Autentiškumo nustatymo būdai
+label_subproject_plural: Subprojektai
+label_and_its_subprojects: %s projektas ir jo subprojektai
+label_min_max_length: Min - Maks ilgis
+label_list: Sąrašas
+label_date: Data
+label_integer: Sveikasis skaiÄius
+label_float: Float
+label_boolean: Boolean
+label_string: Tekstas
+label_text: Ilgas tekstas
+label_attribute: Požymis
+label_attribute_plural: Požymiai
+label_download: %d Persiuntimas
+label_download_plural: %d Persiuntimai
+label_no_data: NÄ—ra kÄ… atvaizduoti
+label_change_status: Pakeitimo padÄ—tis
+label_history: Istorija
label_attachment: Rinkmena
label_attachment_new: Nauja rinkmena
label_attachment_delete: Pašalinkite rinkmeną
-label_attachment_plural: Rinkmenos
+label_attachment_plural: Rinkmenos
+label_file_added: Byla pridÄ—ta
label_report: Ataskaita
-label_report_plural: Ataskaitos
-label_news: Žinia
-label_news_new: Pridėkite žinią
-label_news_plural: Žinios
-label_news_latest: PaskutinÄ—s naujienos
-label_news_view_all: Peržiūrėti visas žinias
-label_change_log: Pakeitimų žurnalas
-label_settings: Nustatymai
-label_overview: Apžvalga
+label_report_plural: Ataskaitos
+label_news: Žinia
+label_news_new: Pridėkite žinią
+label_news_plural: Žinios
+label_news_latest: PaskutinÄ—s naujienos
+label_news_view_all: Peržiūrėti visas žinias
+label_news_added: Naujiena pridÄ—ta
+label_change_log: Pakeitimų žurnalas
+label_settings: Nustatymai
+label_overview: Apžvalga
label_version: Versija
label_version_new: Nauja versija
-label_version_plural: Versijos
-label_confirmation: Patvirtinimas
-label_export_to: Eksportuoti į
-label_read: Skaitykite...
-label_public_projects: Vieši projektai
+label_version_plural: Versijos
+label_confirmation: Patvirtinimas
+label_export_to: Eksportuoti į
+label_read: Skaitykite...
+label_public_projects: Vieši projektai
label_open_issues: atidaryta
label_open_issues_plural: atidarytos
-label_closed_issues: uždaryta
+label_closed_issues: uždaryta
label_closed_issues_plural: uždarytos
-label_total: Bendra suma
-label_permissions: Leidimai
-label_current_status: Einamoji padÄ—tis
-label_new_statuses_allowed: Naujos padÄ—tys galimos
-label_all: visi
-label_none: niekas
-label_nobody: niekas
-label_next: Kitas
-label_previous: Ankstesnis
-label_used_by: Naudotas
-label_details: DetalÄ—s
-label_add_note: PridÄ—kite pastabÄ…
+label_total: Bendra suma
+label_permissions: Leidimai
+label_current_status: Einamoji padÄ—tis
+label_new_statuses_allowed: Naujos padÄ—tys galimos
+label_all: visi
+label_none: niekas
+label_nobody: niekas
+label_next: Kitas
+label_previous: Ankstesnis
+label_used_by: Naudotas
+label_details: DetalÄ—s
+label_add_note: PridÄ—kite pastabÄ…
label_per_page: Per puslapį
-label_calendar: Kalendorius
-label_months_from: mÄ—nesiai nuo
-label_gantt: Gantt
-label_internal: Vidinis
-label_last_changes: paskutiniai %d, pokyÄiai
-label_change_view_all: Peržiūrėti visus pakeitimus
+label_calendar: Kalendorius
+label_months_from: mÄ—nesiai nuo
+label_gantt: Gantt
+label_internal: Vidinis
+label_last_changes: paskutiniai %d, pokyÄiai
+label_change_view_all: Peržiūrėti visus pakeitimus
label_personalize_page: Suasmeninti šį puslapį
label_comment: Komentaras
-label_comment_plural: Komentarai
+label_comment_plural: Komentarai
label_comment_add: PridÄ—kite komentarÄ…
-label_comment_added: Komentaras pridÄ—tas
-label_comment_delete: Pašalinkite komentarus
+label_comment_added: Komentaras pridÄ—tas
+label_comment_delete: Pašalinkite komentarus
label_query: Užklausa
-label_query_plural: Užklausos
-label_query_new: Nauja užklausa
-label_filter_add: PridÄ—ti filtrÄ…
-label_filter_plural: Filtrai
-label_equals: yra
-label_not_equals: nÄ—ra
-label_in_less_than: mažiau negu
-label_in_more_than: daugiau negu
+label_query_plural: Užklausos
+label_query_new: Nauja užklausa
+label_filter_add: PridÄ—ti filtrÄ…
+label_filter_plural: Filtrai
+label_equals: yra
+label_not_equals: nÄ—ra
+label_in_less_than: mažiau negu
+label_in_more_than: daugiau negu
label_in: in
-label_today: šiandien
-label_this_week: šią savaitę
-label_less_than_ago: mažiau negu dienomis prieš
-label_more_than_ago: daugiau negu dienomis prieš
-label_ago: dienomis prieš
-label_contains: turi savyje
-label_not_contains: neturi savyje
-label_day_plural: dienos
-label_repository: Saugykla
+label_today: šiandien
+label_all_time: visas laikas
+label_yesterday: vakar
+label_this_week: šią savaitę
+label_last_week: paskutinÄ— savaitÄ—
+label_last_n_days: paskutinių %d dienų
+label_this_month: šis menuo
+label_last_month: paskutinis menuo
+label_this_year: šiemet
+label_date_range: Dienų diapazonas
+label_less_than_ago: mažiau negu dienomis prieš
+label_more_than_ago: daugiau negu dienomis prieš
+label_ago: dienomis prieš
+label_contains: turi savyje
+label_not_contains: neturi savyje
+label_day_plural: dienos
+label_repository: Saugykla
+label_repository_plural: Saugiklos
label_browse: Naršyti
-label_modification: %d pakeitimas
-label_modification_plural: %d pakeitimai
-label_revision: Revizija
-label_revision_plural: Revizijos
-label_added: pridÄ—tas
-label_modified: pakeistas
-label_deleted: pašalintas
+label_modification: %d pakeitimas
+label_modification_plural: %d pakeitimai
+label_revision: Revizija
+label_revision_plural: Revizijos
+label_associated_revisions: susijusios revizijos
+label_added: pridÄ—tas
+label_modified: pakeistas
+label_copied: nukopijuotas
+label_renamed: pervardintas
+label_deleted: pašalintas
label_latest_revision: PaskutinÄ— revizija
-label_latest_revision_plural: PaskutinÄ—s revizijos
-label_view_revisions: Pežiūrėti revizijas
-label_max_size: Maksimalus dydis
-label_on: 'iš'
-label_sort_highest: Perkelti į viršūnę
-label_sort_higher: Perkelti į viršų
-label_sort_lower: Perkelti žemyn
-label_sort_lowest: Perkelti į apaÄiÄ…
-label_roadmap: Veiklos grafikas
+label_latest_revision_plural: PaskutinÄ—s revizijos
+label_view_revisions: Pežiūrėti revizijas
+label_max_size: Maksimalus dydis
+label_on: 'iš'
+label_sort_highest: Perkelti į viršūnę
+label_sort_higher: Perkelti į viršų
+label_sort_lower: Perkelti žemyn
+label_sort_lowest: Perkelti į apaÄiÄ…
+label_roadmap: Veiklos grafikas
label_roadmap_due_in: Baigiasi po %s
-label_roadmap_overdue: %s vÄ—luojama
+label_roadmap_overdue: %s vÄ—luojama
label_roadmap_no_issues: Jokio darbo šiai versijai nėra
-label_search: Ieškoti
-label_result_plural: Rezultatai
-label_all_words: Visi žodžiai
-label_wiki: Wiki
-label_wiki_edit: Wiki redakcija
-label_wiki_edit_plural: Wiki redakcijos
+label_search: Ieškoti
+label_result_plural: Rezultatai
+label_all_words: Visi žodžiai
+label_wiki: Wiki
+label_wiki_edit: Wiki redakcija
+label_wiki_edit_plural: Wiki redakcijos
label_wiki_page: Wiki puslapis
-label_wiki_page_plural: Wiki puslapiai
-label_index_by_title: Indeksas prie pavadinimo
-label_index_by_date: Indeksas prie datos
+label_wiki_page_plural: Wiki puslapiai
+label_index_by_title: Indeksas prie pavadinimo
+label_index_by_date: Indeksas prie datos
label_current_version: Einamoji versija
-label_preview: Peržiūra
+label_preview: Peržiūra
label_feed_plural: Įeitys(Feeds)
-label_changes_details: Visų pakeitimų detalės
-label_issue_tracking: Darbų sekimas
-label_spent_time: Sugaištas laikas
-label_f_hour: %.2f valanda
-label_f_hour_plural: %.2f valandų
-label_time_tracking: Laiko sekimas
-label_change_plural: Pakeitimai
-label_statistics: Statistika
-label_commits_per_month: Paveda(commit) per mėnesį
-label_commits_per_author: Autoriaus pavedos(commit)
-label_view_diff: Skirtumų peržiūra
-label_diff_inline: įterptas
-label_diff_side_by_side: šalia
+label_changes_details: Visų pakeitimų detalės
+label_issue_tracking: Darbų sekimas
+label_spent_time: Sugaištas laikas
+label_f_hour: %.2f valanda
+label_f_hour_plural: %.2f valandų
+label_time_tracking: Laiko sekimas
+label_change_plural: Pakeitimai
+label_statistics: Statistika
+label_commits_per_month: Paveda(commit) per mėnesį
+label_commits_per_author: Autoriaus pavedos(commit)
+label_view_diff: Skirtumų peržiūra
+label_diff_inline: įterptas
+label_diff_side_by_side: šalia
label_options: Pasirinkimai
-label_copy_workflow_from: Kopijuoti darbų eiga iš
-label_permissions_report: Leidimų pranešimas
-label_watched_issues: Stebimi darbai
-label_related_issues: SusijÄ™ darbai
-label_applied_status: Taikomoji padÄ—tis
-label_loading: Kraunama...
-label_relation_new: Naujas ryšys
-label_relation_delete: Pašalinkite ryšį
-label_relates_to: susietas su
-label_duplicates: dublikatai
-label_blocks: blokai
-label_blocked_by: blokuotas
-label_precedes: įvyksta pirma
-label_follows: seka
+label_copy_workflow_from: Kopijuoti darbų eiga iš
+label_permissions_report: Leidimų pranešimas
+label_watched_issues: Stebimi darbai
+label_related_issues: SusijÄ™ darbai
+label_applied_status: Taikomoji padÄ—tis
+label_loading: Kraunama...
+label_relation_new: Naujas ryšys
+label_relation_delete: Pašalinkite ryšį
+label_relates_to: susietas su
+label_duplicates: dubliuoja
+label_duplicated_by: dubliuojasi
+label_blocks: blokuoja
+label_blocked_by: blokuojasi
+label_precedes: ankstesnÄ—
+label_follows: seka
label_end_to_start: užbaigti, kad pradėti
-label_end_to_end: užbaigti, kad pabaigti
-label_start_to_start: pradÄ—kite pradÄ—ti
-label_start_to_end: pradėkite užbaigti
-label_stay_logged_in: Likti prisijungus
-label_disabled: išjungta(as)
-label_show_completed_versions: Parodyti užbaigtas versijas
-label_me: aš
-label_board: Forumas
-label_board_new: Naujas forumas
-label_board_plural: Forumai
-label_topic_plural: Temos
-label_message_plural: Pranešimai
+label_end_to_end: užbaigti, kad pabaigti
+label_start_to_start: pradÄ—kite pradÄ—ti
+label_start_to_end: pradėkite užbaigti
+label_stay_logged_in: Likti prisijungus
+label_disabled: išjungta(as)
+label_show_completed_versions: Parodyti užbaigtas versijas
+label_me: aš
+label_board: Forumas
+label_board_new: Naujas forumas
+label_board_plural: Forumai
+label_topic_plural: Temos
+label_message_plural: Pranešimai
label_message_last: Paskutinis pranešimas
label_message_new: Naujas pranešimas
-label_reply_plural: Atsakymai
-label_send_information: Nusiųsti paskyros informaciją vartotojui
-label_year: Metai
-label_month: MÄ—nuo
-label_week: SavaitÄ—
-label_date_from: Nuo
-label_date_to: Iki
-label_language_based: Pagrįsta vartotojo kalba
-label_sort_by: Rūšiuoti pagal %s
-label_send_test_email: Nusiųsti bandomąjį elektroninį laišką
-label_feeds_access_key_created_on: RSS prieigos raktas sukūrtas prieš %s
-label_module_plural: Moduliai
+label_message_posted: Pranešimas pridėtas
+label_reply_plural: Atsakymai
+label_send_information: Nusiųsti paskyros informaciją vartotojui
+label_year: Metai
+label_month: MÄ—nuo
+label_week: SavaitÄ—
+label_date_from: Nuo
+label_date_to: Iki
+label_language_based: Pagrįsta vartotojo kalba
+label_sort_by: Rūšiuoti pagal %s
+label_send_test_email: Nusiųsti bandomąjį elektroninį laišką
+label_feeds_access_key_created_on: RSS prieigos raktas sukūrtas prieš %s
+label_module_plural: Moduliai
label_added_time_by: Pridėjo %s prieš %s
-label_updated_time: Atnaujinta prieš %s
-label_jump_to_a_project: Šuolis į projektą...
-label_file_plural: Bylos
-label_changeset_plural: Changesets
-label_default_columns: Numatytosios skiltys
-label_no_change_option: (Jokio pakeitimo)
-label_bulk_edit_selected_issues: Masinis pasirinktų darbų(issues) redagavimas
-label_theme: Tema
-label_default: Numatyta(as)
-label_search_titles_only: Ieškoti pavadinimų tiktai
-label_user_mail_option_all: "Bet kokiam įvykiui visuose mano projektuose"
-label_user_mail_option_selected: "Bet kokiam įvykiui tiktai pasirinktuose projektuose ..."
-label_user_mail_option_none: "Tiktai dalykai kuriuos aš stebiu ar aš esu įtrauktas į"
+label_updated_time_by: Atnaujino %s %s atgal
+label_updated_time: Atnaujinta prieš %s
+label_jump_to_a_project: Šuolis į projektą...
+label_file_plural: Bylos
+label_changeset_plural: Changesets
+label_default_columns: Numatyti stulpeliai
+label_no_change_option: (Jokio pakeitimo)
+label_bulk_edit_selected_issues: Masinis pasirinktų darbų(issues) redagavimas
+label_theme: Tema
+label_default: Numatyta(as)
+label_search_titles_only: Ieškoti pavadinimų tiktai
+label_user_mail_option_all: "Bet kokiam įvykiui visuose mano projektuose"
+label_user_mail_option_selected: "Bet kokiam įvykiui tiktai pasirinktuose projektuose ..."
+label_user_mail_option_none: "Tiktai dalykai kuriuos aš stebiu ar aš esu įtrauktas į"
label_user_mail_no_self_notified: "Nenoriu būti informuotas apie pakeitimus, kuriuos pats atlieku"
label_registration_activation_by_email: "paskyros aktyvacija per e-paštą"
label_registration_manual_activation: "rankinÄ— paskyros aktyvacija"
label_registration_automatic_activation: "automatinÄ— paskyros aktyvacija"
-
-button_login: Registruotis
-button_submit: Pateikti
-button_save: Išsaugoti
+label_display_per_page: '%s įrašų puslapyje'
+label_age: Amžius
+label_change_properties: Pakeisti nustatymus
+label_general: Bendri
+label_more: Daugiau
+label_scm: SCM
+label_plugins: Plugins
+label_ldap_authentication: LDAP autentifikacija
+label_downloads_abbr: siunt.
+label_optional_description: Apibūdinimas (laisvai pasirenkamas)
+label_add_another_file: PridÄ—ti kitÄ… bylÄ…
+label_preferences: SavybÄ—s
+label_chronological_order: Chronologine tvarka
+label_reverse_chronological_order: Atbuline chronologine tvarka
+label_planning: Planavimas
+label_incoming_emails: Įeinantys laiškai
+label_generate_key: Generuoti raktÄ…
+label_issue_watchers: StebÄ—tojai
+label_example: Pavizdys
+
+button_login: Registruotis
+button_submit: Pateikti
+button_save: Išsaugoti
button_check_all: Žymėti visus
button_uncheck_all: Atžymėti visus
-button_delete: Trinti
-button_create: Sukurti
-button_test: Testas
-button_edit: Redaguoti
-button_add: PridÄ—ti
-button_change: Keisti
+button_delete: Trinti
+button_create: Sukurti
+button_test: Testas
+button_edit: Redaguoti
+button_add: PridÄ—ti
+button_change: Keisti
button_apply: Pritaikyti
-button_clear: Išvalyti
-button_lock: Rakinti
-button_unlock: Atrakinti
-button_download: Atsisiųsti
-button_list: Sąrašas
-button_view: Žiūrėti
-button_move: Perkelti
-button_back: Atgal
-button_cancel: Atšaukti
-button_activate: Aktyvinti
+button_clear: Išvalyti
+button_lock: Rakinti
+button_unlock: Atrakinti
+button_download: Atsisiųsti
+button_list: Sąrašas
+button_view: Žiūrėti
+button_move: Perkelti
+button_back: Atgal
+button_cancel: Atšaukti
+button_activate: Aktyvinti
button_sort: Rūšiuoti
-button_log_time: Praleistas laikas
+button_log_time: Praleistas laikas
button_rollback: Grįžti į šią versiją
-button_watch: StebÄ—ti
+button_watch: StebÄ—ti
button_unwatch: NestebÄ—ti
-button_reply: Atsakyti
+button_reply: Atsakyti
button_archive: Archyvuoti
-button_unarchive: Išpakuoti
-button_reset: Reset
-button_rename: Pervadinti
+button_unarchive: Išpakuoti
+button_reset: Reset
+button_rename: Pervadinti
button_change_password: Pakeisti slaptažodį
-button_copy: Kopijuoti
-button_annotate: Rašyti pastabą
-
-status_active: aktyvus
-status_registered: užregistruotas
-status_locked: užrakintas
-
+button_copy: Kopijuoti
+button_annotate: Rašyti pastabą
+button_update: Atnaujinti
+button_configure: Konfigūruoti
+button_quote: Cituoti
+
+status_active: aktyvus
+status_registered: užregistruotas
+status_locked: užrakintas
+
text_select_mail_notifications: Išrinkite veiksmus, apie kuriuos būtų pranešta elektroniniu paštu.
-text_regexp_info: pvz. ^[A-Z0-9]+$
+text_regexp_info: pvz. ^[A-Z0-9]+$
text_min_max_length_info: 0 reiškia jokių apribojimų
-text_project_destroy_confirmation: Ar esate įsitikinęs, kad jūs norite pašalinti šį projektą ir visus susijusius duomenis?
-text_workflow_edit: Išrinkite vaidmenį ir pėdsekį, kad redaguotumėte darbų eigą
-text_are_you_sure: Ar esate įsitikinęs?
-text_journal_changed: pakeistas iš %s į %s
-text_journal_set_to: nustatyta į %s
-text_journal_deleted: ištrintas
-text_tip_task_begin_day: užduotis, prasidedanti šią dieną
-text_tip_task_end_day: užduotis, pasibaigianti šią dieną
-text_tip_task_begin_end_day: užduotis, prasidedanti ir pasibaigianti šią dieną
-text_project_identifier_info: 'Mažosios raidÄ—s (a-z), skaiÄiai ir brÅ«kÅ¡niai galimi.<br/>IÅ¡saugojus, identifikuotojas negali bÅ«ti keiÄiamas.'
-text_caracters_maximum: %d simbolių maksimumas.
-text_caracters_minimum: Turi būti mažiausiai %d simbolių ilgio.
-text_length_between: Ilgis tarp %d ir %d simbolių.
-text_tracker_no_workflow: Jokia darbų eiga neapibrėžta šiam pėdsekiui
-text_unallowed_characters: Neleistini simboliai
-text_comma_separated: Leistinos kelios reikšmės (atskirtos kableliu).
-text_issues_ref_in_commit_messages: Darbų pavedimų(commit) nurodymas ir fiksavimas pranešimuose
-text_issue_added: Darbas %s buvo praneštas (by %s).
-text_issue_updated: Darbas %s buvo atnaujintas (by %s).
-text_wiki_destroy_confirmation: Ar esate įsitikinęs, kad jūs norite pašalinti wiki ir visą jos turinį?
-text_issue_category_destroy_question: Kai kurie darbai (%d) yra paskirti šiai kategorijai. Ką jūs norite daryti?
-text_issue_category_destroy_assignments: Pašalinti kategorijos užduotis
-text_issue_category_reassign_to: Iš naujo priskirti darbus šiai kategorijai
-text_user_mail_option: "neišrinktiems projektams, jūs tiktai gausite pranešimus apie įvykius, kuriuos jūs stebite, arba į kuriuos esate įtrauktas (pvz. darbai, jūs esate autorius ar įgaliotinis)."
-
-default_role_manager: Vadovas
-default_role_developper: Projektuotojas
-default_role_reporter: Pranešėjas
-default_tracker_bug: Klaida
-default_tracker_feature: YpatybÄ—
-default_tracker_support: Palaikymas
-default_issue_status_new: Nauja
-default_issue_status_assigned: Priskirta
-default_issue_status_resolved: Išspręsta
-default_issue_status_feedback: Grįžtamasis ryšys
-default_issue_status_closed: Uždaryta
-default_issue_status_rejected: Atmesta
-default_doc_category_user: Vartotojo dokumentacija
-default_doc_category_tech: Techniniai dokumentacija
-default_priority_low: Žemas
-default_priority_normal: Normalus
-default_priority_high: Aukštas
-default_priority_urgent: Skubus
-default_priority_immediate: NeatidÄ—liotinas
-default_activity_design: Projektavimas
-default_activity_development: Vystymas
-
-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: Įrašų puslapyje nustatimas
-notice_default_data_loaded: Numatytoji konfiguracija sėkmingai užkrauta.
-label_age: Amžius
-label_general: Bendri
-button_update: Atnaujinti
-setting_issues_export_limit: Darbų eksportavimo limitas
-label_change_properties: Pakeisti nustatymus
-text_load_default_configuration: Užkrauti numatytąj konfiguraciją
+text_project_destroy_confirmation: Ar esate įsitikinęs, kad jūs norite pašalinti šį projektą ir visus susijusius duomenis?
+text_subprojects_destroy_warning: 'Šis(ie) subprojektas(ai): %s taip pat bus ištrintas(i).'
+text_workflow_edit: Išrinkite vaidmenį ir pėdsekį, kad redaguotumėte darbų eigą
+text_are_you_sure: Ar esate įsitikinęs?
+text_journal_changed: pakeistas iš %s į %s
+text_journal_set_to: nustatyta į %s
+text_journal_deleted: ištrintas
+text_tip_task_begin_day: užduotis, prasidedanti šią dieną
+text_tip_task_end_day: užduotis, pasibaigianti šią dieną
+text_tip_task_begin_end_day: užduotis, prasidedanti ir pasibaigianti šią dieną
+text_project_identifier_info: 'Mažosios raidÄ—s (a-z), skaiÄiai ir brÅ«kÅ¡niai galimi.<br/>IÅ¡saugojus, identifikuotojas negali bÅ«ti keiÄiamas.'
+text_caracters_maximum: %d simbolių maksimumas.
+text_caracters_minimum: Turi būti mažiausiai %d simbolių ilgio.
+text_length_between: Ilgis tarp %d ir %d simbolių.
+text_tracker_no_workflow: Jokia darbų eiga neapibrėžta šiam pėdsekiui
+text_unallowed_characters: Neleistini simboliai
+text_comma_separated: Leistinos kelios reikšmės (atskirtos kableliu).
+text_issues_ref_in_commit_messages: Darbų pavedimų(commit) nurodymas ir fiksavimas pranešimuose
+text_issue_added: Darbas %s buvo praneštas (by %s).
+text_issue_updated: Darbas %s buvo atnaujintas (by %s).
+text_wiki_destroy_confirmation: Ar esate įsitikinęs, kad jūs norite pašalinti wiki ir visą jos turinį?
+text_issue_category_destroy_question: Kai kurie darbai (%d) yra paskirti šiai kategorijai. Ką jūs norite daryti?
+text_issue_category_destroy_assignments: Pašalinti kategorijos užduotis
+text_issue_category_reassign_to: Iš naujo priskirti darbus šiai kategorijai
+text_user_mail_option: "neišrinktiems projektams, jūs tiktai gausite pranešimus apie įvykius, kuriuos jūs stebite, arba į kuriuos esate įtrauktas (pvz. darbai, jūs esate autorius ar įgaliotinis)."
text_no_configuration_data: "Vaidmenys, pėdsekiai, darbų būsenos ir darbų eiga dar nebuvo konfigūruoti.\nGriežtai rekomenduojam užkrauti numatytąją(default)konfiguraciją. Užkrovus, galėsite ją modifikuoti."
-label_repository_plural: Saugiklos
-error_can_t_load_default_data: "Numatytoji konfiguracija negali būti užkrauta: %s"
-label_associated_revisions: susijusios revizijos
-setting_user_format: Vartotojo atvaizdavimo formatas
+text_load_default_configuration: Užkrauti numatytąj konfiguraciją
text_status_changed_by_changeset: Pakeista %s revizijoi.
-label_more: Daugiau
text_issues_destroy_confirmation: 'Ar jūs tikrai norite panaikinti pažimėtą(us) darbą(us)?'
-label_scm: SCM
text_select_project_modules: 'Parinkite modulius, kuriuos norite naudoti šiame projekte:'
-label_issue_added: Darbas pridÄ—tas
-label_issue_updated: Darbas atnaujintas
-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: Forumai
-project_module_issue_tracking: Darbu pÄ—dsekys
-project_module_wiki: Wiki
-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_file_repository_writable: Ä® rinkmenu saugyklÄ… galima saugoti (RW)
text_rmagick_available: RMagick pasiekiamas (pasirinktinai)
-button_configure: Konfiguruoti
-label_plugins: Plugins
-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_assign_time_entries_to_project: Priskirti valandas prie projekto
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_enumeration_category_reassign_to: 'Priskirti juos šiai reikšmei:'
text_email_delivery_not_configured: "Email pristatymas nesukonfigūruotas , ir perspėjimai neaktyvus.\nSukonfigūruokyte savo SMTP serverį byloje config/email.yml ir perleiskyte programą kad pritaikyti pakeitymus."
-field_parent_title: Aukštesnio lygio puslapis
-label_issue_watchers: Stebetojai
-setting_commit_logs_encoding: Commit pranėšimų koduotė
-setting_sequential_project_identifiers: Generate sequential project identifiers
-button_quote: Cituoti
-notice_unable_delete_version: Neimanoma panaikinti versijÄ…
-label_renamed: pervardintas
-label_copied: nukopijuotas
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+text_diff_truncated: "... Å is diff'as nukarpitas, todÄ—l kad jis virÅ¡ijo maksimalu rodoma eiluÄiu skaiÄiu."
+
+default_role_manager: Vadovas
+default_role_developper: Projektuotojas
+default_role_reporter: Pranešėjas
+default_tracker_bug: Klaida
+default_tracker_feature: YpatybÄ—
+default_tracker_support: Palaikymas
+default_issue_status_new: Nauja
+default_issue_status_assigned: Priskirta
+default_issue_status_resolved: Išspręsta
+default_issue_status_feedback: Grįžtamasis ryšys
+default_issue_status_closed: Uždaryta
+default_issue_status_rejected: Atmesta
+default_doc_category_user: Vartotojo dokumentacija
+default_doc_category_tech: Techniniai dokumentacija
+default_priority_low: Žemas
+default_priority_normal: Normalus
+default_priority_high: Aukštas
+default_priority_urgent: Skubus
+default_priority_immediate: NeatidÄ—liotinas
+default_activity_design: Projektavimas
+default_activity_development: Vystymas
+
+enumeration_issue_priorities: Darbo prioritetai
+enumeration_doc_categories: Dokumento kategorijos
+enumeration_activities: Veiklos (laiko sekimas)
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/mk.yml b/lang/mk.yml
new file mode 100644
index 000000000..269d4dddb
--- /dev/null
+++ b/lang/mk.yml
@@ -0,0 +1,711 @@
+_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: doesn't match 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: This relation would create a circular dependency
+
+general_fmt_age: %d год.
+general_fmt_age_plural: %d год.
+general_fmt_date: %%d/%%m/%%Y
+general_fmt_datetime: %%d/%%m/%%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: 'МакедонÑки'
+general_csv_separator: ','
+general_csv_decimal_separator: '.'
+general_csv_encoding: UTF-8
+general_pdf_encoding: UTF-8
+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: Data has been updated by another user.
+notice_not_authorized: Ðемате овлаÑтување да приÑтапите до оваа Ñтраница.
+notice_email_sent: ИÑпратена е е-порака до %s
+notice_email_error: Грешка при праќањето на е-пошта (%s)
+notice_feeds_access_key_reseted: Вашиот RSS клуч е реÑетиран.
+notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s."
+notice_no_issue_selected: "Ðе е избрана задача! Изберете ги задачите што Ñакате да ги уредувате."
+notice_account_pending: "Вашиот профил е креиран и чека одобрување од админиÑтраторот."
+notice_default_data_loaded: ОÑновната конфигурација е уÑпешно вчитана.
+notice_unable_delete_version: Верзијата неможе да Ñе избрише.
+
+error_can_t_load_default_data: "ОÑновната конфигурација неможе да биде вчитана: %s"
+error_scm_not_found: "The entry or revision was not found in the repository."
+error_scm_command_failed: "An error occurred when trying to access the repository: %s"
+error_scm_annotate: "The entry does not exist or can not be annotated."
+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) е региÑтриран. Профилот чека на Ваше одобрување:'
+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 грешка
+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: Tracker
+field_subject: ÐаÑлов
+field_due_date: Краен датум
+field_assigned_to: Доделена на
+field_priority: Приоритет
+field_fixed_version: За верзија
+field_user: КориÑник
+field_role: Функција
+field_homepage: Homepagе
+field_is_public: Јавен
+field_parent: Подпроект на
+field_is_in_chlog: Issues displayed in changelog
+field_is_in_roadmap: Issues displayed 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: Account
+field_base_dn: Base DN
+field_attr_login: Login attribute
+field_attr_firstname: Firstname attribute
+field_attr_lastname: Lastname attribute
+field_attr_mail: Email attribute
+field_onthefly: On-the-fly user creation
+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: Used as a filter
+field_issue_to_id: Related issue
+field_delay: КаÑнење
+field_assignable: Ðа оваа функција може да и Ñе доделуваат задачи
+field_redirect_existing_links: Redirect existing links
+field_estimated_hours: Предвидено време
+field_column_names: Колони
+field_time_zone: ВременÑка зона
+field_searchable: Searchable
+field_default_value: ОÑновна вредноÑÑ‚
+field_comments_sorting: Прикажи коментари
+field_parent_title: Parent page
+
+setting_app_title: Име на апликацијата
+setting_app_subtitle: Application 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: Blind carbon copy recipients (bcc)
+setting_plain_text_mail: обичен текÑÑ‚ (без HTML)
+setting_host_name: Име на хоÑтот и патека
+setting_text_formatting: Форматирање на текÑÑ‚
+setting_wiki_compression: КомпреÑија на иÑторијата на Вики
+setting_feeds_limit: Feed content limit
+setting_default_projects_public: Ðовите проекти Ñе иницијално јавни
+setting_autofetch_changesets: Autofetch commits
+setting_sys_api_enabled: Enable WS for repository management
+setting_commit_ref_keywords: Referencing keywords
+setting_commit_fix_keywords: Fixing keywords
+setting_autologin: ÐвтоматÑка најава
+setting_date_format: Формат на дата
+setting_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: Подножје на е-пораките
+setting_protocol: Протокол
+setting_per_page_options: Objects per page options
+setting_user_format: Приказ на кориÑник
+setting_activity_days_default: Денови прикажани од активноÑта на проект
+setting_display_subprojects_issues: Display subprojects issues on main projects by default
+setting_enabled_scm: Ðктивирај SCM
+setting_mail_handler_api_enabled: Enable WS for incoming emails
+setting_mail_handler_api_key: API клуч
+setting_sequential_project_identifiers: Генерирај поÑледователни идентификатори на проекти
+setting_gravatar_enabled: КориÑи Gravatar кориÑнички икони
+setting_diff_max_lines_displayed: МакÑ. број на прикажани diff линии
+
+permission_edit_project: Уреди проект
+permission_select_project_modules: Избери модули за проектот
+permission_manage_members: Manage members
+permission_manage_versions: Manage versions
+permission_manage_categories: Manage issue categories
+permission_add_issues: Додади задачи
+permission_edit_issues: Уреди задачи
+permission_manage_issue_relations: Manage issue relations
+permission_add_issue_notes: Додади белешки
+permission_edit_issue_notes: Уреди белешки
+permission_edit_own_issue_notes: Уреди лични белешки
+permission_move_issues: ПремеÑти задачи
+permission_delete_issues: Избриши задачи
+permission_manage_public_queries: Manage public queries
+permission_save_queries: Save queries
+permission_view_gantt: View gantt chart
+permission_view_calendar: Прикажи календар
+permission_view_issue_watchers: Прикажи лиÑта на монитори
+permission_add_issue_watchers: Додади монитори
+permission_log_time: Бележи потрошено време
+permission_view_time_entries: Прикажи потрошено време
+permission_edit_time_entries: Edit time logs
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_news: Уредувај новоÑти
+permission_comment_news: Коментирај новоÑти
+permission_manage_documents: Уредувај документи
+permission_view_documents: Прегледувај документи
+permission_manage_files: Уредувај датотеки
+permission_view_files: Прегледувај датотеки
+permission_manage_wiki: Уредувај вики
+permission_rename_wiki_pages: Преименувај вики Ñтрани
+permission_delete_wiki_pages: Бриши вики Ñтрани
+permission_view_wiki_pages: Прегледувај вики
+permission_view_wiki_edits: Прегледувај вики иÑторија
+permission_edit_wiki_pages: Уредувај вики Ñтрани
+permission_delete_wiki_pages_attachments: Бриши прилози
+permission_protect_wiki_pages: Protect wiki pages
+permission_manage_repository: Manage repository
+permission_browse_repository: Browse repository
+permission_view_changesets: View changesets
+permission_commit_access: Commit access
+permission_manage_boards: Уредувај форуми
+permission_view_messages: Прегледувај пораки
+permission_add_messages: Праќај пораки
+permission_edit_messages: Уредувај пораки
+permission_edit_own_messages: Уредувај ÑопÑтвени пораки
+permission_delete_messages: Бриши пораки
+permission_delete_own_messages: Бриши ÑопÑтвени пораки
+
+project_module_issue_tracking: Следење на задачи
+project_module_time_tracking: Следење на време
+project_module_news: ÐовоÑти
+project_module_documents: Документи
+project_module_files: Датотеки
+project_module_wiki: Вики
+project_module_repository: 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: Tracker
+label_tracker_plural: Trackers
+label_tracker_new: New tracker
+label_workflow: 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: Custom field
+label_custom_field_plural: Custom fields
+label_custom_field_new: New custom field
+label_enumerations: 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_overall_activity: Севкупна активноÑÑ‚
+label_user_activity: "ÐктивноÑÑ‚ на %s"
+label_new: Ðов
+label_logged_as: Ðајавен како
+label_environment: Environment
+label_authentication: Ðвтентикација
+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: Датум
+label_integer: Цел број
+label_float: Децимален
+label_boolean: 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: поÑледни %d промени
+label_change_view_all: Прегледај ги Ñите промени
+label_personalize_page: Прилагоди ја оваа Ñтрана
+label_comment: Коментар
+label_comment_plural: Коментари
+label_comment_add: Додади коментар
+label_comment_added: Коментарот е додаден
+label_comment_delete: Избриши ги коментарите
+label_query: Custom query
+label_query_plural: Custom queries
+label_query_new: New query
+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: Date range
+label_less_than_ago: пред помалку од дена
+label_more_than_ago: пред повеќе од дена
+label_ago: пред дена
+label_contains: Ñодржи
+label_not_contains: не Ñодржи
+label_day_plural: дена
+label_repository: Repository
+label_repository_plural: Repositories
+label_browse: Browse
+label_modification: %d промена
+label_modification_plural: %d промени
+label_revision: Ревизија
+label_revision_plural: Ревизии
+label_associated_revisions: Associated revisions
+label_added: додадено
+label_modified: променето
+label_copied: копирано
+label_renamed: преименувано
+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: За %s
+label_roadmap_overdue: %s покаÑно
+label_roadmap_no_issues: Ðема задачи за оваа верзија
+label_search: Пребарувај
+label_result_plural: Резултати
+label_all_words: Сите зборови
+label_wiki: Вики
+label_wiki_edit: Уредување на вики
+label_wiki_edit_plural: Уредувања на вики
+label_wiki_page: Вики Ñтраница
+label_wiki_page_plural: Вики Ñтраници
+label_index_by_title: Index by title
+label_index_by_date: 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 per month
+label_commits_per_author: Commits per author
+label_view_diff: Прегледај ги разликите
+label_diff_inline: inline
+label_diff_side_by_side: side by side
+label_options: Опции
+label_copy_workflow_from: Copy workflow from
+label_permissions_report: Извештај Ñо овлаÑтувања
+label_watched_issues: Следени задачи
+label_related_issues: Поврзани задачи
+label_applied_status: Applied status
+label_loading: Вчитување...
+label_relation_new: New relation
+label_relation_delete: Delete relation
+label_relates_to: related to
+label_duplicates: дупликати
+label_duplicated_by: duplicated by
+label_blocks: blocks
+label_blocked_by: 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_stay_logged_in: ОÑтани најавен
+label_disabled: disabled
+label_show_completed_versions: Покажи ги завршените верзии
+label_me: јаÑ
+label_board: Форум
+label_board_new: Ðов форум
+label_board_plural: Форуми
+label_topic_plural: Topics
+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 клучот е креиран пред %s
+label_module_plural: Модули
+label_added_time_by: Додадено од %s пред %s
+label_updated_time_by: Ðжурирано од %s пред %s
+label_updated_time: Ðжурирано пред %s
+label_jump_to_a_project: Промени проект...
+label_file_plural: Датотеки
+label_changeset_plural: Changesets
+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: account activation by email
+label_registration_manual_activation: manual account activation
+label_registration_automatic_activation: automatic account activation
+label_display_per_page: 'По Ñтрана: %s'
+label_age: Age
+label_change_properties: Промени ÑвојÑтва
+label_general: Општо
+label_more: Повеќе
+label_scm: SCM
+label_plugins: Додатоци
+label_ldap_authentication: LDAP автентикација
+label_downloads_abbr: Преземања
+label_optional_description: Дополнителен опиÑ
+label_add_another_file: Додадете друга датотека
+label_preferences: Параметри
+label_chronological_order: In chronological order
+label_reverse_chronological_order: In reverse chronological order
+label_planning: Планирање
+label_incoming_emails: Incoming emails
+label_generate_key: Генерирај клуч
+label_issue_watchers: Монитори
+label_example: Пример
+
+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: Back
+button_cancel: Откажи
+button_activate: Ðктивирај
+button_sort: Сортирај
+button_log_time: Бележи време
+button_rollback: Врати Ñе на оваа верзија
+button_watch: Следи
+button_unwatch: Ðе Ñледи
+button_reply: Одговори
+button_archive: Ðрхивирај
+button_unarchive: Unarchive
+button_reset: Reset
+button_rename: Преименувај
+button_change_password: Промени лозинка
+button_copy: Копирај
+button_annotate: Annotate
+button_update: Ðжурирај
+button_configure: Конфигурирај
+button_quote: Цитирај
+
+status_active: активен
+status_registered: региÑтриран
+status_locked: заклучен
+
+text_select_mail_notifications: Select actions for which email notifications should be sent.
+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: Select a role and a tracker to edit the workflow
+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), бројките и dashes Ñе дозволени.<br />По зачувувањето, идентификаторот неможе да Ñе менува.'
+text_caracters_maximum: МакÑимум %d знаци.
+text_caracters_minimum: Мора да е најмалку %d знаци долгa.
+text_length_between: Должина меѓу %d и %d знаци.
+text_tracker_no_workflow: No workflow defined for this tracker
+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: Дали Ñте Ñигурни дека Ñакате да ја избришете оваа вики и целата нејзина Ñодржина ?
+text_issue_category_destroy_question: Ðекои задачи (%d) Ñе доделени во оваа категорија. Што Ñакате да направите ?
+text_issue_category_destroy_assignments: Remove category assignments
+text_issue_category_reassign_to: Reassign issues to this category
+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: "Финкции, trackers, ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° задачи and workflow Ñеуште не Ñе конфигурирани.\nПрепорачано е вчитување на оÑновната конфигурација. По вчитувањето може да Ñе промени."
+text_load_default_configuration: Вчитај ја оÑновната конфигурација
+text_status_changed_by_changeset: Applied in changeset %s.
+text_issues_destroy_confirmation: 'Дали Ñте Ñигурни дека Ñакате да ги избришете избраните задачи ?'
+text_select_project_modules: 'Избери кои модули да бидат активни за овој проект:'
+text_default_administrator_account_changed: ОÑновниот админиÑтраторÑки профил е Ñменет
+text_file_repository_writable: File repository writable
+text_rmagick_available: RMagick инÑталиран (optional)
+text_destroy_time_entries_question: %.02f чаÑа Ñе пријавени за задачите што Ñакате да ги избришете. Што Ñакате да направите ?
+text_destroy_time_entries: Избриши ги пријавените чаÑови
+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 напиша:'
+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.\nКонфигурирајте го Вашиот SMTP Ñервер во config/email.yml и реÑтартирај те ја апликацијата to enable them."
+text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+
+default_role_manager: Менаџер
+default_role_developper: Програмер
+default_role_reporter: Reporter
+default_tracker_bug: Грешка
+default_tracker_feature: ФункционалноÑÑ‚
+default_tracker_support: Поддршка
+default_issue_status_new: New
+default_issue_status_assigned: Доделена
+default_issue_status_resolved: Решена
+default_issue_status_feedback: 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: Типови на активноÑÑ‚ (Ñледење на време)
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+warning_attachments_not_saved: "%d file(s) could not be saved."
+field_editable: Editable
+text_plugin_assets_writable: Plugin assets directory writable
+label_display: Display
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/nl.yml b/lang/nl.yml
index 18954534a..859972ee0 100644
--- a/lang/nl.yml
+++ b/lang/nl.yml
@@ -1,5 +1,4 @@
_gloc_rule_default: '|n| n==1 ? "" : "_plural" '
-
actionview_datehelper_select_day_prefix:
actionview_datehelper_select_month_names: Januari,Februari,Maart,April,Mei,Juni,Juli,Augustus,September,Oktober,November,December
actionview_datehelper_select_month_names_abbr: Jan,Feb,Maa,Apr,Mei,Jun,Jul,Aug,Sep,Okt,Nov,Dec
@@ -18,630 +17,678 @@ actionview_datehelper_time_in_words_minute_single: 1 minuut
actionview_datehelper_time_in_words_second_less_than: minder dan een seconde
actionview_datehelper_time_in_words_second_less_than_plural: minder dan %d seconden
actionview_instancetag_blank_option: Selecteer
-
-activerecord_error_inclusion: staat niet in de lijst
-activerecord_error_exclusion: is gereserveerd
-activerecord_error_invalid: is ongeldig
-activerecord_error_confirmation: komt niet overeen met confirmatie
activerecord_error_accepted: moet geaccepteerd worden
-activerecord_error_empty: mag niet leeg zijn
activerecord_error_blank: mag niet blanco zijn
-activerecord_error_too_long: is te lang
-activerecord_error_too_short: is te kort
-activerecord_error_wrong_length: heeft de verkeerde lengte
-activerecord_error_taken: is al in gebruik
-activerecord_error_not_a_number: is geen getal
+activerecord_error_circular_dependency: Deze relatie zou een circulaire afhankelijkheid tot gevolg hebben
+activerecord_error_confirmation: komt niet overeen met bevestiging
+activerecord_error_empty: mag niet leeg zijn
+activerecord_error_exclusion: is gereserveerd
+activerecord_error_greater_than_start_date: moet na de startdatum liggen
+activerecord_error_inclusion: staat niet in de lijst
+activerecord_error_invalid: is ongeldig
activerecord_error_not_a_date: is geen valide datum
-activerecord_error_greater_than_start_date: moet hoger zijn dan startdatum
+activerecord_error_not_a_number: is geen getal
activerecord_error_not_same_project: hoort niet bij hetzelfde project
-activerecord_error_circular_dependency: Deze relatie zou een circulaire afhankelijkheid tot gevolg hebben
-
-general_fmt_age: %d jr
-general_fmt_age_plural: %d jr
-general_fmt_date: %%m/%%d/%%Y
-general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p
-general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
-general_fmt_time: %%I:%%M %%p
-general_text_No: 'Nee'
-general_text_Yes: 'Ja'
-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
-general_first_day_of_week: '7'
-
-notice_account_updated: Account is met succes gewijzigd
-notice_account_invalid_creditentials: Incorrecte gebruikersnaam of wachtwoord
-notice_account_password_updated: Wachtwoord is met succes gewijzigd
-notice_account_wrong_password: Incorrect wachtwoord
-notice_account_register_done: Account is met succes aangemaakt.
-notice_account_unknown_email: Onbekende gebruiker.
-notice_can_t_change_password: Dit account gebruikt een externe bron voor authenticatie. Het is niet mogelijk om het wachtwoord te veranderen.
-notice_account_lost_email_sent: Er is een email naar U verstuurd met instructies over het kiezen van een nieuw wachtwoord.
-notice_account_activated: Uw account is geactiveerd. U kunt nu inloggen.
-notice_successful_create: Maken succesvol.
-notice_successful_update: Wijzigen succesvol.
-notice_successful_delete: Verwijderen succesvol.
-notice_successful_connection: Verbinding succesvol.
-notice_file_not_found: De pagina die U probeerde te benaderen bestaat niet of is verwijderd.
-notice_locking_conflict: De gegevens zijn gewijzigd door een andere gebruiker.
-notice_not_authorized: Het is U niet toegestaan om deze pagina te raadplegen.
-notice_email_sent: Een e-mail werd verstuurd naar %s
-notice_email_error: Er is een fout opgetreden tijdens het versturen van (%s)
-notice_feeds_access_key_reseted: Je RSS toegangssleutel werd gereset.
-
+activerecord_error_taken: is al in gebruik
+activerecord_error_too_long: is te lang
+activerecord_error_too_short: is te kort
+activerecord_error_wrong_length: heeft een onjuiste lengte
+button_activate: Activeer
+button_add: Voeg toe
+button_annotate: Annoteer
+button_apply: Pas toe
+button_archive: Archiveer
+button_back: Terug
+button_cancel: Annuleer
+button_change: Wijzig
+button_change_password: Wijzig wachtwoord
+button_check_all: Selecteer alle
+button_clear: Leeg maken
+button_configure: Configureer
+button_copy: Kopiëer
+button_create: Maak
+button_delete: Verwijder
+button_download: Download
+button_edit: Bewerk
+button_list: Lijst
+button_lock: Sluit
+button_log_time: Log tijd
+button_login: Inloggen
+button_move: Verplaatsen
+button_quote: Citaat
+button_rename: Hernoemen
+button_reply: Antwoord
+button_reset: Reset
+button_rollback: Rollback naar deze versie
+button_save: Bewaren
+button_sort: Sorteer
+button_submit: Toevoegen
+button_test: Test
+button_unarchive: Dearchiveer
+button_uncheck_all: Deselecteer alle
+button_unlock: Open
+button_unwatch: Niet meer monitoren
+button_update: Update
+button_view: Bekijken
+button_watch: Monitor
+default_activity_design: Ontwerp
+default_activity_development: Ontwikkeling
+default_doc_category_tech: Technische documentatie
+default_doc_category_user: Gebruikersdocumentatie
+default_issue_status_assigned: Toegewezen
+default_issue_status_closed: Gesloten
+default_issue_status_feedback: Terugkoppeling
+default_issue_status_new: Nieuw
+default_issue_status_rejected: Afgewezen
+default_issue_status_resolved: Opgelost
+default_priority_high: Hoog
+default_priority_immediate: Onmiddellijk
+default_priority_low: Laag
+default_priority_normal: Normaal
+default_priority_urgent: Spoed
+default_role_developper: Ontwikkelaar
+default_role_manager: Manager
+default_role_reporter: Rapporteur
+default_tracker_bug: Bug
+default_tracker_feature: Feature
+default_tracker_support: Support
+enumeration_activities: Activiteiten (tijdtracking)
+enumeration_doc_categories: Documentcategorieën
+enumeration_issue_priorities: Issueprioriteiten
+error_can_t_load_default_data: "De standaard configuratie kon niet worden geladen: %s"
+error_issue_not_found_in_project: 'Deze issue is niet gevonden of behoort niet toe tot dit project.'
+error_scm_annotate: "Er kan geen commentaar toegevoegd worden."
+error_scm_command_failed: "Er trad een fout op tijdens de poging om verbinding te maken met de repository: %s"
error_scm_not_found: "Deze ingang of revisie bestaat niet in de repository."
-error_scm_command_failed: "Een fout trad op tijdens de poging om verbinding te maken met de repository: %s"
-
-mail_subject_lost_password: Uw %s wachtwoord
-mail_body_lost_password: 'Gebruik de volgende link om Uw wachtwoord te wijzigen:'
-mail_subject_register: Uw %s account activatie
-mail_body_register: 'Gebruik de volgende link om Uw account te activeren:'
-
-gui_validation_error: 1 fout
-gui_validation_error_plural: %d fouten
-
-field_name: Naam
-field_description: Beschrijving
-field_summary: Samenvatting
-field_is_required: Verplicht
-field_firstname: Voornaam
-field_lastname: Achternaam
-field_mail: Email
-field_filename: Bestand
-field_filesize: Grootte
-field_downloads: Downloads
+field_account: Account
+field_activity: Activiteit
+field_admin: Beheerder
+field_assignable: Issues kunnen toegewezen worden aan deze rol
+field_assigned_to: Toegewezen aan
+field_attr_firstname: Voornaam attribuut
+field_attr_lastname: Achternaam attribuut
+field_attr_login: Login attribuut
+field_attr_mail: E-mail attribuut
+field_auth_source: Authenticatiemethode
field_author: Auteur
+field_base_dn: Base DN
+field_category: Categorie
+field_column_names: Kolommen
+field_comments: Commentaar
+field_comments_sorting: Commentaar weergeven
field_created_on: Aangemaakt
-field_updated_on: Gewijzigd
+field_default_value: Standaardwaarde
+field_delay: Vertraging
+field_description: Beschrijving
+field_done_ratio: %% Gereed
+field_downloads: Downloads
+field_due_date: Verwachte datum gereed
+field_effective_date: Datum
+field_estimated_hours: Geschatte tijd
field_field_format: Formaat
-field_is_for_all: Voor alle projecten
-field_possible_values: Mogelijke waarden
-field_regexp: Reguliere expressie
-field_min_length: Minimale lengte
-field_max_length: Maximale lengte
-field_value: Waarde
-field_category: Categorie
-field_title: Titel
-field_project: Project
-field_issue: Issue
-field_status: Status
-field_notes: Notities
+field_filename: Bestand
+field_filesize: Grootte
+field_firstname: Voornaam
+field_fixed_version: Versie
+field_hide_mail: Verberg mijn e-mailadres
+field_homepage: Homepage
+field_host: Host
+field_hours: Uren
+field_identifier: Identificatiecode
field_is_closed: Issue gesloten
field_is_default: Standaard
-field_tracker: Tracker
-field_subject: Onderwerp
-field_due_date: Verwachte datum gereed
-field_assigned_to: Toegewezen aan
-field_priority: Prioriteit
-field_fixed_version: Doel versie
-field_user: Gebruiker
-field_role: Rol
-field_homepage: Homepage
-field_is_public: Publiek
-field_parent: Subproject van
+field_is_filter: Gebruikt als een filter
+field_is_for_all: Voor alle projecten
field_is_in_chlog: Issues weergegeven in wijzigingslog
field_is_in_roadmap: Issues weergegeven in roadmap
+field_is_public: Publiek
+field_is_required: Verplicht
+field_issue: Issue
+field_issue_to_id: Gerelateerd issue
+field_language: Taal
+field_last_login_on: Laatste bezoek
+field_lastname: Achternaam
field_login: Inloggen
+field_mail: E-mail
field_mail_notification: Mail mededelingen
-field_admin: Beheerder
-field_last_login_on: Laatste bezoek
-field_language: Taal
-field_effective_date: Datum
-field_password: Wachtwoord
+field_max_length: Maximale lengte
+field_min_length: Minimale lengte
+field_name: Naam
field_new_password: Nieuw wachtwoord
+field_notes: Notities
+field_onthefly: On-the-fly aanmaken van een gebruiker
+field_parent: Subproject van
+field_parent_title: Bovenliggende pagina
+field_password: Wachtwoord
field_password_confirmation: Bevestigen
-field_version: Versie
-field_type: Type
-field_host: Host
field_port: Port
-field_account: Account
-field_base_dn: Base DN
-field_attr_login: Login attribuut
-field_attr_firstname: Voornaam attribuut
-field_attr_lastname: Achternaam attribuut
-field_attr_mail: Email attribuut
-field_onthefly: On-the-fly aanmaken van een gebruiker
-field_start_date: Start
-field_done_ratio: %% Gereed
-field_auth_source: Authenticatiemethode
-field_hide_mail: Verberg mijn emailadres
-field_comments: Commentaar
-field_url: URL
+field_possible_values: Mogelijke waarden
+field_priority: Prioriteit
+field_project: Project
+field_redirect_existing_links: Verwijs bestaande links door
+field_regexp: Reguliere expressie
+field_role: Rol
+field_searchable: Doorzoekbaar
+field_spent_on: Datum
+field_start_date: Startdatum
field_start_page: Startpagina
+field_status: Status
+field_subject: Onderwerp
field_subproject: Subproject
-field_hours: Uren
-field_activity: Activiteit
-field_spent_on: Datum
-field_identifier: Identificatiecode
-field_is_filter: Gebruikt als een filter
-field_issue_to_id: Gerelateerd issue
-field_delay: Vertraging
-field_assignable: Issues kunnen toegewezen worden aan deze rol
-field_redirect_existing_links: Verwijs bestaande links door
-field_estimated_hours: Geschatte tijd
-field_default_value: Standaard waarde
-
-setting_app_title: Applicatie titel
-setting_app_subtitle: Applicatie ondertitel
-setting_welcome_text: Welkomsttekst
-setting_default_language: Standaard taal
-setting_login_required: Authent. nodig
-setting_self_registration: Zelf-registratie toegestaan
-setting_attachment_max_size: Attachment max. grootte
-setting_issues_export_limit: Limiet export issues
-setting_mail_from: Afzender mail adres
-setting_host_name: Host naam
-setting_text_formatting: Tekst formaat
-setting_wiki_compression: Wiki geschiedenis comprimeren
-setting_feeds_limit: Feed inhoud limiet
-setting_autofetch_changesets: Haal commits automatisch op
-setting_sys_api_enabled: Gebruik WS voor repository beheer
-setting_commit_ref_keywords: Referencing trefwoorden
-setting_commit_fix_keywords: Fixing trefwoorden
-setting_autologin: Autologin
-setting_date_format: Datum formaat
-setting_cross_project_issue_relations: Sta cross-project issue relaties toe
-
-label_user: Gebruiker
-label_user_plural: Gebruikers
-label_user_new: Nieuwe gebruiker
-label_project: Project
-label_project_new: Nieuw project
-label_project_plural: Projecten
-label_project_all: Alle Projecten
-label_project_latest: Nieuwste projecten
-label_issue: Issue
-label_issue_new: Nieuw issue
-label_issue_plural: Issues
-label_issue_view_all: Bekijk alle issues
+field_summary: Samenvatting
+field_time_zone: Tijdzone
+field_title: Titel
+field_tracker: Tracker
+field_type: Type
+field_updated_on: Gewijzigd
+field_url: URL
+field_user: Gebruiker
+field_value: Waarde
+field_version: Versie
+general_csv_decimal_separator: '.'
+general_csv_encoding: ISO-8859-1
+general_csv_separator: ','
+general_day_names: Maandag, Dinsdag, Woensdag, Donderdag, Vrijdag, Zaterdag, Zondag
+general_first_day_of_week: '7'
+general_fmt_age: %d jr
+general_fmt_age_plural: %d jr
+general_fmt_date: %%d/%%m/%%Y
+general_fmt_datetime: %%d/%%m/%%Y %%H:%%M
+general_fmt_datetime_short: %%d %%b, %%H:%%M
+general_fmt_time: %%H:%%M
+general_lang_name: 'Nederlands'
+general_pdf_encoding: ISO-8859-1
+general_text_No: 'Nee'
+general_text_Yes: 'Ja'
+general_text_no: 'nee'
+general_text_yes: 'ja'
+gui_validation_error: 1 fout
+gui_validation_error_plural: %d fouten
+label_activity: Activiteit
+label_add_another_file: Ander bestand toevoegen
+label_add_note: Voeg een notitie toe
+label_added: toegevoegd
+label_added_time_by: Toegevoegd door %s %s geleden
+label_administration: Administratie
+label_age: Leeftijd
+label_ago: dagen geleden
+label_all: alle
+label_all_time: alles
+label_all_words: Alle woorden
+label_and_its_subprojects: %s en zijn subprojecten.
+label_applied_status: Toegekende status
+label_assigned_to_me_issues: Aan mij toegewezen issues
+label_associated_revisions: Geassociëerde revisies
+label_attachment: Bestand
+label_attachment_delete: Verwijder bestand
+label_attachment_new: Nieuw bestand
+label_attachment_plural: Bestanden
+label_attribute: Attribuut
+label_attribute_plural: Attributen
+label_auth_source: Authenticatiemodus
+label_auth_source_new: Nieuwe authenticatiemodus
+label_auth_source_plural: Authenticatiemodi
+label_authentication: Authenticatie
+label_blocked_by: geblokkeerd door
+label_blocks: blokkeert
+label_board: Forum
+label_board_new: Nieuw forum
+label_board_plural: Forums
+label_boolean: Boolean
+label_browse: Blader
+label_bulk_edit_selected_issues: Bewerk geselecteerde issues in bulk
+label_calendar: Kalender
+label_change_log: Wijzigingslog
+label_change_plural: Wijzigingen
+label_change_properties: Eigenschappen wijzigen
+label_change_status: Wijzig status
+label_change_view_all: Bekijk alle wijzigingen
+label_changes_details: Details van alle wijzigingen
+label_changeset_plural: Changesets
+label_chronological_order: In chronologische volgorde
+label_closed_issues: gesloten
+label_closed_issues_plural: gesloten
+label_comment: Commentaar
+label_comment_add: Voeg commentaar toe
+label_comment_added: Commentaar toegevoegd
+label_comment_delete: Verwijder commentaar
+label_comment_plural: Commentaar
+label_commits_per_author: Commits per auteur
+label_commits_per_month: Commits per maand
+label_confirmation: Bevestiging
+label_contains: bevat
+label_copied: gekopieerd
+label_copy_workflow_from: Kopieer workflow van
+label_current_status: Huidige status
+label_current_version: Huidige versie
+label_custom_field: Specifiek veld
+label_custom_field_new: Nieuw specifiek veld
+label_custom_field_plural: Specifieke velden
+label_date: Datum
+label_date_from: Van
+label_date_range: Datumbereik
+label_date_to: Tot
+label_day_plural: dagen
+label_default: Standaard
+label_default_columns: Standaard kolommen.
+label_deleted: verwijderd
+label_details: Details
+label_diff_inline: inline
+label_diff_side_by_side: naast elkaar
+label_disabled: uitgeschakeld
+label_display_per_page: 'Per pagina: %s'
label_document: Document
+label_document_added: Document toegevoegd
label_document_new: Nieuw document
label_document_plural: Documenten
-label_role: Rol
-label_role_plural: Rollen
-label_role_new: Nieuwe rol
-label_role_and_permissions: Rollen en permissies
-label_member: Lid
-label_member_new: Nieuw lid
-label_member_plural: Leden
-label_tracker: Tracker
-label_tracker_plural: Trackers
-label_tracker_new: Nieuwe tracker
-label_workflow: Workflow
-label_issue_status: Issue status
-label_issue_status_plural: Issue statussen
-label_issue_status_new: Nieuwe status
-label_issue_category: Issue categorie
-label_issue_category_plural: Issue categorieën
-label_issue_category_new: Nieuwe categorie
-label_custom_field: Specifiek veld
-label_custom_field_plural: Specifieke velden
-label_custom_field_new: Nieuw specifiek veld
-label_enumerations: Enumeraties
+label_download: %d Download
+label_download_plural: %d Downloads
+label_downloads_abbr: D/L
+label_duplicated_by: gedupliceerd door
+label_duplicates: dupliceert
+label_end_to_end: eind tot eind
+label_end_to_start: eind tot start
label_enumeration_new: Nieuwe waarde
+label_enumerations: Enumeraties
+label_environment: Omgeving
+label_equals: is gelijk
+label_example: Voorbeeld
+label_export_to: Exporteer naar
+label_f_hour: %.2f uur
+label_f_hour_plural: %.2f uren
+label_feed_plural: Feeds
+label_feeds_access_key_created_on: RSS toegangssleutel %s geleden gemaakt.
+label_file_added: Bericht toegevoegd
+label_file_plural: Bestanden
+label_filter_add: Voeg filter toe
+label_filter_plural: Filters
+label_float: Float
+label_follows: volgt op
+label_gantt: Gantt
+label_general: Algemeen
+label_generate_key: Genereer een sleutel
+label_help: Help
+label_history: Geschiedenis
+label_home: Home
+label_in: in
+label_in_less_than: in minder dan
+label_in_more_than: in meer dan
+label_incoming_emails: Inkomende e-mail
+label_index_by_date: Indexeer op datum
+label_index_by_title: Indexeer op titel
label_information: Informatie
label_information_plural: Informatie
-label_please_login: Inloggen a.u.b.
-label_register: Registreer
-label_password_lost: Wachtwoord verloren
-label_home: Home
-label_my_page: Mijn pagina
-label_my_account: Mijn account
-label_my_projects: Mijn projecten
-label_administration: Administratie
-label_login: Inloggen
-label_logout: Uitloggen
-label_help: Help
-label_reported_issues: Gemelde issues
-label_assigned_to_me_issues: Aan mij toegewezen issues
+label_integer: Integer
+label_internal: Intern
+label_issue: Issue
+label_issue_added: Issue toegevoegd
+label_issue_category: Issuecategorie
+label_issue_category_new: Nieuwe categorie
+label_issue_category_plural: Issuecategorieën
+label_issue_new: Nieuw issue
+label_issue_plural: Issues
+label_issue_status: Issuestatus
+label_issue_status_new: Nieuwe status
+label_issue_status_plural: Issue statussen
+label_issue_tracking: Issue-tracking
+label_issue_updated: Issue bijgewerkt
+label_issue_view_all: Bekijk alle issues
+label_issue_watchers: Monitoren
+label_issues_by: Issues door %s
+label_jump_to_a_project: Ga naar een project...
+label_language_based: Taal gebaseerd
+label_last_changes: laatste %d wijzigingen
label_last_login: Laatste bezoek
+label_last_month: laatste maand
+label_last_n_days: %d dagen geleden
label_last_updates: Laatste wijziging
label_last_updates_plural: %d laatste wijziging
-label_registered_on: Geregistreerd op
-label_activity: Activiteit
-label_new: Nieuw
-label_logged_as: Ingelogd als
-label_environment: Omgeving
-label_authentication: Authenticatie
-label_auth_source: Authenticatie modus
-label_auth_source_new: Nieuwe authenticatie modus
-label_auth_source_plural: Authenticatie modi
-label_subproject_plural: Subprojecten
-label_min_max_length: Min - Max lengte
+label_last_week: vorige week
+label_latest_revision: Meest recente revisie
+label_latest_revision_plural: Meest recente revisies
+label_ldap_authentication: LDAP authenticatie
+label_less_than_ago: minder dan x dagen geleden
label_list: Lijst
-label_date: Datum
-label_integer: Integer
-label_boolean: Boolean
-label_string: Tekst
-label_text: Lange tekst
-label_attribute: Attribuut
-label_attribute_plural: Attributen
-label_download: %d Download
-label_download_plural: %d Downloads
-label_no_data: Geen gegevens om te tonen
-label_change_status: Wijzig status
-label_history: Geschiedenis
-label_attachment: Bestand
-label_attachment_new: Nieuw bestand
-label_attachment_delete: Verwijder bestand
-label_attachment_plural: Bestanden
-label_report: Rapport
-label_report_plural: Rapporten
+label_loading: Laden...
+label_logged_as: Ingelogd als
+label_login: Inloggen
+label_logout: Uitloggen
+label_max_size: Maximumgrootte
+label_me: mij
+label_member: Lid
+label_member_new: Nieuw lid
+label_member_plural: Leden
+label_message_last: Laatste bericht
+label_message_new: Nieuw bericht
+label_message_plural: Berichten
+label_message_posted: Bericht toegevoegd
+label_min_max_length: Min-max lengte
+label_modification: %d wijziging
+label_modification_plural: %d wijzigingen
+label_modified: gewijzigd
+label_module_plural: Modules
+label_month: Maand
+label_months_from: maanden vanaf
+label_more: Meer
+label_more_than_ago: meer dan x dagen geleden
+label_my_account: Mijn account
+label_my_page: Mijn pagina
+label_my_projects: Mijn projecten
+label_new: Nieuw
+label_new_statuses_allowed: Nieuwe toegestane statussen
label_news: Nieuws
+label_news_added: Nieuws toegevoegd
+label_news_latest: Laatste nieuws
label_news_new: Voeg nieuws toe
label_news_plural: Nieuws
-label_news_latest: Laatste nieuws
label_news_view_all: Bekijk al het nieuws
-label_change_log: Wijzigingslog
-label_settings: Instellingen
-label_overview: Overzicht
-label_version: Versie
-label_version_new: Nieuwe versie
-label_version_plural: Versies
-label_confirmation: Bevestiging
-label_export_to: Exporteer naar
-label_read: Lees...
-label_public_projects: Publieke projecten
+label_next: Volgende
+label_no_change_option: (Geen wijziging)
+label_no_data: Geen gegevens om te tonen
+label_nobody: niemand
+label_none: geen
+label_not_contains: bevat niet
+label_not_equals: is niet gelijk
+label_on: 'van'
label_open_issues: open
label_open_issues_plural: open
-label_closed_issues: gesloten
-label_closed_issues_plural: gesloten
-label_total: Totaal
-label_permissions: Permissies
-label_current_status: Huidige status
-label_new_statuses_allowed: Nieuwe statuses toegestaan
-label_all: alle
-label_none: geen
-label_next: Volgende
-label_previous: Vorige
-label_used_by: Gebruikt door
-label_details: Details
-label_add_note: Voeg een notitie toe
+label_optional_description: Optionele beschrijving
+label_options: Opties
+label_overall_activity: Activiteit
+label_overview: Overzicht
+label_password_lost: Wachtwoord verloren
label_per_page: Per pagina
-label_calendar: Kalender
-label_months_from: maanden vanaf
-label_gantt: Gantt
-label_internal: Intern
-label_last_changes: laatste %d wijzigingen
-label_change_view_all: Bekijk alle wijzigingen
+label_permissions: Permissies
+label_permissions_report: Permissierapport
label_personalize_page: Personaliseer deze pagina
-label_comment: Commentaar
-label_comment_plural: Commentaar
-label_comment_add: Voeg commentaar toe
-label_comment_added: Commentaar toegevoegd
-label_comment_delete: Verwijder commentaar
+label_planning: Planning
+label_please_login: Log a.u.b. in
+label_plugins: Plugins
+label_precedes: gaat vooraf aan
+label_preferences: Voorkeuren
+label_preview: Voorbeeldweergave
+label_previous: Vorige
+label_project: Project
+label_project_all: Alle projecten
+label_project_latest: Nieuwste projecten
+label_project_new: Nieuw project
+label_project_plural: Projecten
+label_public_projects: Publieke projecten
label_query: Eigen zoekvraag
-label_query_plural: Eigen zoekvragen
label_query_new: Nieuwe zoekvraag
-label_filter_add: Voeg filter toe
-label_filter_plural: Filters
-label_equals: is gelijk
-label_not_equals: is niet gelijk
-label_in_less_than: in minder dan
-label_in_more_than: in meer dan
-label_in: in
-label_today: vandaag
-label_this_week: deze week
-label_less_than_ago: minder dan dagen geleden
-label_more_than_ago: meer dan dagen geleden
-label_ago: dagen geleden
-label_contains: bevat
-label_not_contains: bevat niet
-label_day_plural: dagen
+label_query_plural: Eigen zoekvragen
+label_read: Lees...
+label_register: Registreer
+label_registered_on: Geregistreerd op
+label_registration_activation_by_email: accountactivatie per e-mail
+label_registration_automatic_activation: automatische accountactivatie
+label_registration_manual_activation: handmatige accountactivatie
+label_related_issues: Gerelateerde issues
+label_relates_to: gerelateerd aan
+label_relation_delete: Verwijder relatie
+label_relation_new: Nieuwe relatie
+label_renamed: hernoemd
+label_reply_plural: Antwoorden
+label_report: Rapport
+label_report_plural: Rapporten
+label_reported_issues: Gemelde issues
label_repository: Repository
-label_browse: Blader
-label_modification: %d wijziging
-label_modification_plural: %d wijzigingen
+label_repository_plural: Repositories
+label_result_plural: Resultaten
+label_reverse_chronological_order: In omgekeerde chronologische volgorde
label_revision: Revisie
label_revision_plural: Revisies
-label_added: toegevoegd
-label_modified: gewijzigd
-label_deleted: verwijderd
-label_latest_revision: Meest recente revisie
-label_latest_revision_plural: Meest recente revisies
-label_view_revisions: Bekijk revisies
-label_max_size: Maximum grootte
-label_on: 'van'
-label_sort_highest: Verplaats naar begin
-label_sort_higher: Verplaats naar boven
-label_sort_lower: Verplaats naar beneden
-label_sort_lowest: Verplaats naar eind
label_roadmap: Roadmap
label_roadmap_due_in: Voldaan in %s
-label_roadmap_overdue: %s overtijd
label_roadmap_no_issues: Geen issues voor deze versie
+label_roadmap_overdue: %s over tijd
+label_role: Rol
+label_role_and_permissions: Rollen en permissies
+label_role_new: Nieuwe rol
+label_role_plural: Rollen
+label_scm: SCM
label_search: Zoeken
-label_result_plural: Resultaten
-label_all_words: Alle woorden
-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: Huidige versie
-label_preview: Testweergave
-label_feed_plural: Feeds
-label_changes_details: Details van alle wijzigingen
-label_issue_tracking: Issue tracking
+label_search_titles_only: Enkel titels doorzoeken
+label_send_information: Stuur accountinformatie naar de gebruiker
+label_send_test_email: Stuur een test e-mail
+label_settings: Instellingen
+label_show_completed_versions: Toon afgeronde versies
+label_sort_by: Sorteer op %s
+label_sort_higher: Verplaats naar boven
+label_sort_highest: Verplaats naar begin
+label_sort_lower: Verplaats naar beneden
+label_sort_lowest: Verplaats naar eind
label_spent_time: Gespendeerde tijd
-label_f_hour: %.2f uur
-label_f_hour_plural: %.2f uren
-label_time_tracking: Tijd tracking
-label_change_plural: Wijzigingen
-label_statistics: Statistieken
-label_commits_per_month: Commits per maand
-label_commits_per_author: Commits per auteur
-label_view_diff: Bekijk verschillen
-label_diff_inline: inline
-label_diff_side_by_side: naast elkaar
-label_options: Opties
-label_copy_workflow_from: Kopieer workflow van
-label_permissions_report: Permissies rapport
-label_watched_issues: Gemonitorde issues
-label_related_issues: Gerelateerde issues
-label_applied_status: Toegekende status
-label_loading: Laden...
-label_relation_new: Nieuwe relatie
-label_relation_delete: Verwijder relatie
-label_relates_to: gerelateerd aan
-label_duplicates: dupliceert
-label_blocks: blokkeert
-label_blocked_by: geblokkeerd door
-label_precedes: gaat vooraf aan
-label_follows: volgt op
-label_end_to_start: eind tot start
-label_end_to_end: eind tot eind
-label_start_to_start: start tot start
label_start_to_end: start tot eind
+label_start_to_start: start tot start
+label_statistics: Statistieken
label_stay_logged_in: Blijf ingelogd
-label_disabled: uitgeschakeld
-label_show_completed_versions: Toon afgeronde versies
-label_me: ik
-label_board: Forum
-label_board_new: Nieuw forum
-label_board_plural: Forums
-label_topic_plural: Onderwerpen
-label_message_plural: Berichten
-label_message_last: Laatste bericht
-label_message_new: Nieuw bericht
-label_reply_plural: Antwoorden
-label_send_information: Stuur account informatie naar de gebruiker
-label_year: Jaar
-label_month: Maand
-label_week: Week
-label_date_from: Van
-label_date_to: Tot
-label_language_based: Taal gebaseerd
-label_sort_by: Sorteer op %s
-label_send_test_email: Stuur een test e-mail
-label_feeds_access_key_created_on: RSS toegangssleutel %s geleden gemaakt.
-label_module_plural: Modules
-label_added_time_by: Toegevoegd door %s %s geleden
-label_updated_time: Upgedated %s geleden
-label_jump_to_a_project: Spring naar een project...
-
-button_login: Inloggen
-button_submit: Toevoegen
-button_save: Bewaren
-button_check_all: Selecteer alle
-button_uncheck_all: Deselecteer alle
-button_delete: Verwijder
-button_create: Maak
-button_test: Test
-button_edit: Bewerk
-button_add: Voeg toe
-button_change: Wijzig
-button_apply: Pas toe
-button_clear: Leeg maken
-button_lock: Sluit
-button_unlock: Open
-button_download: Download
-button_list: Lijst
-button_view: Bekijken
-button_move: Verplaatsen
-button_back: Terug
-button_cancel: Annuleer
-button_activate: Activeer
-button_sort: Sorteer
-button_log_time: Log tijd
-button_rollback: Rollback naar deze versie
-button_watch: Monitor
-button_unwatch: Niet meer monitoren
-button_reply: Antwoord
-button_archive: Archiveer
-button_unarchive: Desarchiveer
-button_reset: Reset
-button_rename: Hernoemen
-
-status_active: Actief
-status_registered: geregistreerd
-status_locked: gelockt
-
-text_select_mail_notifications: Selecteer acties waarvoor mededelingen via mail moeten worden verstuurd.
-text_regexp_info: bv. ^[A-Z0-9]+$
-text_min_max_length_info: 0 betekent geen restrictie
-text_project_destroy_confirmation: Weet U zeker dat U dit project en alle gerelateerde gegevens wilt verwijderen ?
-text_workflow_edit: Selecteer een rol en een tracker om de workflow te wijzigen
-text_are_you_sure: Weet U het zeker ?
-text_journal_changed: gewijzigd van %s naar %s
-text_journal_set_to: ingesteld op %s
-text_journal_deleted: verwijderd
-text_tip_task_begin_day: taak die op deze dag begint
-text_tip_task_end_day: taak die op deze dag eindigt
-text_tip_task_begin_end_day: taak die op deze dag begint en eindigt
-text_project_identifier_info: 'kleine letters (a-z), cijfers en liggende streepjes toegestaan.<br />Eenmaal bewaard kan de identificatiecode niet meer worden gewijzigd.'
-text_caracters_maximum: %d van maximum aantal tekens.
-text_length_between: Lengte tussen %d en %d tekens.
-text_tracker_no_workflow: Geen workflow gedefinieerd voor deze tracker
-text_unallowed_characters: Niet toegestane tekens
-text_coma_separated: Meerdere waarden toegestaan (door komma's gescheiden).
-text_issues_ref_in_commit_messages: Opzoeken en aanpassen van issues in commit berichten
-text_issue_added: Issue %s is gerapporteerd (by %s).
-text_issue_updated: Issue %s is gewijzigd (by %s).
-text_wiki_destroy_confirmation: Bent u zeker dat u deze wiki en zijn inhoud wenst te verwijderen?
-text_issue_category_destroy_question: Sommige issues (%d) zijn aan deze categorie toegewezen. Wat wilt u hiermee doen ?
-text_issue_category_destroy_assignments: Verwijder categorie toewijzigingen
-text_issue_category_reassign_to: Issues opnieuw toewijzen aan deze categorie
-
-default_role_manager: Manager
-default_role_developper: Ontwikkelaar
-default_role_reporter: Rapporteur
-default_tracker_bug: Bug
-default_tracker_feature: Feature
-default_tracker_support: Support
-default_issue_status_new: Nieuw
-default_issue_status_assigned: Toegewezen
-default_issue_status_resolved: Opgelost
-default_issue_status_feedback: Terugkoppeling
-default_issue_status_closed: Gesloten
-default_issue_status_rejected: Afgewezen
-default_doc_category_user: Gebruikersdocumentatie
-default_doc_category_tech: Technische documentatie
-default_priority_low: Laag
-default_priority_normal: Normaal
-default_priority_high: Hoog
-default_priority_urgent: Spoed
-default_priority_immediate: Onmiddellijk
-default_activity_design: Design
-default_activity_development: Development
-
-enumeration_issue_priorities: Issue prioriteiten
-enumeration_doc_categories: Document categorieën
-enumeration_activities: Activiteiten (tijd tracking)
-text_comma_separated: Meerdere waarden toegestaan (kommagescheiden).
-label_file_plural: Bestanden
-label_changeset_plural: Changesets
-field_column_names: Kolommen
-label_default_columns: Standaard kolommen.
-setting_issue_list_default_columns: Standaard kolommen getoond om de lijst met issues
-setting_repositories_encodings: Repositories encodings
-notice_no_issue_selected: "Er is geen issue geselecteerd. Selecteer de issue die u wilt bewerken."
-label_bulk_edit_selected_issues: Bewerk geselecteerde issues in bulk
-label_no_change_option: (Geen wijziging)
-notice_failed_to_save_issues: "Gefaald om %d issue(s) (%d geselecteerd) te bewaren: %s."
-
+label_string: Tekst
+label_subproject_plural: Subprojecten
+label_text: Lange tekst
label_theme: Thema
-label_default: Standaard
-label_search_titles_only: Enkel titels doorzoeken
-label_nobody: nobody
-button_change_password: Wijzig wachtwoord
-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_user_mail_option: "Bij niet-geselecteerde projecten zult u enkel notificaties ontvangen voor issues die u monitort of waar je bij betrokken bent (auteur of toegewezen persoon)."
-label_user_mail_option_selected: "Enkel bij elke event op het geselecteerde project..."
-label_user_mail_option_all: "Bij elk event in al mijn projecten..."
-label_user_mail_option_none: "Alleen in de dingen die ik monitor of in betrokken ben"
-setting_emails_footer: Emails footer
-label_float: Float
-button_copy: Kopieer
-mail_body_account_information_external: Je kan je account (%s) gebruiken om in te loggen.
-mail_body_account_information: Je account gegevens
-setting_protocol: Protocol
+label_this_month: deze maand
+label_this_week: deze week
+label_this_year: dit jaar
+label_time_tracking: Tijdregistratie bijhouden
+label_today: vandaag
+label_topic_plural: Onderwerpen
+label_total: Totaal
+label_tracker: Tracker
+label_tracker_new: Nieuwe tracker
+label_tracker_plural: Trackers
+label_updated_time: %s geleden bijgewerkt
+label_updated_time_by: %2$s geleden bijgewerkt door %1$s
+label_used_by: Gebruikt door
+label_user: Gebruiker
+label_user_activity: "%s's activiteit"
label_user_mail_no_self_notified: "Ik wil niet verwittigd worden van wijzigingen die ik zelf maak."
-setting_time_format: Tijd formaat
-label_registration_activation_by_email: account activatie per email
-mail_subject_account_activation_request: %s account activatie verzoek
+label_user_mail_option_all: "Bij elk gebeurtenis in al mijn projecten..."
+label_user_mail_option_none: "Alleen in de dingen die ik monitor of in betrokken ben"
+label_user_mail_option_selected: "Enkel bij elke gebeurtenis op het geselecteerde project..."
+label_user_new: Nieuwe gebruiker
+label_user_plural: Gebruikers
+label_version: Versie
+label_version_new: Nieuwe versie
+label_version_plural: Versies
+label_view_diff: Bekijk verschillen
+label_view_revisions: Bekijk revisies
+label_watched_issues: Gemonitorde issues
+label_week: Week
+label_wiki: Wiki
+label_wiki_edit: Wiki edit
+label_wiki_edit_plural: Wiki edits
+label_wiki_page: Wikipagina
+label_wiki_page_plural: Wikipagina's
+label_workflow: Workflow
+label_year: Jaar
+label_yesterday: gisteren
mail_body_account_activation_request: 'Een nieuwe gebruiker (%s) is geregistreerd. Zijn account wacht op uw akkoord:'
-label_registration_automatic_activation: automatische account activatie
-label_registration_manual_activation: manuele account activatie
-notice_account_pending: "Je account is aangemaakt maar wacht nog op goedkeuring van de beheerder."
-field_time_zone: Tijd zone
-text_caracters_minimum: Moet minstens %d karakters lang zijn.
-setting_bcc_recipients: Blind carbon copy ontvangers (bcc)
-button_annotate: Annotate
-label_issues_by: Issues door %s
-field_searchable: Doorzoekbaar
-label_display_per_page: 'Per pagina: %s'
-setting_per_page_options: Objects per pagina opties
-label_age: Leeftijd
+mail_body_account_information: Uw account gegevens
+mail_body_account_information_external: U kunt uw account (%s) gebruiken om in te loggen.
+mail_body_lost_password: 'Gebruik de volgende link om uw wachtwoord te wijzigen:'
+mail_body_register: 'Gebruik de volgende link om uw account te activeren:'
+mail_body_reminder: "%d issue(s) die aan u toegewezen zijn en voldaan moeten zijn in de komende %d dagen:"
+mail_subject_account_activation_request: %s accountactivatieverzoek
+mail_subject_lost_password: uw %s wachtwoord
+mail_subject_register: uw %s accountactivatie
+mail_subject_reminder: "%d issue(s) die voldaan moeten zijn in de komende dagen."
+notice_account_activated: uw account is geactiveerd. u kunt nu inloggen.
+notice_account_invalid_creditentials: Incorrecte gebruikersnaam of wachtwoord
+notice_account_lost_email_sent: Er is een e-mail naar u verstuurd met instructies over het kiezen van een nieuw wachtwoord.
+notice_account_password_updated: Wachtwoord is met succes gewijzigd
+notice_account_pending: "Uw account is aangemaakt, maar wacht nog op goedkeuring van de beheerder."
+notice_account_register_done: Account is met succes aangemaakt.
+notice_account_unknown_email: Onbekende gebruiker.
+notice_account_updated: Account is met succes gewijzigd
+notice_account_wrong_password: Incorrect wachtwoord
+notice_can_t_change_password: Dit account gebruikt een externe bron voor authenticatie. Het is niet mogelijk om het wachtwoord te veranderen.
notice_default_data_loaded: Standaard configuratie succesvol geladen.
-text_load_default_configuration: Laad de standaardconfiguratie
-text_no_configuration_data: "Rollen, trackers, issue statuses en workflow zijn nog niet geconfigureerd.\nHet is ten zeerste aangeraden om de standaardconfiguratie in te laden. Je kan deze aanpassen nadat deze is ingeladen."
-error_can_t_load_default_data: "Standaard configuratie kon niet worden geladen: %s"
-button_update: Update
-label_change_properties: Eigenschappen wijzigen
-label_general: Algemeen
-label_repository_plural: Repositories
-label_associated_revisions: Geassocieerde revisies
-setting_user_format: Gebruikers weergave formaat
-text_status_changed_by_changeset: Toegepast in changeset %s.
-label_more: Meer
-text_issues_destroy_confirmation: 'Ben je zeker dat je deze issue(s) wenst te verwijderen?'
-label_scm: SCM
-text_select_project_modules: 'Selecteer de modules die je wenst te gebruiken voor dit project:'
-label_issue_added: Issue toegevoegd
-label_issue_updated: Issue geupdated
-label_document_added: Document toegevoegd
-label_message_posted: Bericht toegevoegd
-label_file_added: Bericht toegevoegd
-label_news_added: Nieuws toegevoegd
-project_module_boards: Boards
-project_module_issue_tracking: Issue tracking
-project_module_wiki: Wiki
-project_module_files: Bestanden
+notice_email_error: Er is een fout opgetreden tijdens het versturen van (%s)
+notice_email_sent: Een e-mail werd verstuurd naar %s
+notice_failed_to_save_issues: "Fout bij bewaren van %d issue(s) (%d geselecteerd): %s."
+notice_feeds_access_key_reseted: Je RSS toegangssleutel werd gereset.
+notice_file_not_found: De pagina die u probeerde te benaderen bestaat niet of is verwijderd.
+notice_locking_conflict: De gegevens zijn gewijzigd door een andere gebruiker.
+notice_no_issue_selected: "Er is geen issue geselecteerd. Selecteer de issue die u wilt bewerken."
+notice_not_authorized: Het is u niet toegestaan deze pagina te raadplegen.
+notice_successful_connection: Verbinding succesvol.
+notice_successful_create: Succesvol aangemaakt.
+notice_successful_delete: Succesvol verwijderd.
+notice_successful_update: Wijzigen succesvol.
+notice_unable_delete_version: Niet mogelijk om deze versie te verwijderen.
+permission_add_issue_notes: Voeg notities toe
+permission_add_issue_watchers: Voeg monitors toe
+permission_add_issues: Voeg issues toe
+permission_add_messages: Voeg berichten toe
+permission_browse_repository: Repository doorbladeren
+permission_comment_news: Nieuws commentaar geven
+permission_commit_access: Commit toegang
+permission_delete_issues: Issues verwijderen
+permission_delete_messages: Berichten verwijderen
+permission_delete_own_messages: Eigen berichten verwijderen
+permission_delete_wiki_pages: Wiki pagina's verwijderen
+permission_delete_wiki_pages_attachments: Bijlagen verwijderen
+permission_edit_issue_notes: Notities bewerken
+permission_edit_issues: Issues bewerken
+permission_edit_messages: Berichten bewerken
+permission_edit_own_issue_notes: Eigen notities bewerken
+permission_edit_own_messages: Eigen berichten bewerken
+permission_edit_own_time_entries: Eigen tijdlogboek bewerken
+permission_edit_project: Project bewerken
+permission_edit_time_entries: Tijdlogboek bewerken
+permission_edit_wiki_pages: Wiki pagina's bewerken
+permission_log_time: Gespendeerde tijd loggen
+permission_manage_boards: Forums beheren
+permission_manage_categories: Issue-categorieën beheren
+permission_manage_documents: Documenten beheren
+permission_manage_files: Bestanden beheren
+permission_manage_issue_relations: Issuerelaties beheren
+permission_manage_members: Leden beheren
+permission_manage_news: Nieuws beheren
+permission_manage_public_queries: Publieke queries beheren
+permission_manage_repository: Repository beheren
+permission_manage_versions: Versiebeheer
+permission_manage_wiki: Wikibeheer
+permission_move_issues: Issues verplaatsen
+permission_protect_wiki_pages: Wikipagina's beschermen
+permission_rename_wiki_pages: Wikipagina's hernoemen
+permission_save_queries: Queries opslaan
+permission_select_project_modules: Project modules selecteren
+permission_view_calendar: Kalender bekijken
+permission_view_changesets: Changesets bekijken
+permission_view_documents: Documenten bekijken
+permission_view_files: Bestanden bekijken
+permission_view_gantt: Gantt grafiek bekijken
+permission_view_issue_watchers: Monitorlijst bekijken
+permission_view_messages: Berichten bekijken
+permission_view_time_entries: Gespendeerde tijd bekijken
+permission_view_wiki_edits: Wikihistorie bekijken
+permission_view_wiki_pages: Wikipagina's bekijken
+project_module_boards: Forums
project_module_documents: Documenten
-project_module_repository: Repository
+project_module_files: Bestanden
+project_module_issue_tracking: Issue tracking
project_module_news: Nieuws
+project_module_repository: Repository
project_module_time_tracking: Tijd tracking
-text_file_repository_writable: Bestandsrepository beschrijfbaar
-text_default_administrator_account_changed: Standaard beheerderaccount gewijzigd
-text_rmagick_available: RMagick beschikbaar (optioneel)
-button_configure: Configureer
-label_plugins: Plugins
-label_ldap_authentication: LDAP authenticatie
-label_downloads_abbr: D/L
-label_this_month: deze maand
-label_last_n_days: %d dagen geleden
-label_all_time: all time
-label_this_year: dit jaar
-label_date_range: Datum bereik
-label_last_week: vorige week
-label_yesterday: gisteren
-label_last_month: laatste maand
-label_add_another_file: Ander bestand toevoegen
-label_optional_description: Optionele beschrijving
-text_destroy_time_entries_question: %.02f uren werden gerapporteerd op de issue(s) die je wou verwijderen. Wat wil je doen?
-error_issue_not_found_in_project: 'Deze issue is niet gevonden of behoort niet toe tot dit project.'
-text_assign_time_entries_to_project: Geraporteerde uren toevoegen aan dit project
-text_destroy_time_entries: Verwijder geraporteerde uren
-text_reassign_time_entries: 'Gerapporteerde uren opnieuw toewijzen:'
+project_module_wiki: Wiki
setting_activity_days_default: Aantal dagen getoond bij het tabblad "Activiteit"
-label_chronological_order: In chronologische volgorde
-field_comments_sorting: Commentaar weergeven
-label_reverse_chronological_order: In omgekeerde chronologische volgorde
-label_preferences: Voorkeuren
-setting_display_subprojects_issues: Standaard issues van subproject tonen
-label_overall_activity: Activiteit
+setting_app_subtitle: Applicatieondertitel
+setting_app_title: Applicatietitel
+setting_attachment_max_size: Attachment max. grootte
+setting_autofetch_changesets: Haal commits automatisch op
+setting_autologin: Automatisch inloggen
+setting_bcc_recipients: Blind carbon copy ontvangers (bcc)
+setting_commit_fix_keywords: Gefixeerde trefwoorden
+setting_commit_logs_encoding: Encodering van commit berichten
+setting_commit_ref_keywords: Refererende trefwoorden
+setting_cross_project_issue_relations: Sta crossproject issuerelaties toe
+setting_date_format: Datumformaat
+setting_default_language: Standaard taal
setting_default_projects_public: Nieuwe projecten zijn standaard publiek
-error_scm_annotate: "Er kan geen commentaar toegevoegd worden."
-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_diff_max_lines_displayed: Max number of diff lines displayed
+setting_display_subprojects_issues: Standaard issues van subproject tonen
+setting_emails_footer: E-mails footer
+setting_enabled_scm: SCM ingeschakeld
+setting_feeds_limit: Feedinhoudlimiet
+setting_gravatar_enabled: Gebruik Gravatar gebruikersiconen
+setting_host_name: Hostnaam
+setting_issue_list_default_columns: Standaardkolommen getoond op de lijst met issues
+setting_issues_export_limit: Limiet export issues
+setting_login_required: Authenticatie vereist
+setting_mail_from: Afzender e-mail adres
setting_mail_handler_api_enabled: Schakel WS in voor inkomende mail.
setting_mail_handler_api_key: API sleutel
-text_email_delivery_not_configured: "Email bezorging is niet geconfigureerd. Notificaties zijn uitgeschakeld.\nConfigureer je SMTP server in config/email.yml en herstant de applicatie om dit in te schakelen."
-field_parent_title: Bovenliggende pagina
-label_issue_watchers: Monitoren
-setting_commit_logs_encoding: Commit messages encodering
-button_quote: Citaat
-setting_sequential_project_identifiers: Genereer sequentiele project identiteiten
-notice_unable_delete_version: Onmogelijk om deze versie te verwijderen.
-label_renamed: hernoemt
-label_copied: gekopieerd
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+setting_per_page_options: Objects per pagina-opties
+setting_plain_text_mail: platte tekst (geen HTML)
+setting_protocol: Protocol
+setting_repositories_encodings: Repositories coderingen
+setting_self_registration: Zelfregistratie toegestaan
+setting_sequential_project_identifiers: Genereer sequentiële projectidentiteiten
+setting_sys_api_enabled: Gebruik WS voor repository beheer
+setting_text_formatting: Tekstformaat
+setting_time_format: Tijd formaat
+setting_user_format: Gebruikers weergaveformaat
+setting_welcome_text: Welkomsttekst
+setting_wiki_compression: Wikigeschiedenis comprimeren
+status_active: actief
+status_locked: gelockt
+status_registered: geregistreerd
+text_are_you_sure: Weet u het zeker?
+text_assign_time_entries_to_project: Gerapporteerde uren toevoegen aan dit project
+text_caracters_maximum: %d van maximum aantal tekens.
+text_caracters_minimum: Moet minstens %d karakters lang zijn.
+text_comma_separated: Meerdere waarden toegestaan (kommagescheiden).
+text_default_administrator_account_changed: Standaard beheerderaccount gewijzigd
+text_destroy_time_entries: Verwijder gerapporteerde uren
+text_destroy_time_entries_question: %.02f uren werden gerapporteerd op de issue(s) die u wilde verwijderen. Wat wil u doen?
+text_diff_truncated: '... Deze diff werd afgekort omdat het de maximale weer te geven karakters overschreed.'
+text_email_delivery_not_configured: "E-mailbezorging is niet geconfigureerd. Notificaties zijn uitgeschakeld.\nConfigureer uw SMTP server in config/email.yml en herstart de applicatie om dit te activeren."
+text_enumeration_category_reassign_to: 'Wijs de volgende waarde toe:'
+text_enumeration_destroy_question: '%d objecten zijn toegewezen aan deze waarde.'
+text_file_repository_writable: Bestandsrepository beschrijfbaar
+text_issue_added: Issue %s is gerapporteerd (door %s).
+text_issue_category_destroy_assignments: Verwijder toewijzingen aan deze categorie
+text_issue_category_destroy_question: Er zijn issues (%d) aan deze categorie toegewezen. Wat wilt u hiermee doen ?
+text_issue_category_reassign_to: Issues opnieuw toewijzen aan deze categorie
+text_issue_updated: Issue %s is gewijzigd (door %s).
+text_issues_destroy_confirmation: 'Weet u zeker dat u deze issue(s) wil verwijderen?'
+text_issues_ref_in_commit_messages: Opzoeken en aanpassen van issues in commitberichten
+text_journal_changed: gewijzigd van %s naar %s
+text_journal_deleted: verwijderd
+text_journal_set_to: ingesteld op %s
+text_length_between: Lengte tussen %d en %d tekens.
+text_load_default_configuration: Laad de standaardconfiguratie
+text_min_max_length_info: 0 betekent geen restrictie
+text_no_configuration_data: "Rollen, trackers, issue statussen en workflows zijn nog niet geconfigureerd.\nHet is ten zeerste aangeraden om de standaard configuratie in te laden. U kunt deze aanpassen nadat deze is ingeladen."
+text_plugin_assets_writable: Plugin assets directory writable
+text_project_destroy_confirmation: Weet u zeker dat u dit project en alle gerelateerde gegevens wilt verwijderen?
+text_project_identifier_info: 'kleine letters (a-z), cijfers en liggende streepjes toegestaan.<br />Eenmaal bewaard kan de identificatiecode niet meer worden gewijzigd.'
+text_reassign_time_entries: 'Gerapporteerde uren opnieuw toewijzen:'
+text_regexp_info: bv. ^[A-Z0-9]+$
+text_repository_usernames_mapping: "Koppel de Redminegebruikers aan gebruikers in de repository log.\nGebruikers met dezelfde Redmine en repository gebruikersnaam of email worden automatisch gekoppeld."
+text_rmagick_available: RMagick beschikbaar (optioneel)
+text_select_mail_notifications: Selecteer acties waarvoor mededelingen via mail moeten worden verstuurd.
+text_select_project_modules: 'Selecteer de modules die u wilt gebruiken voor dit project:'
+text_status_changed_by_changeset: Toegepast in changeset %s.
+text_subprojects_destroy_warning: 'De subprojecten: %s zullen ook verwijderd worden.'
+text_tip_task_begin_day: taak die op deze dag begint
+text_tip_task_begin_end_day: taak die op deze dag begint en eindigt
+text_tip_task_end_day: taak die op deze dag eindigt
+text_tracker_no_workflow: Geen workflow gedefinieerd voor deze tracker
+text_unallowed_characters: Niet toegestane tekens
+text_user_mail_option: "Bij niet-geselecteerde projecten zult u enkel notificaties ontvangen voor issues die u monitort of waar u bij betrokken bent (als auteur of toegewezen persoon)."
+text_user_wrote: '%s schreef:'
+text_wiki_destroy_confirmation: Weet u zeker dat u deze wiki en zijn inhoud wenst te verwijderen?
+text_workflow_edit: Selecteer een rol en een tracker om de workflow te wijzigen
+warning_attachments_not_saved: "%d bestand(en) konden niet opgeslagen worden."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/no.yml b/lang/no.yml
index 9b3be9ae1..10bf48427 100644
--- a/lang/no.yml
+++ b/lang/no.yml
@@ -641,5 +641,70 @@ setting_sequential_project_identifiers: Generate sequential project identifiers
notice_unable_delete_version: Unable to delete version
label_renamed: renamed
label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+setting_plain_text_mail: plain text only (no HTML)
+permission_view_files: View files
+permission_edit_issues: Edit issues
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_public_queries: Manage public queries
+permission_add_issues: Add issues
+permission_log_time: Log spent time
+permission_view_changesets: View changesets
+permission_view_time_entries: View spent time
+permission_manage_versions: Manage versions
+permission_manage_wiki: Manage wiki
+permission_manage_categories: Manage issue categories
+permission_protect_wiki_pages: Protect wiki pages
+permission_comment_news: Comment news
+permission_delete_messages: Delete messages
+permission_select_project_modules: Select project modules
+permission_manage_documents: Manage documents
+permission_edit_wiki_pages: Edit wiki pages
+permission_add_issue_watchers: Add watchers
+permission_view_gantt: View gantt chart
+permission_move_issues: Move issues
+permission_manage_issue_relations: Manage issue relations
+permission_delete_wiki_pages: Delete wiki pages
+permission_manage_boards: Manage boards
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_view_wiki_edits: View wiki history
+permission_add_messages: Post messages
+permission_view_messages: View messages
+permission_manage_files: Manage files
+permission_edit_issue_notes: Edit notes
+permission_manage_news: Manage news
+permission_view_calendar: View calendrier
+permission_manage_members: Manage members
+permission_edit_messages: Edit messages
+permission_delete_issues: Delete issues
+permission_view_issue_watchers: View watchers list
+permission_manage_repository: Manage repository
+permission_commit_access: Commit access
+permission_browse_repository: Browse repository
+permission_view_documents: View documents
+permission_edit_project: Edit project
+permission_add_issue_notes: Add notes
+permission_save_queries: Save queries
+permission_view_wiki_pages: View wiki
+permission_rename_wiki_pages: Rename wiki pages
+permission_edit_time_entries: Edit time logs
+permission_edit_own_issue_notes: Edit own notes
+setting_gravatar_enabled: Use Gravatar user icons
+label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+permission_edit_own_messages: Edit own messages
+permission_delete_own_messages: Delete own messages
+label_user_activity: "%s's activity"
+label_updated_time_by: Updated by %s %s ago
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/pl.yml b/lang/pl.yml
index dc49dce4b..1ca3da32c 100644
--- a/lang/pl.yml
+++ b/lang/pl.yml
@@ -1,644 +1,728 @@
-_gloc_rule_default: '|n| n==1 ? "" : "_plural" '
-
+# Keep this line in order to avoid problems with Windows Notepad UTF-8 EF-BB-BFidea...
+# Best regards from Lublin@Poland :-)
+# PL translation by Mariusz@Olejnik.net,
+_gloc_rule_default: '|n| n10=n%10; n100=n%100; n==1 ? "" : n10>=2 && n10<=4 && (n100<10 || n100>=20) ? "_plural234" : "_plural5"'
actionview_datehelper_select_day_prefix:
actionview_datehelper_select_month_names: Styczeń,Luty,Marzec,Kwiecień,Maj,Czerwiec,Lipiec,Sierpień,Wrzesień,Październik,Listopad,Grudzień
actionview_datehelper_select_month_names_abbr: Sty,Lut,Mar,Kwi,Maj,Cze,Lip,Sie,Wrz,Paź,Lis,Gru
actionview_datehelper_select_month_prefix:
actionview_datehelper_select_year_prefix:
actionview_datehelper_time_in_words_day: 1 dzień
+actionview_datehelper_time_in_words_day_plural234: %d dni
+actionview_datehelper_time_in_words_day_plural5: %d dni
actionview_datehelper_time_in_words_day_plural: %d dni
actionview_datehelper_time_in_words_hour_about: około godziny
+actionview_datehelper_time_in_words_hour_about_plural234: około %d godzin
+actionview_datehelper_time_in_words_hour_about_plural5: około %d godzin
actionview_datehelper_time_in_words_hour_about_plural: około %d godzin
actionview_datehelper_time_in_words_hour_about_single: około godziny
actionview_datehelper_time_in_words_minute: 1 minuta
actionview_datehelper_time_in_words_minute_half: pół minuty
actionview_datehelper_time_in_words_minute_less_than: mniej niż minuta
+actionview_datehelper_time_in_words_minute_plural234: %d minuty
+actionview_datehelper_time_in_words_minute_plural5: %d minut
actionview_datehelper_time_in_words_minute_plural: %d minut
actionview_datehelper_time_in_words_minute_single: 1 minuta
actionview_datehelper_time_in_words_second_less_than: mniej niż sekunda
+actionview_datehelper_time_in_words_second_less_than_plural234: mniej niż %d sekundy
+actionview_datehelper_time_in_words_second_less_than_plural5: mniej niż %d sekund
actionview_datehelper_time_in_words_second_less_than_plural: mniej niż %d sekund
actionview_instancetag_blank_option: ProszÄ™ wybierz
-
-activerecord_error_inclusion: nie jest zawarte na liście
-activerecord_error_exclusion: jest zarezerwowane
-activerecord_error_invalid: jest nieprawidłowe
-activerecord_error_confirmation: nie pasuje do potwierdzenia
activerecord_error_accepted: musi być zaakceptowane
-activerecord_error_empty: nie może być puste
activerecord_error_blank: nie może być czyste
+activerecord_error_circular_dependency: Ta relacja może wytworzyć kołową zależność
+activerecord_error_confirmation: nie pasuje do potwierdzenia
+activerecord_error_empty: nie może być puste
+activerecord_error_exclusion: jest zarezerwowane
+activerecord_error_greater_than_start_date: musi być większe niż początkowa data
+activerecord_error_inclusion: nie jest zawarte na liście
+activerecord_error_invalid: jest nieprawidłowe
+activerecord_error_not_a_date: nie jest prawidłową datą
+activerecord_error_not_a_number: nie jest numerem
+activerecord_error_not_same_project: nie należy do tego samego projektu
+activerecord_error_taken: jest już wybrane
activerecord_error_too_long: jest za długie
activerecord_error_too_short: jest za krótkie
activerecord_error_wrong_length: ma złą długość
-activerecord_error_taken: jest już wybrane
-activerecord_error_not_a_number: nie jest numerem
-activerecord_error_not_a_date: nie jest prawidłową datą
-activerecord_error_greater_than_start_date: musi być większe niż początkowa data
-activerecord_error_not_same_project: nie należy do tego samego projektu
-activerecord_error_circular_dependency: Ta relacja może wytworzyć kołową zależność
-
-general_fmt_age: %d lat
-general_fmt_age_plural: %d lat
-general_fmt_date: %%m/%%d/%%Y
-general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p
-general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
-general_fmt_time: %%I:%%M %%p
-general_text_No: 'Nie'
-general_text_Yes: 'Tak'
-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
-general_first_day_of_week: '1'
-
-notice_account_updated: Konto prawidłowo zaktualizowane.
-notice_account_invalid_creditentials: Zły użytkownik lub hasło
-notice_account_password_updated: Hasło prawidłowo zmienione.
-notice_account_wrong_password: Złe hasło
-notice_account_register_done: Konto prawidłowo stworzone.
-notice_account_unknown_email: Nieznany użytkownik.
-notice_can_t_change_password: To konto ma zewnętrzne źródło identyfikacji. Nie możesz zmienić hasła.
-notice_account_lost_email_sent: Email z instrukcjami zmiany hasła został wysłany do Ciebie.
-notice_account_activated: Twoje konto zostało aktywowane. Możesz się zalogować.
-notice_successful_create: Udane stworzenie.
-notice_successful_update: Udane poprawienie.
-notice_successful_delete: Udane usunięcie.
-notice_successful_connection: Udane nawiązanie połączenia.
-notice_file_not_found: Strona do której próbujesz się dostać nie istnieje lub została usunięta.
-notice_locking_conflict: Dane poprawione przez innego użytkownika.
-notice_not_authorized: Nie jesteś autoryzowany by zobaczyć stronę.
-
+button_activate: Aktywuj
+button_add: Dodaj
+button_annotate: Adnotuj
+button_apply: Ustaw
+button_archive: Archiwizuj
+button_back: Wstecz
+button_cancel: Anuluj
+button_change: Zmień
+button_change_password: Zmień hasło
+button_check_all: Zaznacz wszystko
+button_clear: Wyczyść
+button_configure: Konfiguruj
+button_copy: Kopia
+button_create: Stwórz
+button_delete: Usuń
+button_download: Pobierz
+button_edit: Edytuj
+button_list: Lista
+button_lock: Zablokuj
+button_log_time: Log czasu
+button_login: Login
+button_move: PrzenieÅ›
+button_quote: Cytuj
+button_rename: Zmień nazwę
+button_reply: Odpowiedz
+button_reset: Resetuj
+button_rollback: Przywróc do tej wersji
+button_save: Zapisz
+button_sort: Sortuj
+button_submit: Wyślij
+button_test: Testuj
+button_unarchive: Przywróc z archiwum
+button_uncheck_all: Odznacz wszystko
+button_unlock: Odblokuj
+button_unwatch: Nie obserwuj
+button_update: Uaktualnij
+button_view: Pokaż
+button_watch: Obserwuj
+default_activity_design: Projektowanie
+default_activity_development: Rozwój
+default_doc_category_tech: Dokumentacja techniczna
+default_doc_category_user: Dokumentacja użytkownika
+default_issue_status_assigned: Przypisany
+default_issue_status_closed: Zamknięty
+default_issue_status_feedback: Odpowiedź
+default_issue_status_new: Nowy
+default_issue_status_rejected: Odrzucony
+default_issue_status_resolved: RozwiÄ…zany
+default_priority_high: Wysoki
+default_priority_immediate: Natychmiastowy
+default_priority_low: Niski
+default_priority_normal: Normalny
+default_priority_urgent: Pilny
+default_role_developper: Programista
+default_role_manager: Kierownik
+default_role_reporter: Wprowadzajacy
+default_tracker_bug: Błąd
+default_tracker_feature: Zadanie
+default_tracker_support: Wsparcie
+enumeration_activities: Działania (śledzenie czasu)
+enumeration_doc_categories: Kategorie dokumentów
+enumeration_issue_priorities: Priorytety zagadnień
+error_can_t_load_default_data: "Domyślna konfiguracja nie może być załadowana: %s"
+error_issue_not_found_in_project: 'Zaganienie nie zostało znalezione lub nie należy do tego projektu'
+error_scm_annotate: "Wpis nie istnieje lub nie można do niego dodawać adnotacji."
+error_scm_command_failed: "Wystąpił błąd przy próbie dostępu do repozytorium: %s"
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
-mail_body_lost_password: 'W celu zmiany swojego hasła użyj poniższego odnośnika:'
-mail_subject_register: Aktywacja konta w %s
-mail_body_register: 'W celu aktywacji Twojego konta, użyj poniższego odnośnika:'
-
-gui_validation_error: 1 błąd
-gui_validation_error_plural: %d błędów
-
-field_name: Nazwa
-field_description: Opis
-field_summary: Podsumowanie
-field_is_required: Wymagane
-field_firstname: ImiÄ™
-field_lastname: Nazwisko
-field_mail: Email
-field_filename: Plik
-field_filesize: Rozmiar
-field_downloads: Pobrań
+field_account: Konto
+field_activity: Aktywność
+field_admin: Administrator
+field_assignable: Zagadnienia mogą być przypisane do tej roli
+field_assigned_to: Przydzielony do
+field_attr_firstname: ImiÄ™ atrybut
+field_attr_lastname: Nazwisko atrybut
+field_attr_login: Login atrybut
+field_attr_mail: Email atrybut
+field_auth_source: Tryb identyfikacji
field_author: Autor
-field_created_on: Stworzone
-field_updated_on: Zmienione
-field_field_format: Format
-field_is_for_all: Dla wszystkich projektów
-field_possible_values: Możliwe wartości
-field_regexp: Wyrażenie regularne
-field_min_length: Minimalna długość
-field_max_length: Maksymalna długość
-field_value: Wartość
+field_base_dn: Base DN
field_category: Kategoria
-field_title: Tytuł
-field_project: Projekt
-field_issue: Zagadnienie
-field_status: Status
-field_notes: Notatki
-field_is_closed: Zagadnienie zamknięte
-field_is_default: Domyślny status
-field_tracker: Typ zagadnienia
-field_subject: Temat
+field_column_names: Nazwy kolumn
+field_comments: Komentarz
+field_comments_sorting: Pokazuj komentarze
+field_created_on: Stworzone
+field_default_value: Domyślny
+field_delay: Opóźnienie
+field_description: Opis
+field_done_ratio: %% Wykonane
+field_downloads: Pobrań
field_due_date: Data oddania
-field_assigned_to: Przydzielony do
-field_priority: Priorytet
+field_effective_date: Data
+field_estimated_hours: Szacowany czas
+field_field_format: Format
+field_filename: Plik
+field_filesize: Rozmiar
+field_firstname: ImiÄ™
field_fixed_version: Wersja docelowa
-field_user: Użytkownik
-field_role: Rola
+field_hide_mail: Ukryj mój adres email
field_homepage: Strona www
-field_is_public: Publiczny
-field_parent: Nadprojekt
+field_host: Host
+field_hours: Godzin
+field_identifier: Identifikator
+field_is_closed: Zagadnienie zamknięte
+field_is_default: Domyślny status
+field_is_filter: Atrybut filtrowania
+field_is_for_all: Dla wszystkich projektów
field_is_in_chlog: Zagadnienie pokazywane w zapisie zmian
field_is_in_roadmap: Zagadnienie pokazywane na mapie
+field_is_public: Publiczny
+field_is_required: Wymagane
+field_issue: Zagadnienie
+field_issue_to_id: PowiÄ…zania zagadnienia
+field_language: Język
+field_last_login_on: Ostatnie połączenie
+field_lastname: Nazwisko
field_login: Login
+field_mail: Email
field_mail_notification: Powiadomienia Email
-field_admin: Administrator
-field_last_login_on: Ostatnie połączenie
-field_language: Język
-field_effective_date: Data
-field_password: Hasło
+field_max_length: Maksymalna długość
+field_min_length: Minimalna długość
+field_name: Nazwa
field_new_password: Nowe hasło
+field_notes: Notatki
+field_onthefly: Tworzenie użytkownika w locie
+field_parent: Nadprojekt
+field_parent_title: Strona rodzica
+field_password: Hasło
field_password_confirmation: Potwierdzenie
-field_version: Wersja
-field_type: Typ
-field_host: Host
field_port: Port
-field_account: Konto
-field_base_dn: Base DN
-field_attr_login: Login atrybut
-field_attr_firstname: ImiÄ™ atrybut
-field_attr_lastname: Nazwisko atrybut
-field_attr_mail: Email atrybut
-field_onthefly: Tworzenie użytkownika w locie
+field_possible_values: Możliwe wartości
+field_priority: Priorytet
+field_project: Projekt
+field_redirect_existing_links: Przekierowanie istniejących odnośników
+field_regexp: Wyrażenie regularne
+field_role: Rola
+field_searchable: Przeszukiwalne
+field_spent_on: Data
field_start_date: Start
-field_done_ratio: %% Wykonane
-field_auth_source: Tryb identyfikacji
-field_hide_mail: Ukryj mój adres email
-field_comments: Komentarz
-field_url: URL
field_start_page: Strona startowa
+field_status: Status
+field_subject: Temat
field_subproject: Podprojekt
-field_hours: Godzin
-field_activity: Aktywność
-field_spent_on: Data
-field_identifier: Identifikator
-field_is_filter: Atrybut filtrowania
-field_issue_to_id: PowiÄ…zania zagadnienia
-field_delay: Opóźnienie
-field_default_value: Domyślny
-
-setting_app_title: Tytuł aplikacji
-setting_app_subtitle: Podtytuł aplikacji
-setting_welcome_text: Tekst powitalny
-setting_default_language: Domyślny język
-setting_login_required: Identyfikacja wymagana
-setting_self_registration: Własna rejestracja umożliwiona
-setting_attachment_max_size: Maks. rozm. załącznika
-setting_issues_export_limit: Limit eksportu zagadnień
-setting_mail_from: Adres email wysyłki
-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: Automatyczne pobieranie zmian
-setting_sys_api_enabled: Włączenie WS do zarządzania repozytorium
-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
-
-label_user: Użytkownik
-label_user_plural: Użytkownicy
-label_user_new: Nowy użytkownik
-label_project: Projekt
-label_project_new: Nowy projekt
-label_project_plural: Projekty
-label_project_all: Wszystkie projekty
-label_project_latest: Ostatnie projekty
-label_issue: Zagadnienie
-label_issue_new: Nowe zagadnienie
-label_issue_plural: Zagadnienia
-label_issue_view_all: Zobacz wszystkie zagadnienia
-label_document: Dokument
-label_document_new: Nowy dokument
-label_document_plural: Dokumenty
-label_role: Rola
-label_role_plural: Role
-label_role_new: Nowa rola
-label_role_and_permissions: Role i Uprawnienia
-label_member: Uczestnik
-label_member_new: Nowy uczestnik
-label_member_plural: Uczestnicy
-label_tracker: Typ zagadnienia
-label_tracker_plural: Typy zagadnień
-label_tracker_new: Nowy typ zagadnienia
-label_workflow: Przepływ
-label_issue_status: Status zagadnienia
-label_issue_status_plural: Statusy zagadnień
-label_issue_status_new: Nowy status
-label_issue_category: Kategoria zagadnienia
-label_issue_category_plural: Kategorie zagadnień
-label_issue_category_new: Nowa kategoria
-label_custom_field: Dowolne pole
-label_custom_field_plural: Dowolne pola
-label_custom_field_new: Nowe dowolne pole
-label_enumerations: Wyliczenia
-label_enumeration_new: Nowa wartość
-label_information: Informacja
-label_information_plural: Informacje
-label_please_login: Zaloguj siÄ™
-label_register: Rejestracja
-label_password_lost: Zapomniane hasło
-label_home: Główna
-label_my_page: Moja strona
-label_my_account: Moje konto
-label_my_projects: Moje projekty
+field_summary: Podsumowanie
+field_time_zone: Strefa czasowa
+field_title: Tytuł
+field_tracker: Typ zagadnienia
+field_type: Typ
+field_updated_on: Zmienione
+field_url: URL
+field_user: Użytkownik
+field_value: Wartość
+field_version: Wersja
+field_vf_personnel: Personel
+field_vf_watcher: Obserwator
+general_csv_decimal_separator: '.'
+general_csv_encoding: UTF-8
+general_csv_separator: ','
+general_day_names: Poniedziałek,Wtorek,Środa,Czwartek,Piątek,Sobota,Niedziela
+general_first_day_of_week: '1'
+general_fmt_age: %d rok
+general_fmt_age_plural234: %d lata
+general_fmt_age_plural5: %d lat
+general_fmt_age_plural: %d lat
+general_fmt_date: %%m/%%d/%%Y
+general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p
+general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
+general_fmt_time: %%I:%%M %%p
+general_lang_name: 'Polski'
+general_pdf_encoding: UTF-8
+general_text_No: 'Nie'
+general_text_Yes: 'Tak'
+general_text_no: 'nie'
+general_text_yes: 'tak'
+gui_validation_error: 1 błąd
+gui_validation_error_plural234: %d błędy
+gui_validation_error_plural5: %d błędów
+gui_validation_error_plural: %d błędów
+label_activity: Aktywność
+label_add_another_file: Dodaj kolejny plik
+label_add_note: Dodaj notatkÄ™
+label_added: dodane
+label_added_time_by: Dodane przez %s %s temu
label_administration: Administracja
-label_login: Login
-label_logout: Wylogowanie
-label_help: Pomoc
-label_reported_issues: Wprowadzone zagadnienia
+label_age: Wiek
+label_ago: dni temu
+label_all: wszystko
+label_all_time: cały czas
+label_all_words: Wszystkie słowa
+label_and_its_subprojects: %s i podprojekty
+label_applied_status: Stosowany status
label_assigned_to_me_issues: Zagadnienia przypisane do mnie
-label_last_login: Ostatnie połączenie
-label_last_updates: Ostatnia zmieniana
-label_last_updates_plural: %d ostatnie zmiany
-label_registered_on: Zarejestrowany
-label_activity: Aktywność
-label_new: Nowy
-label_logged_as: Zalogowany jako
-label_environment: Åšrodowisko
-label_authentication: Identyfikacja
+label_associated_revisions: Skojarzone rewizje
+label_attachment: Plik
+label_attachment_delete: Usuń plik
+label_attachment_new: Nowy plik
+label_attachment_plural: Pliki
+label_attribute: Atrybut
+label_attribute_plural: Atrybuty
label_auth_source: Tryb identyfikacji
label_auth_source_new: Nowy tryb identyfikacji
label_auth_source_plural: Tryby identyfikacji
-label_subproject_plural: Podprojekty
-label_min_max_length: Min - Maks długość
-label_list: Lista
-label_date: Data
-label_integer: Liczba całkowita
+label_authentication: Identyfikacja
+label_blocked_by: zablokowane przez
+label_blocks: blokady
+label_board: Forum
+label_board_new: Nowe forum
+label_board_plural: Fora
label_boolean: Wartość logiczna
-label_string: Tekst
-label_text: Długi tekst
-label_attribute: Atrybut
-label_attribute_plural: Atrybuty
-label_download: %d Pobranie
-label_download_plural: %d Pobrania
-label_no_data: Brak danych do pokazania
-label_change_status: Status zmian
-label_history: Historia
-label_attachment: Plik
-label_attachment_new: Nowy plik
-label_attachment_delete: Skasuj plik
-label_attachment_plural: Pliki
-label_report: Raport
-label_report_plural: Raporty
-label_news: Wiadomość
-label_news_new: Dodaj wiadomość
-label_news_plural: Wiadomości
-label_news_latest: Ostatnie wiadomości
-label_news_view_all: Pokaż wszystkie wiadomości
+label_browse: PrzeglÄ…d
+label_bulk_edit_selected_issues: Zbiorowa edycja zagadnień
+label_calendar: Kalendarz
label_change_log: Lista zmian
-label_settings: Ustawienia
-label_overview: PrzeglÄ…d
-label_version: Wersja
-label_version_new: Nowa wersja
-label_version_plural: Wersje
-label_confirmation: Potwierdzenie
-label_export_to: Eksportuj do
-label_read: Czytanie...
-label_public_projects: Projekty publiczne
-label_open_issues: otwarte
-label_open_issues_plural: otwarte
+label_change_plural: Zmiany
+label_change_properties: Zmień właściwości
+label_change_status: Status zmian
+label_change_view_all: Pokaż wszystkie zmiany
+label_changes_details: Szczegóły wszystkich zmian
+label_changeset_plural: Zestawienia zmian
+label_chronological_order: W kolejności chronologicznej
label_closed_issues: zamknięte
+label_closed_issues_plural234: zamknięte
+label_closed_issues_plural5: zamknięte
label_closed_issues_plural: zamknięte
-label_total: Ogółem
-label_permissions: Uprawnienia
-label_current_status: Obecny status
-label_new_statuses_allowed: Uprawnione nowe statusy
-label_all: wszystko
-label_none: brak
-label_next: Następne
-label_previous: Poprzednie
-label_used_by: Używane przez
-label_details: Szczegóły
-label_add_note: Dodaj notatkÄ™
-label_per_page: Na stronÄ™
-label_calendar: Kalendarz
-label_months_from: miesiÄ…ce od
-label_gantt: Gantt
-label_internal: Wewnętrzny
-label_last_changes: ostatnie %d zmian
-label_change_view_all: Pokaż wszystkie zmiany
-label_personalize_page: Personalizuj tÄ… stronÄ™
label_comment: Komentarz
-label_comment_plural: Komentarze
label_comment_add: Dodaj komentarz
label_comment_added: Komentarz dodany
label_comment_delete: Usuń komentarze
-label_query: Dowolne zapytanie
-label_query_plural: Dowolne zapytania
-label_query_new: Nowe zapytanie
+label_comment_plural234: Komentarze
+label_comment_plural5: Komentarze
+label_comment_plural: Komentarze
+label_commits_per_author: Zatwierdzenia według autorów
+label_commits_per_month: Zatwierdzenia według miesięcy
+label_confirmation: Potwierdzenie
+label_contains: zawiera
+label_copied: skopiowano
+label_copy_workflow_from: Kopiuj przepływ z
+label_current_status: Obecny status
+label_current_version: Obecna wersja
+label_custom_field: Dowolne pole
+label_custom_field_new: Nowe dowolne pole
+label_custom_field_plural: Dowolne pola
+label_date: Data
+label_date_from: Z
+label_date_range: Zakres datowy
+label_date_to: Do
+label_day_plural: dni
+label_default: Domyślne
+label_default_columns: Domyślne kolumny
+label_deleted: usunięte
+label_details: Szczegóły
+label_diff_inline: w linii
+label_diff_side_by_side: obok siebie
+label_disabled: zablokowany
+label_display_per_page: 'Na stronÄ™: %s'
+label_document: Dokument
+label_document_added: Dodano dokument
+label_document_new: Nowy dokument
+label_document_plural: Dokumenty
+label_download: %d Pobranie
+label_download_plural234: %d Pobrania
+label_download_plural5: %d Pobrań
+label_download_plural: %d Pobrania
+label_downloads_abbr: Pobieranie
+label_duplicated_by: zdublikowane przez
+label_duplicates: duplikaty
+label_end_to_end: koniec do końca
+label_end_to_start: koniec do poczÄ…tku
+label_enumeration_new: Nowa wartość
+label_enumerations: Wyliczenia
+label_environment: Åšrodowisko
+label_equals: równa się
+label_example: Przykład
+label_export_to: Eksportuj do
+label_f_hour: %.2f godzina
+label_f_hour_plural234: %.2f godziny
+label_f_hour_plural5: %.2f godzin
+label_f_hour_plural: %.2f godzin
+label_feed_plural: Ilość RSS
+label_feeds_access_key_created_on: Klucz dostępu RSS stworzony %s dni temu
+label_file_added: Dodano plik
+label_file_plural: Pliki
label_filter_add: Dodaj filtr
label_filter_plural: Filtry
-label_equals: jest
-label_not_equals: nie jest
-label_in_less_than: w mniejszych od
-label_in_more_than: w większych niż
+label_float: Liczba rzeczywista
+label_follows: następuje po
+label_gantt: Gantt
+label_general: Ogólne
+label_generate_key: Wygeneruj klucz
+label_help: Pomoc
+label_history: Historia
+label_home: Główna
label_in: w
-label_today: dzisiaj
+label_in_less_than: mniejsze niż
+label_in_more_than: większe niż
+label_incoming_emails: PrzychodzÄ…ca poczta elektroniczna
+label_index_by_date: Index by date
+label_index_by_title: Indeks
+label_information: Informacja
+label_information_plural: Informacje
+label_integer: Liczba całkowita
+label_internal: Wewnętrzny
+label_issue: Zagadnienie
+label_issue_added: Dodano zagadnienie
+label_issue_category: Kategoria zagadnienia
+label_issue_category_new: Nowa kategoria
+label_issue_category_plural: Kategorie zagadnień
+label_issue_new: Nowe zagadnienie
+label_issue_plural: Zagadnienia
+label_issue_status: Status zagadnienia
+label_issue_status_new: Nowy status
+label_issue_status_plural: Statusy zagadnień
+label_issue_tracking: Śledzenie zagadnień
+label_issue_updated: Uaktualniono zagadnienie
+label_issue_view_all: Zobacz wszystkie zagadnienia
+label_issue_watchers: Obserwatorzy
+label_issues_by: Zagadnienia wprowadzone przez %s
+label_jump_to_a_project: Skocz do projektu...
+label_language_based: Na podstawie języka
+label_last_changes: ostatnie %d zmian
+label_last_login: Ostatnie połączenie
+label_last_month: ostatni miesiÄ…c
+label_last_n_days: ostatnie %d dni
+label_last_updates: Ostatnia zmiana
+label_last_updates_plural234: %d ostatnie zmiany
+label_last_updates_plural5: %d ostatnich zmian
+label_last_updates_plural: %d ostatnie zmiany
+label_last_week: ostatni tydzień
+label_latest_revision: Najnowsza rewizja
+label_latest_revision_plural: Najnowsze rewizje
+label_ldap_authentication: Autoryzacja LDAP
label_less_than_ago: dni mniej
+label_list: Lista
+label_loading: Åadowanie...
+label_logged_as: Zalogowany jako
+label_login: Login
+label_logout: Wylogowanie
+label_max_size: Maksymalny rozmiar
+label_me: ja
+label_member: Uczestnik
+label_member_new: Nowy uczestnik
+label_member_plural: Uczestnicy
+label_message_last: Ostatnia wiadomość
+label_message_new: Nowa wiadomość
+label_message_plural: Wiadomości
+label_message_posted: Dodano wiadomość
+label_min_max_length: Min - Maks długość
+label_modification: %d modyfikacja
+label_modification_plural234: %d modyfikacje
+label_modification_plural5: %d modyfikacji
+label_modification_plural: %d modyfikacje
+label_modified: zmodyfikowane
+label_module_plural: Moduły
+label_month: MiesiÄ…c
+label_months_from: miesiÄ…ce od
+label_more: Więcej
label_more_than_ago: dni więcej
-label_ago: dni temu
-label_contains: zawiera
+label_my_account: Moje konto
+label_my_page: Moja strona
+label_my_projects: Moje projekty
+label_new: Nowy
+label_new_statuses_allowed: Uprawnione nowe statusy
+label_news: Komunikat
+label_news_added: Dodano komunikat
+label_news_latest: Ostatnie komunikaty
+label_news_new: Dodaj komunikat
+label_news_plural: Komunikaty
+label_news_view_all: Pokaż wszystkie komunikaty
+label_next: Następne
+label_no_change_option: (Bez zmian)
+label_no_data: Brak danych do pokazania
+label_nobody: nikt
+label_none: brak
label_not_contains: nie zawiera
-label_day_plural: dni
+label_not_equals: różni się
+label_on: 'z'
+label_open_issues: otwarte
+label_open_issues_plural234: otwarte
+label_open_issues_plural5: otwarte
+label_open_issues_plural: otwarte
+label_optional_description: Opcjonalny opis
+label_options: Opcje
+label_overall_activity: Ogólna aktywność
+label_overview: PrzeglÄ…d
+label_password_lost: Zapomniane hasło
+label_per_page: Na stronÄ™
+label_permissions: Uprawnienia
+label_permissions_report: Raport uprawnień
+label_personalize_page: Personalizuj tÄ… stronÄ™
+label_planning: Planowanie
+label_please_login: Zaloguj siÄ™
+label_plugins: Wtyczki
+label_precedes: poprzedza
+label_preferences: Preferencje
+label_preview: PodglÄ…d
+label_previous: Poprzednie
+label_project: Projekt
+label_project_all: Wszystkie projekty
+label_project_latest: Ostatnie projekty
+label_project_new: Nowy projekt
+label_project_plural234: Projekty
+label_project_plural5: Projekty
+label_project_plural: Projekty
+label_public_projects: Projekty publiczne
+label_query: Kwerenda
+label_query_new: Nowa kwerenda
+label_query_plural: Kwerendy
+label_read: Czytanie...
+label_register: Rejestracja
+label_registered_on: Zarejestrowany
+label_registration_activation_by_email: aktywacja konta przez e-mail
+label_registration_automatic_activation: automatyczna aktywacja kont
+label_registration_manual_activation: manualna aktywacja kont
+label_related_issues: PowiÄ…zane zagadnienia
+label_relates_to: powiÄ…zane z
+label_relation_delete: Usuń powiązanie
+label_relation_new: Nowe powiÄ…zanie
+label_renamed: przemianowano
+label_reply_plural: Odpowiedzi
+label_report: Raport
+label_report_plural: Raporty
+label_reported_issues: Wprowadzone zagadnienia
label_repository: Repozytorium
-label_browse: PrzeglÄ…d
-label_modification: %d modyfikacja
-label_modification_plural: %d modyfikacja
+label_repository_plural: Repozytoria
+label_result_plural: Rezultatów
+label_reverse_chronological_order: W kolejności odwrotnej do chronologicznej
label_revision: Rewizja
label_revision_plural: Rewizje
-label_added: dodane
-label_modified: zmodyfikowane
-label_deleted: usunięte
-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ę
-label_sort_higher: Do góry
-label_sort_lower: Do dołu
-label_sort_lowest: Przesuń na dół
label_roadmap: Mapa
-label_roadmap_due_in: W czasie %s
+label_roadmap_due_in: W czasie
label_roadmap_no_issues: Brak zagadnień do tej wersji
+label_roadmap_overdue: %s spóźnienia
+label_role: Rola
+label_role_and_permissions: Role i Uprawnienia
+label_role_new: Nowa rola
+label_role_plural: Role
+label_scm: SCM
label_search: Szukaj
-label_result_plural: Rezultatów
-label_all_words: Wszystkie słowa
-label_wiki: Wiki
-label_wiki_edit: Edycja wiki
-label_wiki_edit_plural: Edycje wiki
-label_wiki_page: Strona wiki
-label_wiki_page_plural: Strony wiki
-label_index_by_title: Indeks
-label_index_by_date: Index by date
-label_current_version: Obecna wersja
-label_preview: PodglÄ…d
-label_feed_plural: Ilość RSS
-label_changes_details: Szczegóły wszystkich zmian
-label_issue_tracking: Śledzenie zagadnień
+label_search_titles_only: Przeszukuj tylko tytuły
+label_send_information: Wyślij informację użytkownikowi
+label_send_test_email: Wyślij próbny email
+label_settings: Ustawienia
+label_show_completed_versions: Pokaż kompletne wersje
+label_sort_by: Sortuj po %s
+label_sort_higher: Do góry
+label_sort_highest: Przesuń na górę
+label_sort_lower: Do dołu
+label_sort_lowest: Przesuń na dół
label_spent_time: Spędzony czas
-label_f_hour: %.2f godzina
-label_f_hour_plural: %.2f godzin
-label_time_tracking: Åšledzenie czasu
-label_change_plural: Zmiany
-label_statistics: Statystyki
-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
-label_options: Opcje
-label_copy_workflow_from: Kopiuj przepływ z
-label_permissions_report: Raport uprawnień
-label_watched_issues: Obserwowane zagadnienia
-label_related_issues: PowiÄ…zane zagadnienia
-label_applied_status: Stosowany status
-label_loading: Åadowanie...
-label_relation_new: Nowe powiÄ…zanie
-label_relation_delete: Usuń powiązanie
-label_relates_to: powiÄ…zane z
-label_duplicates: duplikaty
-label_blocks: blokady
-label_blocked_by: zablokowane przez
-label_precedes: poprzedza
-label_follows: podąża
-label_end_to_start: koniec do poczÄ…tku
-label_end_to_end: koniec do końca
-label_start_to_start: poczÄ…tek do poczÄ…tku
label_start_to_end: początek do końca
+label_start_to_start: poczÄ…tek do poczÄ…tku
+label_statistics: Statystyki
label_stay_logged_in: Pozostań zalogowany
-label_disabled: zablokowany
-label_show_completed_versions: Pokaż kompletne wersje
-label_me: ja
-label_board: Forum
-label_board_new: Nowe forum
-label_board_plural: Fora
-label_topic_plural: Tematy
-label_message_plural: Wiadomości
-label_message_last: Ostatnia wiadomość
-label_message_new: Nowa wiadomość
-label_reply_plural: Odpowiedzi
-label_send_information: Wyślij informację użytkownikowi
-label_year: Rok
-label_month: MiesiÄ…c
-label_week: Tydzień
-label_date_from: Z
-label_date_to: Do
-label_language_based: Na podstawie języka
-
-button_login: Login
-button_submit: Wyślij
-button_save: Zapisz
-button_check_all: Zaznacz wszystko
-button_uncheck_all: Odznacz wszystko
-button_delete: Usuń
-button_create: Stwórz
-button_test: Testuj
-button_edit: Edytuj
-button_add: Dodaj
-button_change: Zmień
-button_apply: Ustaw
-button_clear: Wyczyść
-button_lock: Zablokuj
-button_unlock: Odblokuj
-button_download: Pobierz
-button_list: Lista
-button_view: Pokaż
-button_move: PrzenieÅ›
-button_back: Wstecz
-button_cancel: Anuluj
-button_activate: Aktywuj
-button_sort: Sortuj
-button_log_time: Log czasu
-button_rollback: Przywróc do tej wersji
-button_watch: Obserwuj
-button_unwatch: Nie obserwuj
-button_reply: Odpowiedz
-button_archive: Archiwizuj
-button_unarchive: Przywróc z archiwum
-
-status_active: aktywny
-status_registered: zarejestrowany
-status_locked: zablokowany
-
-text_select_mail_notifications: Zaznacz czynności przy których użytkownik powinien być powiadomiony mailem.
-text_regexp_info: np. ^[A-Z0-9]+$
-text_min_max_length_info: 0 oznacza brak restrykcji
-text_project_destroy_confirmation: Jesteś pewien, że chcesz usunąć ten projekt i wszyskie powiązane dane?
-text_workflow_edit: Zaznacz rolę i typ zagadnienia do edycji przepływu
-text_are_you_sure: JesteÅ› pewien ?
-text_journal_changed: zmienione %s do %s
-text_journal_set_to: ustawione na %s
-text_journal_deleted: usunięte
-text_tip_task_begin_day: zadanie zaczynajÄ…ce siÄ™ dzisiaj
-text_tip_task_end_day: zadanie kończące się dzisiaj
-text_tip_task_begin_end_day: zadanie zaczynające i kończące się dzisiaj
-text_project_identifier_info: 'Małe litery (a-z), liczby i myślniki dozwolone.<br />Raz zapisany, identyfikator nie może być zmieniony.'
-text_caracters_maximum: %d znaków maksymalnie.
-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: 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: Zadanie
-default_tracker_support: Wsparcie
-default_issue_status_new: Nowy
-default_issue_status_assigned: Przypisany
-default_issue_status_resolved: RozwiÄ…zany
-default_issue_status_feedback: Odpowiedź
-default_issue_status_closed: Zamknięty
-default_issue_status_rejected: Odrzucony
-default_doc_category_user: Dokumentacja użytkownika
-default_doc_category_tech: Dokumentacja techniczna
-default_priority_low: Niski
-default_priority_normal: Normalny
-default_priority_high: Wysoki
-default_priority_urgent: Pilny
-default_priority_immediate: Natychmiastowy
-default_activity_design: Projektowanie
-default_activity_development: Rozwój
-
-enumeration_issue_priorities: Priorytety zagadnień
-enumeration_doc_categories: Kategorie dokumentów
-enumeration_activities: Działania (śledzenie czasu)
-button_rename: Zmień nazwę
-text_issue_category_destroy_question: Zagadnienia (%d) są przypisane do tej kategorii. Co chcesz uczynić?
-label_feeds_access_key_created_on: Klucz dostępu RSS stworzony %s dni temu
-setting_cross_project_issue_relations: Zezwól na powiązania zagadnień między projektami
-label_roadmap_overdue: %s spóźnienia
-label_module_plural: Moduły
+label_string: Tekst
+label_subproject_plural: Podprojekty
+label_text: Długi tekst
+label_theme: Temat
+label_this_month: ten miesiÄ…c
label_this_week: ten tydzień
-label_jump_to_a_project: Skocz do projektu...
-field_assignable: Zagadnienia mogą być przypisane do tej roli
-label_sort_by: Sortuj po %s
-text_issue_updated: Zagadnienie %s zostało zaktualizowane (by %s).
-notice_feeds_access_key_reseted: Twój klucz dostępu RSS został zrestetowany.
-field_redirect_existing_links: Przekierowanie istniejących odnośników
-text_issue_category_reassign_to: Przydziel zagadnienie do tej kategorii
-notice_email_sent: Email został wysłany do %s
-text_issue_added: Zagadnienie %s zostało wprowadzone (by %s).
-text_wiki_destroy_confirmation: Jesteś pewien, że chcesz usunąć to wiki i całą jego zawartość ?
-notice_email_error: Wystąpił błąd w trakcie wysyłania maila (%s)
+label_this_year: ten rok
+label_time_tracking: Åšledzenie czasu
+label_today: dzisiaj
+label_topic_plural: Tematy
+label_total: Ogółem
+label_tracker: Typ zagadnienia
+label_tracker_new: Nowy typ zagadnienia
+label_tracker_plural: Typy zagadnień
label_updated_time: Zaktualizowane %s temu
-text_issue_category_destroy_assignments: Usuń przydziały kategorii
-label_send_test_email: Wyślij próbny email
-button_reset: Resetuj
-label_added_time_by: Dodane przez %s %s temu
-field_estimated_hours: Szacowany czas
-label_file_plural: Pliki
-label_changeset_plural: Zestawienia zmian
-field_column_names: Nazwy kolumn
-label_default_columns: Domyślne kolumny
-setting_issue_list_default_columns: Domyślne kolumny wiświetlane na liście zagadnień
-setting_repositories_encodings: Kodowanie repozytoriów
-notice_no_issue_selected: "Nie wybrano zagadnienia! Zaznacz zagadnienie, które chcesz edytować."
-label_bulk_edit_selected_issues: Zbiorowa edycja zagadnień
-label_no_change_option: (Bez zmian)
-notice_failed_to_save_issues: "Błąd podczas zapisu zagadnień %d z %d zaznaczonych: %s."
-label_theme: Temat
-label_default: Domyślne
-label_search_titles_only: Przeszukuj tylko tytuły
-label_nobody: nikt
-button_change_password: Zmień hasło
-text_user_mail_option: "W przypadku niezaznaczonych projektów, będziesz otrzymywał powiadomienia tylko na temat zagadnien, które obserwujesz, lub w których bierzesz udział (np. jesteś autorem lub adresatem)."
-label_user_mail_option_selected: "Tylko dla każdego zdarzenia w wybranych projektach..."
+label_used_by: Używane przez
+label_user: Użytkownik
+label_user_mail_no_self_notified: "Nie chcę powiadomień o zmianach, które sam wprowadzam."
label_user_mail_option_all: "Dla każdego zdarzenia w każdym moim projekcie"
label_user_mail_option_none: "Tylko to co obserwuje lub w czym biorę udział"
-setting_emails_footer: Stopka e-mail
-label_float: Liczba rzeczywista
-button_copy: Kopia
-mail_body_account_information_external: Możesz użyć twojego "%s" konta do zalogowania.
+label_user_mail_option_selected: "Tylko dla każdego zdarzenia w wybranych projektach..."
+label_user_new: Nowy użytkownik
+label_user_plural: Użytkownicy
+label_version: Wersja
+label_version_new: Nowa wersja
+label_version_plural: Wersje
+label_view_diff: Pokaż różnice
+label_view_revisions: Pokaż rewizje
+label_watched_issues: Obserwowane zagadnienia
+label_week: Tydzień
+label_wiki: Wiki
+label_wiki_edit: Edycja wiki
+label_wiki_edit_plural: Edycje wiki
+label_wiki_page: Strona wiki
+label_wiki_page_plural: Strony wiki
+label_workflow: Przepływ
+label_year: Rok
+label_yesterday: wczoraj
+mail_body_account_activation_request: 'Zarejestrowano nowego użytkownika: (%s). Konto oczekuje na twoje zatwierdzenie:'
mail_body_account_information: Twoje konto
-setting_protocol: Protokoł
-label_user_mail_no_self_notified: "Nie chcę powiadomień o zmianach, które sam wprowadzam."
-setting_time_format: Format czasu
-label_registration_activation_by_email: aktywacja konta przez e-mail
+mail_body_account_information_external: Możesz użyć twojego "%s" konta do zalogowania.
+mail_body_lost_password: 'W celu zmiany swojego hasła użyj poniższego odnośnika:'
+mail_body_register: 'W celu aktywacji Twojego konta, użyj poniższego odnośnika:'
+mail_body_reminder: "Wykaz przypisanych do Ciebie zagadnień, których termin wypada w ciągu następnych %d dni"
mail_subject_account_activation_request: Zapytanie aktywacyjne konta %s
-mail_body_account_activation_request: 'Zarejestrowano nowego użytkownika: (%s). Konto oczekuje na twoje zatwierdzenie:'
-label_registration_automatic_activation: automatyczna aktywacja kont
-label_registration_manual_activation: manualna aktywacja kont
+mail_subject_lost_password: Twoje hasło do %s
+mail_subject_register: Aktywacja konta w %s
+mail_subject_reminder: "Uwaga na terminy, masz zagadnienia do obsłużenia w ciągu następnych %d dni!"
+notice_account_activated: Twoje konto zostało aktywowane. Możesz się zalogować.
+notice_account_invalid_creditentials: Zły użytkownik lub hasło
+notice_account_lost_email_sent: Email z instrukcjami zmiany hasła został wysłany do Ciebie.
+notice_account_password_updated: Hasło prawidłowo zmienione.
notice_account_pending: "Twoje konto zostało utworzone i oczekuje na zatwierdzenie administratora."
-field_time_zone: Strefa czasowa
-text_caracters_minimum: Musi być nie krótsze niż %d znaków.
-setting_bcc_recipients: Odbiorcy kopii tajnej (kt/bcc)
-button_annotate: Adnotuj
-label_issues_by: Zagadnienia wprowadzone przez %s
-field_searchable: Przeszukiwalne
-label_display_per_page: 'Na stronÄ™: %s'
-setting_per_page_options: Opcje ilości obiektów na stronie
-label_age: Wiek
+notice_account_register_done: Konto prawidłowo stworzone.
+notice_account_unknown_email: Nieznany użytkownik.
+notice_account_updated: Konto prawidłowo zaktualizowane.
+notice_account_wrong_password: Złe hasło
+notice_can_t_change_password: To konto ma zewnętrzne źródło identyfikacji. Nie możesz zmienić hasła.
notice_default_data_loaded: Domyślna konfiguracja została pomyślnie załadowana.
-text_load_default_configuration: Załaduj domyślną konfigurację
-text_no_configuration_data: "Role użytkowników, typy zagadnień, statusy zagadnień oraz przepływ pracy nie zostały jeszcze skonfigurowane.\nJest wysoce rekomendowane by załadować domyślną konfigurację. Po załadowaniu będzie możliwość edycji tych danych."
-error_can_t_load_default_data: "Domyślna konfiguracja nie może być załadowana: %s"
-button_update: Uaktualnij
-label_change_properties: Zmień właściwości
-label_general: Ogólne
-label_repository_plural: Repozytoria
-label_associated_revisions: Skojarzone rewizje
-setting_user_format: Personalny format wyświetlania
-text_status_changed_by_changeset: Zastosowane w zmianach %s.
-label_more: Więcej
-text_issues_destroy_confirmation: 'Czy jestes pewien, że chcesz usunąć wskazane zagadnienia?'
-label_scm: SCM
-text_select_project_modules: 'Wybierz moduły do aktywacji w tym projekcie:'
-label_issue_added: Dodano zagadnienie
-label_issue_updated: Uaktualniono zagadnienie
-label_document_added: Dodano dokument
-label_message_posted: Dodano wiadomość
-label_file_added: Dodano plik
-label_news_added: Dodano wiadomość
+notice_email_error: Wystąpił błąd w trakcie wysyłania maila (%s)
+notice_email_sent: Email został wysłany do %s
+notice_failed_to_save_issues: "Błąd podczas zapisu zagadnień %d z %d zaznaczonych: %s."
+notice_feeds_access_key_reseted: Twój klucz dostępu RSS został zrestetowany.
+notice_file_not_found: Strona do której próbujesz się dostać nie istnieje lub została usunięta.
+notice_locking_conflict: Dane poprawione przez innego użytkownika.
+notice_no_issue_selected: "Nie wybrano zagadnienia! Zaznacz zagadnienie, które chcesz edytować."
+notice_not_authorized: Nie jesteś autoryzowany by zobaczyć stronę.
+notice_successful_connection: Udane nawiązanie połączenia.
+notice_successful_create: Utworzenie zakończone sukcesem.
+notice_successful_delete: Usunięcie zakończone sukcesem.
+notice_successful_update: Uaktualnienie zakończone sukcesem.
+notice_unable_delete_version: Nie można usunąć wersji
+permission_add_issue_notes: Dodawanie notatek
+permission_add_issue_watchers: Dodawanie obserwatorów
+permission_add_issues: Dodawanie zagadnień
+permission_add_messages: Dodawanie wiadomości
+permission_browse_repository: PrzeglÄ…danie repozytorium
+permission_comment_news: Komentowanie komunikatów
+permission_commit_access: Wykonywanie zatwierdzeń
+permission_delete_issues: Usuwanie zagadnień
+permission_delete_messages: Usuwanie wiadomości
+permission_delete_wiki_pages: Usuwanie stron wiki
+permission_delete_wiki_pages_attachments: Usuwanie załączników
+permission_delete_own_messages: Usuwanie własnych wiadomości
+permission_edit_issue_notes: Edycja notatek
+permission_edit_issues: Edycja zagadnień
+permission_edit_messages: Edycja wiadomości
+permission_edit_own_issue_notes: Edycja własnych notatek
+permission_edit_own_messages: Edycja własnych wiadomości
+permission_edit_own_time_entries: Edycja własnego logu czasu
+permission_edit_project: Edycja projektów
+permission_edit_time_entries: Edycja logów czasu
+permission_edit_wiki_pages: Edycja stron wiki
+permission_log_time: Zapisywanie spędzonego czasu
+permission_manage_boards: ZarzÄ…dzanie forami
+permission_manage_categories: Zarządzanie kategoriami zaganień
+permission_manage_documents: ZarzÄ…dzanie dokumentami
+permission_manage_files: ZarzÄ…dzanie plikami
+permission_manage_issue_relations: Zarządzanie powiązaniami zagadnień
+permission_manage_members: ZarzÄ…dzanie uczestnikami
+permission_manage_news: ZarzÄ…dzanie komunikatami
+permission_manage_public_queries: ZarzÄ…dzanie publicznymi kwerendami
+permission_manage_repository: ZarzÄ…dzanie repozytorium
+permission_manage_versions: ZarzÄ…dzanie wersjami
+permission_manage_wiki: ZarzÄ…dzanie wiki
+permission_move_issues: Przenoszenie zagadnień
+permission_protect_wiki_pages: Blokowanie stron wiki
+permission_rename_wiki_pages: Zmiana nazw stron wiki
+permission_save_queries: Zapisywanie kwerend
+permission_select_project_modules: Wybieranie modułów projektu
+permission_view_calendar: PodglÄ…d kalendarza
+permission_view_changesets: PodglÄ…d zmian
+permission_view_documents: Podgląd dokumentów
+permission_view_files: Podgląd plików
+permission_view_gantt: PodglÄ…d diagramu Gantta
+permission_view_issue_watchers: Podgląd listy obserwatorów
+permission_view_messages: Podgląd wiadomości
+permission_view_time_entries: Podgląd spędzonego czasu
+permission_view_wiki_edits: PodglÄ…d historii wiki
+permission_view_wiki_pages: PodglÄ…d wiki
project_module_boards: Fora
-project_module_issue_tracking: Śledzenie zagadnień
-project_module_wiki: Wiki
-project_module_files: Pliki
project_module_documents: Dokumenty
+project_module_files: Pliki
+project_module_issue_tracking: Śledzenie zagadnień
+project_module_news: Komunikaty
project_module_repository: Repozytorium
-project_module_news: Wiadomości
project_module_time_tracking: Åšledzenie czasu
-text_file_repository_writable: Zapisywalne repozytorium plików
-text_default_administrator_account_changed: Zmieniono domyślne hasło administratora
-text_rmagick_available: RMagick dostępne (opcjonalnie)
-button_configure: Konfiguruj
-label_plugins: Wtyczki
-label_ldap_authentication: Autoryzacja LDAP
-label_downloads_abbr: Pobieranie
-label_this_month: ten miesiÄ…c
-label_last_n_days: ostatnie %d dni
-label_all_time: cały czas
-label_this_year: ten rok
-label_date_range: Zakres datowy
-label_last_week: ostatni tydzień
-label_yesterday: wczoraj
-label_last_month: ostatni miesiÄ…c
-label_add_another_file: Dodaj kolejny plik
-label_optional_description: Opcjonalny opis
-text_destroy_time_entries_question: Zalogowano %.02f godzin przy zagadnieniu, które chcesz usunąć. Co chcesz zrobić?
-error_issue_not_found_in_project: 'Zaganienie nie zostało znalezione lub nie należy do tego projektu'
+project_module_wiki: Wiki
+setting_activity_days_default: Dni wyświetlane w aktywności projektu
+setting_app_subtitle: Podtytuł aplikacji
+setting_app_title: Tytuł aplikacji
+setting_attachment_max_size: Maks. rozm. załącznika
+setting_autofetch_changesets: Automatyczne pobieranie zmian
+setting_autologin: Auto logowanie
+setting_bcc_recipients: Odbiorcy kopii tajnej (kt/bcc)
+setting_commit_fix_keywords: Słowa zmieniające status
+setting_commit_logs_encoding: Kodowanie komentarzy zatwierdzeń
+setting_commit_ref_keywords: Słowa tworzące powiązania
+setting_cross_project_issue_relations: Zezwól na powiązania zagadnień między projektami
+setting_date_format: Format daty
+setting_default_language: Domyślny język
+setting_default_projects_public: Nowe projekty są domyślnie publiczne
+setting_display_subprojects_issues: Domyślnie pokazuj zagadnienia podprojektów w głównym projekcie
+setting_emails_footer: Stopka e-mail
+setting_enabled_scm: Dostępny SCM
+setting_feeds_limit: Limit danych RSS
+setting_gravatar_enabled: Używaj ikon użytkowników Gravatar
+setting_host_name: Nazwa hosta i ścieżka
+setting_issue_list_default_columns: Domyślne kolumny wiświetlane na liście zagadnień
+setting_issues_export_limit: Limit eksportu zagadnień
+setting_login_required: Identyfikacja wymagana
+setting_mail_from: Adres email wysyłki
+setting_mail_handler_api_enabled: Uaktywnij usługi sieciowe (WebServices) dla poczty przychodzącej
+setting_mail_handler_api_key: Klucz API
+setting_per_page_options: Opcje ilości obiektów na stronie
+setting_plain_text_mail: tylko tekst (bez HTML)
+setting_protocol: Protokoł
+setting_repositories_encodings: Kodowanie repozytoriów
+setting_self_registration: Własna rejestracja umożliwiona
+setting_sequential_project_identifiers: Generuj sekwencyjne identyfikatory projektów
+setting_sys_api_enabled: Włączenie WS do zarządzania repozytorium
+setting_text_formatting: Formatowanie tekstu
+setting_time_format: Format czasu
+setting_user_format: Personalny format wyświetlania
+setting_welcome_text: Tekst powitalny
+setting_wiki_compression: Kompresja historii Wiki
+status_active: aktywny
+status_locked: zablokowany
+status_registered: zarejestrowany
+text_are_you_sure: JesteÅ› pewien ?
text_assign_time_entries_to_project: Przypisz logowany czas do projektu
+text_caracters_maximum: %d znaków maksymalnie.
+text_caracters_minimum: Musi być nie krótsze niż %d znaków.
+text_comma_separated: Wielokrotne wartości dozwolone (rozdzielone przecinkami).
+text_default_administrator_account_changed: Zmieniono domyślne hasło administratora
text_destroy_time_entries: Usuń zalogowany czas
+text_destroy_time_entries_question: Zalogowano %.02f godzin przy zagadnieniu, które chcesz usunąć. Co chcesz zrobić?
+text_email_delivery_not_configured: "Dostarczanie poczty elektronicznej nie zostało skonfigurowane, więc powiadamianie jest nieaktywne.\nSkonfiguruj serwer SMTP w config/email.yml a następnie zrestartuj aplikację i uaktywnij to."
+text_enumeration_category_reassign_to: 'Zmień przypisanie na tą wartość:'
+text_enumeration_destroy_question: '%d obiektów jest przypisana do tej wartości.'
+text_file_repository_writable: Zapisywalne repozytorium plików
+text_issue_added: Zagadnienie %s zostało wprowadzone (by %s).
+text_issue_category_destroy_assignments: Usuń przydziały kategorii
+text_issue_category_destroy_question: Zagadnienia (%d) są przypisane do tej kategorii. Co chcesz uczynić?
+text_issue_category_reassign_to: Przydziel zagadnienie do tej kategorii
+text_issue_updated: Zagadnienie %s zostało zaktualizowane (by %s).
+text_issues_destroy_confirmation: 'Czy jestes pewien, że chcesz usunąć wskazane zagadnienia?'
+text_issues_ref_in_commit_messages: Odwołania do zagadnień w komentarzach zatwierdzeń
+text_journal_changed: zmienione %s do %s
+text_journal_deleted: usunięte
+text_journal_set_to: ustawione na %s
+text_length_between: Długość pomiędzy %d i %d znaków.
+text_load_default_configuration: Załaduj domyślną konfigurację
+text_min_max_length_info: 0 oznacza brak restrykcji
+text_no_configuration_data: "Role użytkowników, typy zagadnień, statusy zagadnień oraz przepływ pracy nie zostały jeszcze skonfigurowane.\nJest wysoce rekomendowane by załadować domyślną konfigurację. Po załadowaniu będzie możliwość edycji tych danych."
+text_project_destroy_confirmation: Jesteś pewien, że chcesz usunąć ten projekt i wszyskie powiązane dane?
+text_project_identifier_info: 'Małe litery (a-z), liczby i myślniki dozwolone.<br />Raz zapisany, identyfikator nie może być zmieniony.'
text_reassign_time_entries: 'Przepnij zalogowany czas do tego zagadnienia:'
-label_chronological_order: W kolejności chronologicznej
-setting_activity_days_default: Dni wyświetlane w aktywności projektu
-setting_display_subprojects_issues: Domyślnie pokazuj zagadnienia podprojektów w głównym projekcie
-field_comments_sorting: Pokazuj komentarze
-label_reverse_chronological_order: W kolejności odwrotnej do chronologicznej
-label_preferences: Preferencje
-label_overall_activity: Ogólna aktywność
-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
-setting_sequential_project_identifiers: Generate sequential project identifiers
-notice_unable_delete_version: Unable to delete version
-label_renamed: renamed
-label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+text_regexp_info: np. ^[A-Z0-9]+$
+text_repository_usernames_mapping: "Wybierz lub uaktualnij przyporządkowanie użytkowników Redmine do użytkowników repozytorium.\nUżytkownicy z taką samą nazwą lub adresem email są przyporządkowani automatycznie."
+text_rmagick_available: RMagick dostępne (opcjonalnie)
+text_select_mail_notifications: Zaznacz czynności przy których użytkownik powinien być powiadomiony mailem.
+text_select_project_modules: 'Wybierz moduły do aktywacji w tym projekcie:'
+text_status_changed_by_changeset: Zastosowane w zmianach %s.
+text_subprojects_destroy_warning: 'Podprojekt(y): %s zostaną także usunięte.'
+text_tip_task_begin_day: zadanie zaczynajÄ…ce siÄ™ dzisiaj
+text_tip_task_begin_end_day: zadanie zaczynające i kończące się dzisiaj
+text_tip_task_end_day: zadanie kończące się dzisiaj
+text_tracker_no_workflow: Brak przepływu zefiniowanego dla tego typu zagadnienia
+text_unallowed_characters: Niedozwolone znaki
+text_user_mail_option: "W przypadku niezaznaczonych projektów, będziesz otrzymywał powiadomienia tylko na temat zagadnien, które obserwujesz, lub w których bierzesz udział (np. jesteś autorem lub adresatem)."
+text_user_wrote: '%s napisał:'
+text_wiki_destroy_confirmation: Jesteś pewien, że chcesz usunąć to wiki i całą jego zawartość ?
+text_workflow_edit: Zaznacz rolę i typ zagadnienia do edycji przepływu
+
+label_user_activity: "Aktywność: %s"
+label_updated_time_by: Uaktualnione przez %s %s temu
+text_diff_truncated: '... Ten plik różnic został przycięty ponieważ jest zbyt długi.'
+setting_diff_max_lines_displayed: Maksymalna liczba linii różnicy do pokazania
+text_plugin_assets_writable: Zapisywalny katalog zasobów wtyczek
+warning_attachments_not_saved: "%d file(s) could not be saved."
+field_editable: Editable
+label_display: Display
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/pt-br.yml b/lang/pt-br.yml
index 74ee2486c..073c8e377 100644
--- a/lang/pt-br.yml
+++ b/lang/pt-br.yml
@@ -1,7 +1,7 @@
_gloc_rule_default: '|n| n==1 ? "" : "_plural" '
actionview_datehelper_select_day_prefix:
-actionview_datehelper_select_month_names: Janeiro,Fevereiro,Março,Abrill,Maio,Junho,Julho,Agosto,Setembro,Outubro,Novembro,Dezembro
+actionview_datehelper_select_month_names: Janeiro,Fevereiro,Março,Abril,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:
@@ -47,8 +47,8 @@ general_text_Yes: 'Sim'
general_text_no: 'não'
general_text_yes: 'sim'
general_lang_name: 'Português(Brasil)'
-general_csv_separator: ','
-general_csv_decimal_separator: '.'
+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
@@ -638,8 +638,73 @@ default_activity_development: Desenvolvimento
enumeration_issue_priorities: Prioridade das tarefas
enumeration_doc_categories: Categorias de documento
enumeration_activities: Atividades (time tracking)
-notice_unable_delete_version: Unable to delete version
-label_renamed: renamed
-label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+notice_unable_delete_version: Não foi possível excluir a versão
+label_renamed: renomeado
+label_copied: copiado
+setting_plain_text_mail: texto plano apenas (sem HTML)
+permission_view_files: Ver Arquivos
+permission_edit_issues: Editar tickets
+permission_edit_own_time_entries: Editar o próprio tempo de trabalho
+permission_manage_public_queries: Gerenciar consultas publicas
+permission_add_issues: Adicionar Tickets
+permission_log_time: Adicionar tempo gasto
+permission_view_changesets: Ver changesets
+permission_view_time_entries: Ver tempo gasto
+permission_manage_versions: Gerenciar versões
+permission_manage_wiki: Gerenciar wiki
+permission_manage_categories: Gerenciar categorias de tickets
+permission_protect_wiki_pages: Proteger páginas wiki
+permission_comment_news: Comentar notícias
+permission_delete_messages: Excluir mensagens
+permission_select_project_modules: Selecionar módulos de projeto
+permission_manage_documents: Gerenciar documentos
+permission_edit_wiki_pages: Editar páginas wiki
+permission_add_issue_watchers: Adicionar monitores
+permission_view_gantt: Ver gráfico gantt
+permission_move_issues: Mover tickets
+permission_manage_issue_relations: Gerenciar relacionamentos de tickets
+permission_delete_wiki_pages: Excluir páginas wiki
+permission_manage_boards: Gerenciar fóruns
+permission_delete_wiki_pages_attachments: Excluir anexos
+permission_view_wiki_edits: Ver histórico do wiki
+permission_add_messages: Postar mensagens
+permission_view_messages: Ver mensagens
+permission_manage_files: Gerenciar arquivos
+permission_edit_issue_notes: Editar notas
+permission_manage_news: Gerenciar notícias
+permission_view_calendar: Ver caneldário
+permission_manage_members: Gerenciar membros
+permission_edit_messages: Editar mensagens
+permission_delete_issues: Excluir tickets
+permission_view_issue_watchers: Ver lista de monitores
+permission_manage_repository: Gerenciar repositório
+permission_commit_access: Acesso de commit
+permission_browse_repository: Pesquisar repositorio
+permission_view_documents: Ver documentos
+permission_edit_project: Editar projeto
+permission_add_issue_notes: Adicionar notas
+permission_save_queries: Salvar consultas
+permission_view_wiki_pages: Ver wiki
+permission_rename_wiki_pages: Renomear páginas wiki
+permission_edit_time_entries: Editar tempo gasto
+permission_edit_own_issue_notes: Editar próprias notas
+setting_gravatar_enabled: Usar ícones do Gravatar
+label_example: Exemplo
+text_repository_usernames_mapping: "Seleciona ou atualiza os usuários do Redmine mapeando para cada usuário encontrado no log do repositório.\nUsuários com o mesmo login ou email no Redmine e no repositório serão mapeados automaticamente."
+permission_edit_own_messages: Editar próprias mensagens
+permission_delete_own_messages: Excluir próprias mensagens
+label_user_activity: "Atividade de %s"
+label_updated_time_by: Atualizado por %s à %s
+text_diff_truncated: '... Este diff foi truncado porque excede o tamanho máximo que pode ser exibido.'
+setting_diff_max_lines_displayed: Número máximo de linhas exibidas no diff
+text_plugin_assets_writable: Diretório de plugins gravável
+warning_attachments_not_saved: "%d arquivo(s) não puderam ser salvo(s)."
+button_create_and_continue: Criar e continuar
+text_custom_field_possible_values_info: 'Uma linha para cada valor'
+label_display: Exibição
+field_editable: Editável
+setting_repository_log_display_limit: Número máximo de revisões exibidas no arquivo de log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/pt.yml b/lang/pt.yml
index 98f13108b..7836f0fc6 100644
--- a/lang/pt.yml
+++ b/lang/pt.yml
@@ -1,3 +1,4 @@
+## Translated by: Pedro Araújo <phcrva19@hotmail.com>
_gloc_rule_default: '|n| n==1 ? "" : "_plural" '
actionview_datehelper_select_day_prefix:
@@ -7,9 +8,9 @@ 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: em torno de uma hora
-actionview_datehelper_time_in_words_hour_about_plural: em torno de %d horas
-actionview_datehelper_time_in_words_hour_about_single: em torno de uma hora
+actionview_datehelper_time_in_words_hour_about: cerca de uma hora
+actionview_datehelper_time_in_words_hour_about_plural: cerca de %d horas
+actionview_datehelper_time_in_words_hour_about_single: cerca de 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 de um minuto
@@ -22,19 +23,19 @@ actionview_instancetag_blank_option: Seleccione
activerecord_error_inclusion: não existe na lista
activerecord_error_exclusion: já existe na lista
activerecord_error_invalid: é inválido
-activerecord_error_confirmation: não está de acordo com sua confirmação
-activerecord_error_accepted: deve ser aceite
-activerecord_error_empty: não pode ser vazio
+activerecord_error_confirmation: não está de acordo com a confirmação
+activerecord_error_accepted: tem de ser aceite
+activerecord_error_empty: não pode estar 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: possui o comprimento errado
+activerecord_error_too_long: é demasiado longo
+activerecord_error_too_short: é demasiado curto
+activerecord_error_wrong_length: tem o comprimento errado
activerecord_error_taken: já foi usado em outro registo
activerecord_error_not_a_number: não é um número
activerecord_error_not_a_date: não é uma data válida
activerecord_error_greater_than_start_date: deve ser maior que a data inicial
activerecord_error_not_same_project: não pertence ao mesmo projecto
-activerecord_error_circular_dependency: Esta relação pode criar uma dependência circular
+activerecord_error_circular_dependency: Esta relação iria criar uma dependência circular
general_fmt_age: %d ano
general_fmt_age_plural: %d anos
@@ -47,40 +48,54 @@ general_text_Yes: 'Sim'
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_csv_separator: ';'
+general_csv_decimal_separator: ','
+general_csv_encoding: ISO-8859-15
+general_pdf_encoding: ISO-8859-15
general_day_names: Segunda,Terça,Quarta,Quinta,Sexta,Sábado,Domingo
general_first_day_of_week: '1'
-notice_account_updated: Conta actualizada com sucesso.
+notice_account_updated: A conta foi actualizada com sucesso.
notice_account_invalid_creditentials: Utilizador ou palavra-chave inválidos.
-notice_account_password_updated: Palavra-chave foi alterada com sucesso.
+notice_account_password_updated: A palavra-chave foi alterada com sucesso.
notice_account_wrong_password: Palavra-chave errada.
-notice_account_register_done: Conta criada com sucesso.
+notice_account_register_done: A conta foi criada com sucesso.
notice_account_unknown_email: Utilizador desconhecido.
-notice_can_t_change_password: Esta conta utiliza autenticação externa. Não é possível alterar a palavra-chave.
-notice_account_lost_email_sent: Foi enviado e-mail com as instruções para criar uma nova palavra-chave.
-notice_account_activated: Conta foi activada. Pode ligar-se agora
+notice_can_t_change_password: Esta conta utiliza uma fonte de autenticação externa. Não é possível alterar a palavra-chave.
+notice_account_lost_email_sent: Foi-lhe enviado um e-mail com as instruções para escolher uma nova palavra-chave.
+notice_account_activated: A sua conta foi activada. Já pode autenticar-se.
notice_successful_create: Criado com sucesso.
notice_successful_update: Alterado com sucesso.
notice_successful_delete: Apagado com sucesso.
notice_successful_connection: Ligado com sucesso.
-notice_file_not_found: A página que está a tentar aceder não existe ou foi eliminada.
+notice_file_not_found: A página que está a tentar aceder não existe ou foi removida.
notice_locking_conflict: Os dados foram actualizados por outro utilizador.
notice_not_authorized: Não está autorizado a visualizar esta página.
notice_email_sent: Foi enviado um e-mail para %s
notice_email_error: Ocorreu um erro ao enviar o e-mail (%s)
-notice_feeds_access_key_reseted: A sua chave RSS foi inicializada.
+notice_feeds_access_key_reseted: A sua chave de RSS foi inicializada.
+notice_failed_to_save_issues: "Não foi possível guardar %d tarefa(s) das %d seleccionadas: %s."
+notice_no_issue_selected: "Nenhuma tarefa seleccionada! Por favor, seleccione as tarefas que quer editar."
+notice_account_pending: "A sua conta foi criada e está agora à espera de aprovação do administrador."
+notice_default_data_loaded: Configuração padrão carregada com sucesso.
+notice_unable_delete_version: Não foi possível apagar a versão.
-error_scm_not_found: "A entrada e/ou a revisão não existem no repositório."
-error_scm_command_failed: "Ocorreu um erro ao tentar aceder ao repositorio: %s"
+error_can_t_load_default_data: "Não foi possível carregar a configuração padrão: %s"
+error_scm_not_found: "A entrada ou revisão não foi encontrada no repositório."
+error_scm_command_failed: "Ocorreu um erro ao tentar aceder ao repositório: %s"
+error_scm_annotate: "A entrada não existe ou não pode ser anotada."
+error_issue_not_found_in_project: 'A tarefa não foi encontrada ou não pertence a este projecto.'
-mail_subject_lost_password: Palavra-chave do %s.
-mail_body_lost_password: 'Para mudar a palavra-chave, clique no link abaixo:'
-mail_subject_register: Activação de conta do %s.
-mail_body_register: 'Para activar a conta, clique no link abaixo:'
+mail_subject_lost_password: Palavra-chave de %s
+mail_body_lost_password: 'Para mudar a sua palavra-chave, clique no link abaixo:'
+mail_subject_register: Activação de conta de %s
+mail_body_register: 'Para activar a sua conta, clique no link abaixo:'
+mail_body_account_information_external: Pode utilizar a conta "%s" para autenticar-se.
+mail_body_account_information: Informação da sua conta
+mail_subject_account_activation_request: Pedido de activação da conta %s
+mail_body_account_activation_request: 'Um novo utilizador (%s) registou-se. A sua conta está à espera de aprovação:'
+mail_subject_reminder: "%d tarefa(s) para entregar nos próximos dias"
+mail_body_reminder: "%d tarefa(s) que estão atribuídas a si estão agendadas para estarem completas nos próximos %d dias:"
gui_validation_error: 1 erro
gui_validation_error_plural: %d erros
@@ -89,10 +104,10 @@ field_name: Nome
field_description: Descrição
field_summary: Sumário
field_is_required: Obrigatório
-field_firstname: Primeiro nome
-field_lastname: Último nome
+field_firstname: Nome
+field_lastname: Apelido
field_mail: E-mail
-field_filename: Arquivo
+field_filename: Ficheiro
field_filesize: Tamanho
field_downloads: Downloads
field_author: Autor
@@ -100,7 +115,7 @@ field_created_on: Criado
field_updated_on: Alterado
field_field_format: Formato
field_is_for_all: Para todos os projectos
-field_possible_values: Possíveis valores
+field_possible_values: Valores possíveis
field_regexp: Expressão regular
field_min_length: Tamanho mínimo
field_max_length: Tamanho máximo
@@ -108,11 +123,11 @@ field_value: Valor
field_category: Categoria
field_title: Título
field_project: Projecto
-field_issue: Tarefa
+field_issue: Tarefa
field_status: Estado
field_notes: Notas
field_is_closed: Tarefa fechada
-field_is_default: Estado padrão
+field_is_default: Valor por omissão
field_tracker: Tipo
field_subject: Assunto
field_due_date: Data final
@@ -120,16 +135,16 @@ field_assigned_to: Atribuído a
field_priority: Prioridade
field_fixed_version: Versão
field_user: Utilizador
-field_role: Regra
-field_homepage: Página inicial
+field_role: Papel
+field_homepage: Página
field_is_public: Público
-field_parent: Subprojecto de
+field_parent: Sub-projecto de
field_is_in_chlog: Tarefas mostradas no changelog
field_is_in_roadmap: Tarefas mostradas no roadmap
-field_login: Utilizador
+field_login: Nome de utilizador
field_mail_notification: Notificações por e-mail
field_admin: Administrador
-field_last_login_on: Último acesso
+field_last_login_on: Última visita
field_language: Língua
field_effective_date: Data
field_password: Palavra-chave
@@ -142,12 +157,12 @@ field_port: Porta
field_account: Conta
field_base_dn: Base DN
field_attr_login: Atributo utilizador
-field_attr_firstname: Atributo primeiro nome
+field_attr_firstname: Atributo nome próprio
field_attr_lastname: Atributo último nome
field_attr_mail: Atributo e-mail
-field_onthefly: Criação de utilizadores sob demanda
+field_onthefly: Criação de utilizadores na hora
field_start_date: Início
-field_done_ratio: %% Terminado
+field_done_ratio: %% Completo
field_auth_source: Modo de autenticação
field_hide_mail: Esconder endereço de e-mail
field_comments: Comentário
@@ -161,31 +176,61 @@ field_identifier: Identificador
field_is_filter: Usado como filtro
field_issue_to_id: Tarefa relacionada
field_delay: Atraso
-field_assignable: As tarefas não podem ser associados a esta regra
-field_redirect_existing_links: Redireccionar as hiperligações
-field_estimated_hours: Estimativa de horas
-field_default_value: Padrão
+field_assignable: As tarefas podem ser associados a este papel
+field_redirect_existing_links: Redireccionar links existentes
+field_estimated_hours: Tempo estimado
+field_column_names: Colunas
+field_time_zone: Fuso horário
+field_searchable: Procurável
+field_default_value: Valor por omissão
+field_comments_sorting: Mostrar comentários
+field_parent_title: Página pai
setting_app_title: Título da aplicação
-setting_app_subtitle: Subtítulo da aplicação
-setting_welcome_text: Texto de boas-vindas
-setting_default_language: Linguagem padrão
+setting_app_subtitle: Sub-título da aplicação
+setting_welcome_text: Texto de boas vindas
+setting_default_language: Língua por omissão
setting_login_required: Autenticação obrigatória
-setting_self_registration: Registro permitido
+setting_self_registration: Auto-registo
setting_attachment_max_size: Tamanho máximo do anexo
setting_issues_export_limit: Limite de exportação das tarefas
setting_mail_from: E-mail enviado de
-setting_host_name: Servidor
-setting_text_formatting: Formato do texto
-setting_wiki_compression: Compactação do histórico do Wiki
-setting_feeds_limit: Limite do Feed
+setting_bcc_recipients: Recipientes de BCC
+setting_host_name: Hostname
+setting_text_formatting: Formatação do texto
+setting_wiki_compression: Compressão do histórico do Wiki
+setting_feeds_limit: Limite de conteúdo do feed
+setting_default_projects_public: Projectos novos são públicos por omissão
setting_autofetch_changesets: Buscar automaticamente commits
setting_sys_api_enabled: Activar Web Service para gestão do repositório
setting_commit_ref_keywords: Palavras-chave de referência
-setting_commit_fix_keywords: Palavras-chave fixas
-setting_autologin: Acesso automático
+setting_commit_fix_keywords: Palavras-chave de fecho
+setting_autologin: Login automático
setting_date_format: Formato da data
-setting_cross_project_issue_relations: Permitir relações de tarefas de projectos diferentes
+setting_time_format: Formato do tempo
+setting_cross_project_issue_relations: Permitir relações entre tarefas de projectos diferentes
+setting_issue_list_default_columns: Colunas na lista de tarefas por omissão
+setting_repositories_encodings: Encodings dos repositórios
+setting_commit_logs_encoding: Encoding das mensagens de commit
+setting_emails_footer: Rodapé do e-mails
+setting_protocol: Protocolo
+setting_per_page_options: Opções de objectos por página
+setting_user_format: Formato de apresentaão de utilizadores
+setting_activity_days_default: Dias mostrados na actividade do projecto
+setting_display_subprojects_issues: Mostrar as tarefas dos sub-projectos nos projectos principais
+setting_enabled_scm: Activar SCM
+setting_mail_handler_api_enabled: Activar Web Service para e-mails recebidos
+setting_mail_handler_api_key: Chave da API
+setting_sequential_project_identifiers: Gerar identificadores de projecto sequênciais
+
+project_module_issue_tracking: Tarefas
+project_module_time_tracking: Registo de tempo
+project_module_news: Notícias
+project_module_documents: Documentos
+project_module_files: Ficheiros
+project_module_wiki: Wiki
+project_module_repository: Repositório
+project_module_boards: Forum
label_user: Utilizador
label_user_plural: Utilizadores
@@ -199,13 +244,17 @@ label_issue: Tarefa
label_issue_new: Nova tarefa
label_issue_plural: Tarefas
label_issue_view_all: Ver todas as tarefas
+label_issues_by: Tarefas por %s
+label_issue_added: Tarefa adicionada
+label_issue_updated: Tarefa actualizada
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 permissões
+label_document_added: Documento adicionado
+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
@@ -214,36 +263,37 @@ label_tracker_plural: Tipos
label_tracker_new: Novo tipo
label_workflow: Workflow
label_issue_status: Estado da tarefa
-label_issue_status_plural: Estado das tarefas
+label_issue_status_plural: Estados da tarefa
label_issue_status_new: Novo estado
-label_issue_category: Categoria da tarefa
-label_issue_category_plural: Categorias das tarefas
+label_issue_category: Categoria de tarefa
+label_issue_category_plural: Categorias de tarefa
label_issue_category_new: Nova categoria
label_custom_field: Campo personalizado
label_custom_field_plural: Campos personalizados
label_custom_field_new: Novo campo personalizado
-label_enumerations: Enumeração
+label_enumerations: Enumerações
label_enumeration_new: Novo valor
label_information: Informação
label_information_plural: Informações
-label_please_login: Efectuar acesso
-label_register: Registe-se
+label_please_login: Por favor autentique-se
+label_register: Registar
label_password_lost: Perdi a palavra-chave
-label_home: Página inicial
-label_my_page: Página pessoal
+label_home: Página Inicial
+label_my_page: Página Pessoal
label_my_account: Minha conta
label_my_projects: Meus projectos
label_administration: Administração
label_login: Entrar
label_logout: Sair
label_help: Ajuda
-label_reported_issues: Tarefas reportadas
-label_assigned_to_me_issues: Tarefas atribuídas
+label_reported_issues: Tarefas criadas
+label_assigned_to_me_issues: Tarefas atribuídas a mim
label_last_login: Último acesso
label_last_updates: Última alteração
label_last_updates_plural: %d Últimas alterações
label_registered_on: Registado em
label_activity: Actividade
+label_overall_activity: Actividade geral
label_new: Novo
label_logged_as: Ligado como
label_environment: Ambiente
@@ -251,11 +301,13 @@ 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: Subprojectos
-label_min_max_length: Tamanho min-max
+label_subproject_plural: Sub-projectos
+label_and_its_subprojects: %s e sub-projectos
+label_min_max_length: Tamanho mínimo-máximo
label_list: Lista
label_date: Data
label_integer: Inteiro
+label_float: Decimal
label_boolean: Booleano
label_string: Texto
label_text: Texto longo
@@ -266,37 +318,40 @@ label_download_plural: %d Downloads
label_no_data: Sem dados para mostrar
label_change_status: Mudar estado
label_history: Histórico
-label_attachment: Arquivo
-label_attachment_new: Novo arquivo
-label_attachment_delete: Apagar arquivo
-label_attachment_plural: Arquivos
+label_attachment: Ficheiro
+label_attachment_new: Novo ficheiro
+label_attachment_delete: Apagar ficheiro
+label_attachment_plural: Ficheiros
+label_file_added: Ficheiro adicionado
label_report: Relatório
-label_report_plural: Relatório
-label_news: Notícias
-label_news_new: Adicionar notícias
+label_report_plural: Relatórios
+label_news: Notícia
+label_news_new: Nova notícia
label_news_plural: Notícias
label_news_latest: Últimas notícias
label_news_view_all: Ver todas as notícias
-label_change_log: Log de mudanças
+label_news_added: Notícia adicionada
+label_change_log: Change log
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_export_to: 'Também disponível em:'
label_read: Ler...
label_public_projects: Projectos públicos
-label_open_issues: Aberto
-label_open_issues_plural: Abertos
-label_closed_issues: Fechado
-label_closed_issues_plural: Fechados
+label_open_issues: aberto
+label_open_issues_plural: abertos
+label_closed_issues: fechado
+label_closed_issues_plural: fechados
label_total: Total
label_permissions: Permissões
label_current_status: Estado actual
-label_new_statuses_allowed: Novo estado permitido
+label_new_statuses_allowed: Novos estados permitidos
label_all: todos
label_none: nenhum
+label_nobody: ninguém
label_next: Próximo
label_previous: Anterior
label_used_by: Usado por
@@ -304,65 +359,77 @@ label_details: Detalhes
label_add_note: Adicionar nota
label_per_page: Por página
label_calendar: Calendário
-label_months_from: Meses de
+label_months_from: meses de
label_gantt: Gantt
label_internal: Interno
-label_last_changes: últimas %d mudanças
-label_change_view_all: Mostrar todas as mudanças
-label_personalize_page: Personalizar página
+label_last_changes: últimas %d alterações
+label_change_view_all: Ver todas as alterações
+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_comment_delete: Apagar comentários
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: é
-label_not_equals: não e
-label_in_less_than: é maior que
-label_in_more_than: é menor que
+label_not_equals: não é
+label_in_less_than: em menos de
+label_in_more_than: em mais de
label_in: em
label_today: hoje
+label_all_time: sempre
+label_yesterday: ontem
label_this_week: esta semana
-label_less_than_ago: faz menos de
-label_more_than_ago: faz mais de
+label_last_week: semana passada
+label_last_n_days: últimos %d dias
+label_this_month: este mês
+label_last_month: mês passado
+label_this_year: este ano
+label_date_range: Date range
+label_less_than_ago: menos de dias atrás
+label_more_than_ago: mais de dias atrás
label_ago: dias atrás
label_contains: contém
label_not_contains: não contém
label_day_plural: dias
label_repository: Repositório
-label_browse: Procurar
-label_modification: %d mudança
-label_modification_plural: %d mudanças
+label_repository_plural: Repositórios
+label_browse: Navegar
+label_modification: %d alteração
+label_modification_plural: %d alterações
label_revision: Revisão
label_revision_plural: Revisões
+label_associated_revisions: Revisões associadas
label_added: adicionado
label_modified: modificado
+label_copied: copiado
+label_renamed: renomeado
label_deleted: apagado
label_latest_revision: Última revisão
label_latest_revision_plural: Últimas revisões
label_view_revisions: Ver revisões
label_max_size: Tamanho máximo
-label_on: em
+label_on: 'em'
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: Termina em
-label_roadmap_overdue: %s atrasado
-label_roadmap_no_issues: Sem tarefas para essa versão
+label_roadmap_due_in: Termina em %s
+label_roadmap_overdue: Atrasado %s
+label_roadmap_no_issues: Sem tarefas para esta versão
label_search: Procurar
label_result_plural: Resultados
label_all_words: Todas as palavras
label_wiki: Wiki
-label_wiki_edit: Editar Wiki
-label_wiki_edit_plural: Editar Wiki's
-label_wiki_page: Página de Wiki
-label_wiki_page_plural: Páginas de Wiki
+label_wiki_edit: Edição da Wiki
+label_wiki_edit_plural: Edições da Wiki
+label_wiki_page: Página da Wiki
+label_wiki_page_plural: Páginas da Wiki
label_index_by_title: Ãndice por título
label_index_by_date: Ãndice por data
label_current_version: Versão actual
@@ -373,7 +440,7 @@ label_issue_tracking: Tarefas
label_spent_time: Tempo gasto
label_f_hour: %.2f hora
label_f_hour_plural: %.2f horas
-label_time_tracking: Tempo trabalhado
+label_time_tracking: Registo de tempo
label_change_plural: Mudanças
label_statistics: Estatísticas
label_commits_per_month: Commits por mês
@@ -385,59 +452,94 @@ label_options: Opções
label_copy_workflow_from: Copiar workflow de
label_permissions_report: Relatório de permissões
label_watched_issues: Tarefas observadas
-label_related_issues: tarefas relacionadas
+label_related_issues: Tarefas relacionadas
label_applied_status: Estado aplicado
-label_loading: Carregando...
+label_loading: A carregar...
label_relation_new: Nova relação
label_relation_delete: Apagar relação
-label_relates_to: relacionado à
-label_duplicates: duplicadas
-label_blocks: bloqueios
+label_relates_to: relacionado a
+label_duplicates: duplica
+label_duplicated_by: duplicado por
+label_blocks: bloqueia
label_blocked_by: bloqueado por
-label_precedes: procede
+label_precedes: precede
label_follows: segue
-label_end_to_start: fim ao início
-label_end_to_end: fim ao fim
-label_start_to_start: início ao início
-label_start_to_end: início ao fim
+label_end_to_start: fim a início
+label_end_to_end: fim a fim
+label_start_to_start: início a início
+label_start_to_end: início a fim
label_stay_logged_in: Guardar sessão
-label_disabled: desactivar
-label_show_completed_versions: Ver as versões completas
-label_me: Eu
+label_disabled: desactivado
+label_show_completed_versions: Mostrar versões acabadas
+label_me: eu
label_board: Forum
label_board_new: Novo forum
-label_board_plural: Foruns
-label_topic_plural: Topicos
+label_board_plural: Forums
+label_topic_plural: Tópicos
label_message_plural: Mensagens
-label_message_last: Ultima mensagem
+label_message_last: Última mensagem
label_message_new: Nova mensagem
+label_message_posted: Mensagem adicionada
label_reply_plural: Respostas
label_send_information: Enviar dados da conta para o utilizador
label_year: Ano
-label_month: Mês
+label_month: mês
label_week: Semana
label_date_from: De
label_date_to: Para
-label_language_based: Baseado na língua
+label_language_based: Baseado na língua do utilizador
label_sort_by: Ordenar por %s
-label_send_test_email: Enviar e-mail de teste
-label_feeds_access_key_created_on: Chave RSS criada à %s atrás
+label_send_test_email: enviar um e-mail de teste
+label_feeds_access_key_created_on: Chave RSS criada há %s atrás
label_module_plural: Módulos
-label_added_time_by: Adicionado por %s %s atrás
-label_updated_time: Actualizado %s atrás
+label_added_time_by: Adicionado por %s há %s atrás
+label_updated_time: Alterado há %s atrás
label_jump_to_a_project: Ir para o projecto...
+label_file_plural: Ficheiros
+label_changeset_plural: Changesets
+label_default_columns: Colunas por omissão
+label_no_change_option: (sem alteração)
+label_bulk_edit_selected_issues: Editar tarefas seleccionadas em conjunto
+label_theme: Tema
+label_default: Padrão
+label_search_titles_only: Procurar apenas em títulos
+label_user_mail_option_all: "Para qualquer evento em todos os meus projectos"
+label_user_mail_option_selected: "Para qualquer evento apenas nos projectos seleccionados..."
+label_user_mail_option_none: "Apenas para coisas que esteja a observar ou esteja envolvido"
+label_user_mail_no_self_notified: "Não quero ser notificado de alterações feitas por mim"
+label_registration_activation_by_email: Activação da conta por e-mail
+label_registration_manual_activation: Activação manual da conta
+label_registration_automatic_activation: Activação automática da conta
+label_display_per_page: 'Por página: %s'
+label_age: Idade
+label_change_properties: Mudar propriedades
+label_general: Geral
+label_more: Mais
+label_scm: SCM
+label_plugins: Extensões
+label_ldap_authentication: Autenticação LDAP
+label_downloads_abbr: D/L
+label_optional_description: Descrição opcional
+label_add_another_file: Adicionar outro ficheiro
+label_preferences: Preferências
+label_chronological_order: Em ordem cronológica
+label_reverse_chronological_order: Em ordem cronológica inversa
+label_planning: Planeamento
+label_incoming_emails: E-mails a chegar
+label_generate_key: Gerar uma chave
+label_issue_watchers: Observadores
button_login: Entrar
-button_submit: Enviar
+button_submit: Submeter
button_save: Guardar
-button_check_all: Marcar todos
-button_uncheck_all: Desmarcar todos
+button_check_all: Marcar tudo
+button_uncheck_all: Desmarcar tudo
button_delete: Apagar
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
@@ -453,192 +555,157 @@ button_sort: Ordenar
button_log_time: Tempo de trabalho
button_rollback: Voltar para esta versão
button_watch: Observar
-button_unwatch: Não observar
+button_unwatch: Deixar de observar
button_reply: Responder
button_archive: Arquivar
button_unarchive: Desarquivar
button_reset: Reinicializar
button_rename: Renomear
+button_change_password: Mudar palavra-chave
+button_copy: Copiar
+button_annotate: Anotar
+button_update: Actualizar
+button_configure: Configurar
+button_quote: Citar
status_active: activo
-status_registered: registrado
+status_registered: registado
status_locked: bloqueado
-text_select_mail_notifications: Seleccionar as acções que originam uma notificação por e-mail
+text_select_mail_notifications: Seleccionar as acções que originam uma notificação por e-mail.
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 apagar este projecto e todos os dados relacionados?
-text_workflow_edit: Seleccione uma regra e um tipo de tarefa para editar o workflow
-text_are_you_sure: Você tem certeza?
-text_journal_changed: alterado de %s para %s
-text_journal_set_to: alterar para %s
+text_project_destroy_confirmation: Tem a certeza que deseja apagar o projecto e todos os dados relacionados?
+text_subprojects_destroy_warning: 'O(s) seu(s) sub-projecto(s): %s também será/serão apagado(s).'
+text_workflow_edit: Seleccione um papel e um tipo de tarefa para editar o workflow
+text_are_you_sure: Tem a certeza?
+text_journal_changed: mudado de %s para %s
+text_journal_set_to: alterado para %s
text_journal_deleted: apagado
-text_tip_task_begin_day: tarefa começa neste dia
-text_tip_task_end_day: tarefa termina neste dia
-text_tip_task_begin_end_day: tarefa começa e termina neste dia
-text_project_identifier_info: 'Letras minúsculas (a-z), números e traços permitido.<br />Uma vez gravado, o identificador nao pode ser mudado.'
-text_caracters_maximum: %d máximo de caracteres
-text_length_between: Tamanho entre %d e %d caracteres.
-text_tracker_no_workflow: Sem workflow definido para este tipo.
+text_tip_task_begin_day: tarefa a começar neste dia
+text_tip_task_end_day: tarefa a acabar neste dia
+text_tip_task_begin_end_day: tarefa a começar e acabar neste dia
+text_project_identifier_info: 'Apenas são permitidos letras minúsculas (a-z), números e hífens.<br />Uma vez guardado, o identificador não poderá ser alterado.'
+text_caracters_maximum: máximo %d caracteres.
+text_caracters_minimum: Deve ter pelo menos %d caracteres.
+text_length_between: Deve ter entre %d e %d caracteres.
+text_tracker_no_workflow: Sem workflow definido para este tipo de tarefa.
text_unallowed_characters: Caracteres não permitidos
text_comma_separated: Permitidos múltiplos valores (separados por vírgula).
-text_issues_ref_in_commit_messages: Referenciando e arrumando tarefas nas mensagens de commit
-text_issue_added: Tarefa %s foi incluída (by %s).
-text_issue_updated: Tarefa %s foi alterada (by %s).
-text_wiki_destroy_confirmation: Tem certeza que quer apagar esta página de wiki e todo o conteudo relacionado?
-text_issue_category_destroy_question: Existem tarefas (%d) associadas a esta categoria. Seleccione uma opção?
-text_issue_category_destroy_assignments: Remover as relações com a categoria
-text_issue_category_reassign_to: Re-associar as tarefas á categoria
+text_issues_ref_in_commit_messages: Referenciando e fechando tarefas em mensagens de commit
+text_issue_added: Tarefa %s foi criada por %s.
+text_issue_updated: Tarefa %s foi actualizada por %s.
+text_wiki_destroy_confirmation: Tem a certeza que deseja apagar este wiki e todo o seu conteúdo?
+text_issue_category_destroy_question: Algumas tarefas (%d) estão atribuídas a esta categoria. O que quer fazer?
+text_issue_category_destroy_assignments: Remover as atribuições à categoria
+text_issue_category_reassign_to: Re-atribuir as tarefas para esta categoria
+text_user_mail_option: "Para projectos não seleccionados, apenas receberá notificações acerca de coisas que está a observar ou está envolvido (ex. tarefas das quais foi o criador ou lhes foram atribuídas)."
+text_no_configuration_data: "Papeis, tipos de tarefas, estados das tarefas e workflows ainda não foram configurados.\nÉ extremamente recomendado carregar as configurações padrão. Será capaz de as modificar depois de estarem carregadas."
+text_load_default_configuration: Carregar as configurações padrão
+text_status_changed_by_changeset: Aplicado no changeset %s.
+text_issues_destroy_confirmation: 'Tem a certeza que deseja apagar a(s) tarefa(s) seleccionada(s)?'
+text_select_project_modules: 'Seleccione os módulos a activar para este projecto:'
+text_default_administrator_account_changed: Conta default de administrador alterada.
+text_file_repository_writable: Repositório de ficheiros com permissões de escrita
+text_rmagick_available: RMagick disponível (opcional)
+text_destroy_time_entries_question: %.02f horas de trabalho foram atribuídas a estas tarefas que vai apagar. O que deseja fazer?
+text_destroy_time_entries: Apagar as horas
+text_assign_time_entries_to_project: Atribuir as horas ao projecto
+text_reassign_time_entries: 'Re-atribuir as horas para esta tarefa:'
+text_user_wrote: '%s escreveu:'
+text_enumeration_destroy_question: '%d objectos estão atribuídos a este valor.'
+text_enumeration_category_reassign_to: 'Re-atribuí-los para este valor:'
+text_email_delivery_not_configured: "Entrega por e-mail não está configurada, e as notificação estão desactivadas.\nConfigure o seu servidor de SMTP em config/email.yml e reinicie a aplicação para activar estas funcionalidades."
-default_role_manager: Gestor de Projecto
-default_role_developper: Desenvolvedor
-default_role_reporter: Analista de Suporte
+default_role_manager: Gestor
+default_role_developper: Programador
+default_role_reporter: Repórter
default_tracker_bug: Bug
-default_tracker_feature: Implementação
+default_tracker_feature: Funcionalidade
default_tracker_support: Suporte
default_issue_status_new: Novo
default_issue_status_assigned: Atribuído
default_issue_status_resolved: Resolvido
-default_issue_status_feedback: Comentário
+default_issue_status_feedback: Feedback
default_issue_status_closed: Fechado
default_issue_status_rejected: Rejeitado
-default_doc_category_user: Documentação do utilizador
+default_doc_category_user: Documentação de utilizador
default_doc_category_tech: Documentação técnica
-default_priority_low: Baixo
+default_priority_low: Baixa
default_priority_normal: Normal
-default_priority_high: Alto
+default_priority_high: Alta
default_priority_urgent: Urgente
-default_priority_immediate: Imediato
-default_activity_design: Desenho
+default_priority_immediate: Imediata
+default_activity_design: Planeamento
default_activity_development: Desenvolvimento
-enumeration_issue_priorities: Prioridade das tarefas
-enumeration_doc_categories: Categorias de documento
-enumeration_activities: Actividades (time tracking)
-label_file_plural: Arquivos
-label_changeset_plural: Alterações
-field_column_names: Colunas
-label_default_columns: Valores por defeito das colunas
-setting_issue_list_default_columns: Colunas listadas nas tarefas por defeito
-setting_repositories_encodings: Codificação dos repositórios
-notice_no_issue_selected: "Nenhuma tarefa seleccionada! Por favor, selecione uma tarefa para editar."
-label_bulk_edit_selected_issues: Edição em massa das tarefas seleccionadas
-label_no_change_option: (Sem alterações)
-notice_failed_to_save_issues: "Erro ao gravar %d tarefa(s) no %d seleccionado: %s."
-label_theme: Tema
-label_default: Padrão
-label_search_titles_only: Procurar apenas nos títulos
-label_nobody: desconhecido
-button_change_password: Alterar palavra-chave
-text_user_mail_option: "Para os projectos não seleccionados, irá receber e-mails de notificação apenas de eventos que esteja a observar ou envolvido (isto é, tarefas que é autor ou que estão atribuidas a si)."
-label_user_mail_option_selected: "Qualquer evento que ocorra nos projectos selecionados apenas..."
-label_user_mail_option_all: "Todos eventos em todos os projectos"
-label_user_mail_option_none: "Apenas eventos que sou observador ou que estou envolvido"
-setting_emails_footer: Rodapé do e-mail
-label_float: Float
-button_copy: Copiar
-mail_body_account_information_external: Pode utilizar a sua conta "%s" para entrar.
-mail_body_account_information: Informação da sua conta de acesso
-setting_protocol: Protocolo
-label_user_mail_no_self_notified: "Não quero ser notificado de alterações efectuadas por mim"
-setting_time_format: Formato da hora
-label_registration_activation_by_email: Activação de conta de acesso por e-mail
-mail_subject_account_activation_request: %s pedido de activação de conta de acesso
-mail_body_account_activation_request: 'Novo utilizador (%s) registado. Conta de acesso pendente a aguardar validação:'
-label_registration_automatic_activation: Activação de conta de acesso automático
-label_registration_manual_activation: activação de conta de acesso manual
-notice_account_pending: "Conta de acesso criada, mas pendente para validação do administrador"
-field_time_zone: Fuso horário
-text_caracters_minimum: Tem que ter no minimo %d caracteres.
-setting_bcc_recipients: Esconder endereços destinos de e-mail (bcc)
-button_annotate: Anotar
-label_issues_by: tarefas por %s
-field_searchable: Pesquisável
-label_display_per_page: 'Por página: %s'
-setting_per_page_options: Objects per page options
-label_age: Idade
-notice_default_data_loaded: Configuração inicial por defeito carregada.
-text_load_default_configuration: Inserir configuração por defeito inicial
-text_no_configuration_data: "Regras, trackers, estado das tarefas e workflow ainda não foram configurados.\nÉ recomendado que carregue a configuração por defeito. Posteriormente poderá alterar a configuração carregada."
-error_can_t_load_default_data: "Configuração inical por defeito não pode ser carregada: %s"
-button_update: Actualizar
-label_change_properties: Alterar propriedades
-label_general: Geral
-label_repository_plural: Repositórios
-label_associated_revisions: Versões associadas
-setting_user_format: Formato para visualizar o utilizador
-text_status_changed_by_changeset: Applied in changeset %s.
-label_more: mais
-text_issues_destroy_confirmation: 'Tem certeza que deseja apagar as tarefa(s) seleccionada(s)?'
-label_scm: SCM
-text_select_project_modules: 'Seleccione o(s) modulo(s) que deseja activar para o projecto:'
-label_issue_added: Tarefa adicionada
-label_issue_updated: Tarefa alterada
-label_document_added: Documento adicionado
-label_message_posted: Mensagem adicionada
-label_file_added: Arquivo adicionado
-label_news_added: Noticia adicionada
-project_module_boards: Boards
-project_module_issue_tracking: Tracking de tarefas
-project_module_wiki: Wiki
-project_module_files: Ficheiros
-project_module_documents: Documentos
-project_module_repository: Repositório
-project_module_news: Noticias
-project_module_time_tracking: Time tracking
-text_file_repository_writable: Repositório de ficheiros com permissões de escrita
-text_default_administrator_account_changed: Dados da conta de administrador padrão alterados
-text_rmagick_available: RMagick disponível (opcional)
-button_configure: Configurar
-label_plugins: Plugins
-label_ldap_authentication: autenticação por LDAP
-label_downloads_abbr: D/L
-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: ultima semana
-label_yesterday: ontem
-label_last_month: último mês
-label_add_another_file: Adicionar outro ficheiro
-label_optional_description: Descrição opcional
-text_destroy_time_entries_question: %.02f horas reportadas nesta tarefa. Estas horas serão eliminada. Continuar?
-error_issue_not_found_in_project: 'Esta tarefa não foi encontrada ou não pertence a este projecto'
-text_assign_time_entries_to_project: Associar as horas reportadas ao projecto
-text_destroy_time_entries: Apagar as horas reportadas
-text_reassign_time_entries: 'Re-associar as horas reportadas a esta tarefa:'
-setting_activity_days_default: dias visualizados da actividade do projecto
-label_chronological_order: Ordem cronológica
-field_comments_sorting: Apresentar comentários
-label_reverse_chronological_order: Ordem cronológica inversa
-label_preferences: Preferências
-setting_display_subprojects_issues: Ver tarefas dos subprojectos no projecto principal por defeito
-label_overall_activity: Ver todas as actividades
-setting_default_projects_public: Novos projectos são classificados como publicos por defeito
-error_scm_annotate: "Esta entrada não existe ou não pode ser alterada."
-label_planning: Plano
-text_subprojects_destroy_warning: 'O(s) subprojecto(s): %s serão tambem apagados.'
-label_and_its_subprojects: %s e os subprojectos
-mail_body_reminder: "%d tarefa(s) que estão associadas a si e que tem de ser feitas nos próximos %d dias:"
-mail_subject_reminder: "%d tarefa(s) para fazer nos próximos dias"
-text_user_wrote: '%s escreveu:'
-label_duplicated_by: duplicado por
-setting_enabled_scm: Activar SCM
-text_enumeration_category_reassign_to: 're-associar a este valor:'
-text_enumeration_destroy_question: '%d objectos estão associados com este valor.'
-label_incoming_emails: Receber e-mails
-label_generate_key: Gerar uma chave
-setting_mail_handler_api_enabled: Activar Serviço Web para as receber mensagens de e-mail
-setting_mail_handler_api_key: Chave da API
-text_email_delivery_not_configured: "Servidor de e-mail por configurar e notificações inactivas.\nConfigure o servidor de e-mail (SMTP) no ficheiro config/email.yml e re-inicie a aplicação."
-field_parent_title: Página origem
-label_issue_watchers: Observadores
-setting_commit_logs_encoding: Guardar codificação das mensagens de log
-button_quote: Comentário
-setting_sequential_project_identifiers: Gerar identificador sequencial
-notice_unable_delete_version: Impossível apagar esta versão
-label_renamed: renomeado
-label_copied: copiado
-field_cache: Local cache
-setting_repositories_cache_directory: Cache directory for repositories
+enumeration_issue_priorities: Prioridade de tarefas
+enumeration_doc_categories: Categorias de documentos
+enumeration_activities: Actividades (Registo de tempo)
+setting_plain_text_mail: Apenas texto simples (sem HTML)
+permission_view_files: Ver ficheiros
+permission_edit_issues: Editar tarefas
+permission_edit_own_time_entries: Editar horas pessoais
+permission_manage_public_queries: Gerir queries públicas
+permission_add_issues: Adicionar tarefas
+permission_log_time: Registar tempo gasto
+permission_view_changesets: Ver changesets
+permission_view_time_entries: Ver tempo gasto
+permission_manage_versions: Gerir versões
+permission_manage_wiki: Gerir wiki
+permission_manage_categories: Gerir categorias de tarefas
+permission_protect_wiki_pages: Proteger páginas de wiki
+permission_comment_news: Comentar notícias
+permission_delete_messages: Apagar mensagens
+permission_select_project_modules: Seleccionar módulos do projecto
+permission_manage_documents: Gerir documentos
+permission_edit_wiki_pages: Editar páginas de wiki
+permission_add_issue_watchers: Adicionar observadores
+permission_view_gantt: ver diagrama de Gantt
+permission_move_issues: Mover tarefas
+permission_manage_issue_relations: Gerir relações de tarefas
+permission_delete_wiki_pages: Apagar páginas de wiki
+permission_manage_boards: Gerir forums
+permission_delete_wiki_pages_attachments: Apagar anexos
+permission_view_wiki_edits: Ver histórico da wiki
+permission_add_messages: Submeter mensagens
+permission_view_messages: Ver mensagens
+permission_manage_files: Gerir ficheiros
+permission_edit_issue_notes: Editar notas de tarefas
+permission_manage_news: Gerir notícias
+permission_view_calendar: Ver calendário
+permission_manage_members: Gerir membros
+permission_edit_messages: Editar mensagens
+permission_delete_issues: Apagar tarefas
+permission_view_issue_watchers: Ver lista de observadores
+permission_manage_repository: Gerir repositório
+permission_commit_access: Acesso a submissão
+permission_browse_repository: Navegar em repositório
+permission_view_documents: Ver documentos
+permission_edit_project: Editar projecto
+permission_add_issue_notes: Adicionar notas a tarefas
+permission_save_queries: Guardar queries
+permission_view_wiki_pages: Ver wiki
+permission_rename_wiki_pages: Renomear páginas de wiki
+permission_edit_time_entries: Editar entradas de tempo
+permission_edit_own_issue_notes: Editar as prórpias notas
+setting_gravatar_enabled: Utilizar icons Gravatar
+label_example: Exemplo
+text_repository_usernames_mapping: "Seleccionar ou actualizar o utilizador de Redmine mapeado a cada nome de utilizador encontrado no repositório.\nUtilizadores com o mesmo nome de utilizador ou email no Redmine e no repositório são mapeados automaticamente."
+permission_edit_own_messages: Editar as próprias mensagens
+permission_delete_own_messages: Apagar as próprias mensagens
+label_user_activity: "Actividade de %s"
+label_updated_time_by: Actualizado por %s há %s
+text_diff_truncated: '... Este diff foi truncado porque excede o tamanho máximo que pode ser mostrado.'
+setting_diff_max_lines_displayed: Número máximo de linhas de diff mostradas
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/ro.yml b/lang/ro.yml
index 1f399989c..ac06ccda5 100644
--- a/lang/ro.yml
+++ b/lang/ro.yml
@@ -640,5 +640,70 @@ setting_sequential_project_identifiers: Generate sequential project identifiers
notice_unable_delete_version: Unable to delete version
label_renamed: renamed
label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+setting_plain_text_mail: plain text only (no HTML)
+permission_view_files: View files
+permission_edit_issues: Edit issues
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_public_queries: Manage public queries
+permission_add_issues: Add issues
+permission_log_time: Log spent time
+permission_view_changesets: View changesets
+permission_view_time_entries: View spent time
+permission_manage_versions: Manage versions
+permission_manage_wiki: Manage wiki
+permission_manage_categories: Manage issue categories
+permission_protect_wiki_pages: Protect wiki pages
+permission_comment_news: Comment news
+permission_delete_messages: Delete messages
+permission_select_project_modules: Select project modules
+permission_manage_documents: Manage documents
+permission_edit_wiki_pages: Edit wiki pages
+permission_add_issue_watchers: Add watchers
+permission_view_gantt: View gantt chart
+permission_move_issues: Move issues
+permission_manage_issue_relations: Manage issue relations
+permission_delete_wiki_pages: Delete wiki pages
+permission_manage_boards: Manage boards
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_view_wiki_edits: View wiki history
+permission_add_messages: Post messages
+permission_view_messages: View messages
+permission_manage_files: Manage files
+permission_edit_issue_notes: Edit notes
+permission_manage_news: Manage news
+permission_view_calendar: View calendrier
+permission_manage_members: Manage members
+permission_edit_messages: Edit messages
+permission_delete_issues: Delete issues
+permission_view_issue_watchers: View watchers list
+permission_manage_repository: Manage repository
+permission_commit_access: Commit access
+permission_browse_repository: Browse repository
+permission_view_documents: View documents
+permission_edit_project: Edit project
+permission_add_issue_notes: Add notes
+permission_save_queries: Save queries
+permission_view_wiki_pages: View wiki
+permission_rename_wiki_pages: Rename wiki pages
+permission_edit_time_entries: Edit time logs
+permission_edit_own_issue_notes: Edit own notes
+setting_gravatar_enabled: Use Gravatar user icons
+label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+permission_edit_own_messages: Edit own messages
+permission_delete_own_messages: Delete own messages
+label_user_activity: "%s's activity"
+label_updated_time_by: Updated by %s %s ago
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/ru.yml b/lang/ru.yml
index bcc7121b2..87be71e2c 100644
--- a/lang/ru.yml
+++ b/lang/ru.yml
@@ -13,7 +13,7 @@ actionview_datehelper_time_in_words_hour_about_plural2: около %d чаÑов
actionview_datehelper_time_in_words_hour_about_plural5: около %d чаÑов
actionview_datehelper_time_in_words_hour_about_plural: около %d чаÑов
actionview_datehelper_time_in_words_hour_about_single: около чаÑа
-actionview_datehelper_time_in_words_hour_about: около чаÑа
+actionview_datehelper_time_in_words_hour_about: около %d чаÑа
actionview_datehelper_time_in_words_minute: 1 минута
actionview_datehelper_time_in_words_minute_half: полминуты
actionview_datehelper_time_in_words_minute_less_than: менее минуты
@@ -322,6 +322,7 @@ label_enumeration_new: Ðовое значение
label_enumerations: Справочники
label_environment: Окружение
label_equals: ÑоответÑтвует
+label_example: Пример
label_export_to: ЭкÑпортировать в
label_feed_plural: Вводы
label_feeds_access_key_created_on: Ключ доÑтупа RSS Ñоздан %s назад
@@ -517,8 +518,10 @@ label_total: Ð’Ñего
label_tracker_new: Ðовый трекер
label_tracker_plural: Трекеры
label_tracker: Трекер
-label_updated_time: Обновлен %s назад
+label_updated_time: Обновлено %s назад
+label_updated_time_by: Обновлено %s %s назад
label_used_by: ИÑпользуетÑÑ
+label_user_activity: "ÐктивноÑть Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %s"
label_user_mail_no_self_notified: "Ðе извещать об изменениÑÑ…, которые Ñ Ñделал Ñам"
label_user_mail_option_all: "О вÑех ÑобытиÑÑ… во вÑех моих проектах"
label_user_mail_option_none: "Только о тех ÑобытиÑÑ…, которые Ñ Ð¾Ñ‚Ñлеживаю или в которых Ñ ÑƒÑ‡Ð°Ñтвую"
@@ -578,6 +581,55 @@ notice_successful_delete: Удаление уÑпешно завершено.
notice_successful_update: Обновление уÑпешно завершено.
notice_unable_delete_version: Ðевозможно удалить верÑию.
+permission_view_files: ПроÑмотр файлов
+permission_edit_issues: Редактирование задач
+permission_edit_own_time_entries: Редактирование ÑобÑтвенного учета времени
+permission_manage_public_queries: Управление общими запроÑами
+permission_add_issues: Добавление задач
+permission_log_time: Учет затраченного времени
+permission_view_changesets: ПроÑмотр изменений хранилища
+permission_view_time_entries: ПроÑмотр затраченного времени
+permission_manage_versions: Управление верÑиÑми
+permission_manage_wiki: Управление wiki
+permission_manage_categories: Управление категориÑми задач
+permission_protect_wiki_pages: Блокирование Ñтраниц wiki
+permission_comment_news: Комментирование новоÑтей
+permission_delete_messages: Удаление Ñообщений
+permission_select_project_modules: Выбор модулей проекта
+permission_manage_documents: Управление документами
+permission_edit_wiki_pages: Редактирование Ñтраниц wiki
+permission_add_issue_watchers: Добавление наблюдателей
+permission_view_gantt: ПроÑмотр диаграммы Ганта
+permission_move_issues: ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ð·Ð°Ð´Ð°Ñ‡
+permission_manage_issue_relations: Управление ÑвÑзыванием задач
+permission_delete_wiki_pages: Удаление Ñтраниц wiki
+permission_manage_boards: Управление форумами
+permission_delete_wiki_pages_attachments: Удаление прикрепленных файлов
+permission_view_wiki_edits: ПроÑмотр иÑтории wiki
+permission_add_messages: Отправка Ñообщений
+permission_view_messages: ПроÑмотр Ñообщение
+permission_manage_files: Управление файлами
+permission_edit_issue_notes: Редактирование примечаний
+permission_manage_news: Управление новоÑÑ‚Ñми
+permission_view_calendar: ПроÑмотр календарÑ
+permission_manage_members: Управление учаÑтниками
+permission_edit_messages: Редактирование Ñообщений
+permission_delete_issues: Удаление задач
+permission_view_issue_watchers: ПроÑмотр ÑпиÑка наблюдателей
+permission_manage_repository: Управление хранилищем
+permission_commit_access: Разрешение фикÑации
+permission_browse_repository: ПроÑмотр хранилища
+permission_view_documents: ПроÑмотр документов
+permission_edit_project: Редактирование проектов
+permission_add_issue_notes: Добавление примечаний
+permission_save_queries: Сохранение запроÑов
+permission_view_wiki_pages: ПроÑмотр wiki
+permission_rename_wiki_pages: Переименование Ñтраниц wiki
+permission_edit_time_entries: Редактирование учета времени
+permission_edit_own_issue_notes: Редактирование ÑобÑтвенных примечаний
+permission_edit_own_messages: Редактирование ÑобÑтвенных Ñообщений
+permission_delete_own_messages: Удаление ÑобÑтвенных Ñообщений
+
project_module_boards: Форумы
project_module_documents: Документы
project_module_files: Файлы
@@ -601,10 +653,12 @@ setting_cross_project_issue_relations: Разрешить переÑечение
setting_date_format: Формат даты
setting_default_language: Язык по умолчанию
setting_default_projects_public: Ðовые проекты ÑвлÑÑŽÑ‚ÑÑ Ð¾Ð±Ñ‰ÐµÐ´Ð¾Ñтупными
+setting_diff_max_lines_displayed: МакÑимальное чиÑло Ñтрок Ð´Ð»Ñ diff
setting_display_subprojects_issues: Отображение подпроектов по умолчанию
setting_emails_footer: ПодÑтрочные Ð¿Ñ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Email
setting_enabled_scm: Разрешенные SCM
setting_feeds_limit: Ограничение количеÑтва заголовков Ð´Ð»Ñ RSS потока
+setting_gravatar_enabled: ИÑпользовать аватар Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð· Gravatar
setting_host_name: Ð˜Ð¼Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð°
setting_issue_list_default_columns: Колонки, отображаемые в ÑпиÑке задач по умолчанию
setting_issues_export_limit: Ограничение по ÑкÑпортируемым задачам
@@ -613,6 +667,7 @@ setting_mail_from: email Ð°Ð´Ñ€ÐµÑ Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ информации
setting_mail_handler_api_enabled: Включить веб-ÑÐµÑ€Ð²Ð¸Ñ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñщих Ñообщений
setting_mail_handler_api_key: API ключ
setting_per_page_options: КоличеÑтво Ñтрок на Ñтраницу
+setting_plain_text_mail: Только проÑтой текÑÑ‚ (без HTML)
setting_protocol: Протокол
setting_repositories_encodings: Кодировки хранилища
setting_self_registration: Возможна ÑаморегиÑтрациÑ
@@ -636,6 +691,7 @@ text_comma_separated: ДопуÑтимы неÑколько значений (ч
text_default_administrator_account_changed: Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ админиÑтратора по умолчанию изменена
text_destroy_time_entries_question: Ð’Ñ‹ ÑобираетеÑÑŒ удалить %.02f чаÑа(ов) прикрепленных за Ñтой задачей.
text_destroy_time_entries: Удалить зарегиÑтрированное времÑ
+text_diff_truncated: '... Этот diff ограничен, так как превышает макÑимальный отображаемый размер.'
text_email_delivery_not_configured: "Параметры работы Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð²Ñ‹Ð¼ Ñервером не наÑтроены и Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ email не активна.\nÐаÑтроить параметры Ð´Ð»Ñ Ð’Ð°ÑˆÐµÐ³Ð¾ SMTP-Ñервера Ð’Ñ‹ можете в файле config/email.yml. Ð”Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ перезапуÑтите приложение."
text_enumeration_category_reassign_to: 'Ðазначить им Ñледующее значение:'
text_enumeration_destroy_question: '%d объект(а,ов) ÑвÑзаны Ñ Ñтим значением.'
@@ -658,6 +714,7 @@ text_project_destroy_confirmation: Ð’Ñ‹ наÑтаиваете на удален
text_project_identifier_info: 'ДопуÑтимы Ñтрочные буквы (a-z), цифры и дефиÑ.<br />Сохраненный идентификатор не может быть изменен.'
text_reassign_time_entries: 'ПеренеÑти зарегиÑтрированное Ð²Ñ€ÐµÐ¼Ñ Ð½Ð° Ñледующую задачу:'
text_regexp_info: напр. ^[A-Z0-9]+$
+text_repository_usernames_mapping: "Выберите или обновите Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Redmine, ÑвÑзанного Ñ Ð½Ð°Ð¹Ð´ÐµÐ½Ð½Ñ‹Ð¼Ð¸ именами в журнале хранилица.\nПользователи Ñ Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð²Ñ‹Ð¼Ð¸ именами или email в Redmine и хранилище ÑвÑзываютÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки."
text_rmagick_available: ДоÑтупно иÑпользование RMagick (опционально)
text_select_mail_notifications: Выберите дейÑтвиÑ, на которые будет отÑылатьÑÑ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ðµ на Ñлектронную почту.
text_select_project_modules: 'Выберите модули, которые будут иÑпользованы в проекте:'
@@ -670,8 +727,18 @@ text_tracker_no_workflow: Ð”Ð»Ñ Ñтого трекера поÑледоватÐ
text_unallowed_characters: Запрещенные Ñимволы
text_user_mail_option: "Ð”Ð»Ñ Ð½ÐµÐ²Ñ‹Ð±Ñ€Ð°Ð½Ð½Ñ‹Ñ… проектов, Ð’Ñ‹ будете получать ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ о том что проÑматриваете или в чем учаÑтвуете (например, вопроÑÑ‹, автором которых Ð’Ñ‹ ÑвлÑетеÑÑŒ или которые Вам назначены)."
text_user_wrote: '%s напиÑал(а):'
-text_wiki_destroy_confirmation: Ð’Ñ‹ уверены, что хотите удалить данную Wiki и вÑе Ñодержимое?
+text_wiki_destroy_confirmation: Ð’Ñ‹ уверены, что хотите удалить данную Wiki и вÑе ее Ñодержимое?
text_workflow_edit: Выберите роль и трекер Ð´Ð»Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ÑледовательноÑти ÑоÑтоÑний
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+text_plugin_assets_writable: Каталог Ð´Ð»Ñ Ð¿Ð»Ð°Ð³Ð¸Ð½Ð¾Ð² доÑтупен по запиÑи
+warning_attachments_not_saved: "%d файл(ов) невозможно Ñохранить."
+button_create_and_continue: Создать и продолжить
+text_custom_field_possible_values_info: 'По одному значению в каждой Ñтроке'
+label_display: Отображение
+field_editable: Редактируемый
+
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/sk.yml b/lang/sk.yml
new file mode 100644
index 000000000..351173a32
--- /dev/null
+++ b/lang/sk.yml
@@ -0,0 +1,714 @@
+# SK translation by Stanislav Pach | stano.pach@seznam.cz
+
+
+_gloc_rule_default: '|n| n==1 ? "" : "_plural" '
+
+actionview_datehelper_select_day_prefix:
+actionview_datehelper_select_month_names: Január,Február,Marec,Apríl,Máj,Jún,Júl,August,September,Október,November,December
+actionview_datehelper_select_month_names_abbr: Jan,Feb,Mar,Apr,Máj,Jún,Júl,Aug,Sep,Okt,Nov,Dec
+actionview_datehelper_select_month_prefix:
+actionview_datehelper_select_year_prefix:
+actionview_datehelper_time_in_words_day: 1 deň
+actionview_datehelper_time_in_words_day_plural: %d dní/dňami
+actionview_datehelper_time_in_words_hour_about: asi hodinou
+actionview_datehelper_time_in_words_hour_about_plural: asi %d hodinami
+actionview_datehelper_time_in_words_hour_about_single: asi hodinou
+actionview_datehelper_time_in_words_minute: 1 minútou
+actionview_datehelper_time_in_words_minute_half: pol minútou
+actionview_datehelper_time_in_words_minute_less_than: menej ako minútou
+actionview_datehelper_time_in_words_minute_plural: %d minútami
+actionview_datehelper_time_in_words_minute_single: 1 minútou
+actionview_datehelper_time_in_words_second_less_than: menej ako sekundou
+actionview_datehelper_time_in_words_second_less_than_plural: menej ako %d sekundami
+actionview_instancetag_blank_option: Prosím vyberte
+
+activerecord_error_inclusion: nieje zahrnuté v zozname
+activerecord_error_exclusion: je rezervované
+activerecord_error_invalid: je neplatné
+activerecord_error_confirmation: sa nezhoduje s potvrdením
+activerecord_error_accepted: musí byť akceptované
+activerecord_error_empty: nemôže byť prázdne
+activerecord_error_blank: nemôže byť prázdne
+activerecord_error_too_long: je príliš dlhé
+activerecord_error_too_short: je príliš krátke
+activerecord_error_wrong_length: má chybnú dĺžku
+activerecord_error_taken: je už použité
+activerecord_error_not_a_number: nieje Äíslo
+activerecord_error_not_a_date: nieje platný dátum
+activerecord_error_greater_than_start_date: musí byÅ¥ väÄší ako poÄiatoÄný dátum
+activerecord_error_not_same_project: nepatrí rovnakému projektu
+activerecord_error_circular_dependency: Tento vzťah by vytvoril cyklickú závislosť
+
+general_fmt_age: %d rok
+general_fmt_age_plural: %d rokov
+general_fmt_date: %%m/%%d/%%Y
+general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p
+general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
+general_fmt_time: %%I:%%M %%p
+general_text_No: 'Nie'
+general_text_Yes: 'Ãno'
+general_text_no: 'nie'
+general_text_yes: 'áno'
+general_lang_name: 'Slovensky'
+general_csv_separator: ','
+general_csv_decimal_separator: '.'
+general_csv_encoding: UTF-8
+general_pdf_encoding: UTF-8
+general_day_names: Pondelok,Utorok,Streda,Štvrtok,Piatok,Sobota,Nedeľa
+general_first_day_of_week: '1'
+
+notice_account_updated: ÚÄet bol úspeÅ¡ne zmenený.
+notice_account_invalid_creditentials: Chybné meno alebo heslo
+notice_account_password_updated: Heslo bolo úspešne zmenené.
+notice_account_wrong_password: Chybné heslo
+notice_account_register_done: ÚÄet bol úspeÅ¡ne vytvorený. Pre aktiváciu úÄtu kliknite na odkaz v emailu, ktorý vam bol zaslaný.
+notice_account_unknown_email: Neznámy uživateľ.
+notice_can_t_change_password: Tento úÄet používa externú autentifikáciu. Tu heslo zmeniÅ¥ nemôžete.
+notice_account_lost_email_sent: Bol vám zaslaný email s inštrukciami ako si nastavite nové heslo.
+notice_account_activated: Váš úÄet bol aktivovaný. Teraz se môžete prihlásiÅ¥.
+notice_successful_create: Úspešne vytvorené.
+notice_successful_update: Úspešne aktualizované.
+notice_successful_delete: Úspešne odstránené.
+notice_successful_connection: Úspešne pripojené.
+notice_file_not_found: Stránka, ktorú se snažíte zobraziť, neexistuje alebo bola smazaná.
+notice_locking_conflict: Údaje boli zmenené iným užívateľom.
+notice_scm_error: Položka a/alebo revízia neexistuje v repository.
+notice_not_authorized: Nemáte dostatoÄné práva pre zobrazenie tejto stránky.
+notice_email_sent: Na adresu %s bol odeslaný email
+notice_email_error: Pri odosielaní emailu nastala chyba (%s)
+notice_feeds_access_key_reseted: Váš klÃºÄ pre prístup k Atomu bol resetovaný.
+notice_failed_to_save_issues: "Nastala chyba pri ukládaní %d úloh na %d zvolený: %s."
+notice_no_issue_selected: "Nebola zvolená žiadná úloha. Prosím, zvoľte úlohy, ktoré chcete editovať"
+notice_account_pending: "Váš úÄet bol vytvorený, teraz Äaká na schválenie administrátorom."
+notice_default_data_loaded: Výchozia konfigurácia úspešne nahraná.
+
+error_can_t_load_default_data: "Výchozia konfigurácia nebola nahraná: %s"
+error_scm_not_found: "Položka a/alebo revízia neexistuje v repository."
+error_scm_command_failed: "Pri pokuse o prístup k repository došlo k chybe: %s"
+error_issue_not_found_in_project: 'Úloha nebola nájdená alebo nepatrí k tomuto projektu'
+
+mail_subject_lost_password: Vaše heslo (%s)
+mail_body_lost_password: 'Pre zmenu vašeho hesla kliknite na následujúci odkaz:'
+mail_subject_register: Aktivácia úÄtu (%s)
+mail_body_register: 'Pre aktiváciu vaÅ¡eho úÄtu kliknite na následujúci odkaz:'
+mail_body_account_information_external: Pomocou vaÅ¡eho úÄtu "%s" se môžete prihlásiÅ¥.
+mail_body_account_information: Informácie o vaÅ¡om úÄte
+mail_subject_account_activation_request: Aktivácia %s úÄtu
+mail_body_account_activation_request: Bol zaregistrovaný nový uživateľ "%s". Aktivácia jeho úÄtu závisí na vaÅ¡om potvrdení.
+
+gui_validation_error: 1 chyba
+gui_validation_error_plural: %d chyb(y)
+
+field_name: Názov
+field_description: Popis
+field_summary: Prehľad
+field_is_required: Povinné pole
+field_firstname: Meno
+field_lastname: Priezvisko
+field_mail: Email
+field_filename: Súbor
+field_filesize: Veľkosť
+field_downloads: Stiahnuté
+field_author: Autor
+field_created_on: Vytvorené
+field_updated_on: Aktualizované
+field_field_format: Formát
+field_is_for_all: Pre všetky projekty
+field_possible_values: Možné hodnoty
+field_regexp: Regulérny výraz
+field_min_length: Minimálna dĺžka
+field_max_length: Maximálna dĺžka
+field_value: Hodnota
+field_category: Kategória
+field_title: Názov
+field_project: Projekt
+field_issue: Úloha
+field_status: Stav
+field_notes: Poznámka
+field_is_closed: Úloha uzavretá
+field_is_default: Východzí stav
+field_tracker: Fronta
+field_subject: Predmet
+field_due_date: Uzavrieť do
+field_assigned_to: Priradené
+field_priority: Priorita
+field_fixed_version: Priradené k verzii
+field_user: Užívateľ
+field_role: Rola
+field_homepage: Domovská stránka
+field_is_public: Verejný
+field_parent: Nadradený projekt
+field_is_in_chlog: Úlohy zobrazené v rozdielovom logu
+field_is_in_roadmap: Úlohy zobrazené v pláne
+field_login: Login
+field_mail_notification: Emailové oznámenie
+field_admin: Administrátor
+field_last_login_on: Posledné prihlásenie
+field_language: Jazyk
+field_effective_date: Dátum
+field_password: Heslo
+field_new_password: Nové heslo
+field_password_confirmation: Potvrdenie
+field_version: Verzia
+field_type: Typ
+field_host: Host
+field_port: Port
+field_account: ÚÄet
+field_base_dn: Base DN
+field_attr_login: Prihlásenie (atribut)
+field_attr_firstname: Meno (atribut)
+field_attr_lastname: Priezvisko (atribut)
+field_attr_mail: Email (atribut)
+field_onthefly: Automatické vytváranie užívateľov
+field_start_date: ZaÄiatok
+field_done_ratio: %% Hotovo
+field_auth_source: AutentifikaÄný mód
+field_hide_mail: Nezobrazovať môj email
+field_comments: Komentár
+field_url: URL
+field_start_page: Výchozia stránka
+field_subproject: Podprojekt
+field_hours: hodiny
+field_activity: Aktivita
+field_spent_on: Dátum
+field_identifier: Identifikátor
+field_is_filter: Použíť ako filter
+field_issue_to_id: Súvisiaca úloha
+field_delay: Oneskorenie
+field_assignable: Úlohy môžu byť priradené tejto roli
+field_redirect_existing_links: Presmerovať existujúce odkazy
+field_estimated_hours: Odhadovaná doba
+field_column_names: Stĺpce
+field_time_zone: Časové pásmo
+field_searchable: Umožniť vyhľadávanie
+field_default_value: Východzia hodnota
+field_comments_sorting: Zobraziť komentáre
+
+setting_app_title: Názov aplikácie
+setting_app_subtitle: Podtitulok aplikácie
+setting_welcome_text: Uvítací text
+setting_default_language: Východzí jazyk
+setting_login_required: Auten. vyžadovaná
+setting_self_registration: Povolenie vlastnej registrácie
+setting_attachment_max_size: Maximálna veľkosť prílohy
+setting_issues_export_limit: Limit pre export úloh
+setting_mail_from: Odosielať emaily z adresy
+setting_bcc_recipients: Príjemci skrytej kópie (bcc)
+setting_host_name: Hostname
+setting_text_formatting: Formátovanie textu
+setting_wiki_compression: Kompresia histórie Wiki
+setting_feeds_limit: Limit zobrazených položiek (Atom feed)
+setting_default_projects_public: Nové projekty nastavovať ako verejné
+setting_autofetch_changesets: Automatický prenos zmien
+setting_sys_api_enabled: Povolit WS pre správu repozitory
+setting_commit_ref_keywords: KlúÄové slová pre odkazy
+setting_commit_fix_keywords: KlúÄové slová pre uzavretie
+setting_autologin: Automatické prihlasovanie
+setting_date_format: Formát dátumu
+setting_time_format: Formát Äasu
+setting_cross_project_issue_relations: Povoliť väzby úloh skrz projekty
+setting_issue_list_default_columns: Východzie stĺpce zobrazené v zozname úloh
+setting_repositories_encodings: Kódovanie
+setting_emails_footer: Zapätie emailov
+setting_protocol: Protokol
+setting_per_page_options: Povolené množstvo riadkov na stránke
+setting_user_format: Formát zobrazenia užívateľa
+setting_activity_days_default: "Zobrazené dni aktivity projektu:"
+setting_display_subprojects_issues: Prednastavenie zobrazenia úloh podporojektov v hlavnom projekte
+
+project_module_issue_tracking: Sledovanie úloh
+project_module_time_tracking: Sledovanie Äasu
+project_module_news: Novinky
+project_module_documents: Dokumenty
+project_module_files: Súbory
+project_module_wiki: Wiki
+project_module_repository: Repository
+project_module_boards: Diskusie
+
+label_user: Užívateľ
+label_user_plural: Užívatelia
+label_user_new: Nový užívateľ
+label_project: Projekt
+label_project_new: Nový projekt
+label_project_plural: Projekty
+label_project_all: Všetky projekty
+label_project_latest: Posledné projekty
+label_issue: Úloha
+label_issue_new: Nová úloha
+label_issue_plural: Úlohy
+label_issue_view_all: Všetky úlohy
+label_issues_by: Úlohy od užívateľa %s
+label_issue_added: Úloha pridaná
+label_issue_updated: Úloha aktualizovaná
+label_document: Dokument
+label_document_new: Nový dokument
+label_document_plural: Dokumenty
+label_document_added: Dokument pridaný
+label_role: Rola
+label_role_plural: Role
+label_role_new: Nová rola
+label_role_and_permissions: Role a práva
+label_member: ÄŒlen
+label_member_new: Nový Älen
+label_member_plural: ÄŒlenovia
+label_tracker: Fronta
+label_tracker_plural: Fronty
+label_tracker_new: Nová fronta
+label_workflow: Workflow
+label_issue_status: Stav úloh
+label_issue_status_plural: Stavy úloh
+label_issue_status_new: Nový stav
+label_issue_category: Kategória úloh
+label_issue_category_plural: Kategórie úloh
+label_issue_category_new: Nová kategória
+label_custom_field: Užívateľské pole
+label_custom_field_plural: Užívateľské polia
+label_custom_field_new: Nové užívateľské pole
+label_enumerations: Zoznamy
+label_enumeration_new: Nová hodnota
+label_information: Informácia
+label_information_plural: Informácie
+label_please_login: Prosím prihláste sa
+label_register: Registrovať
+label_password_lost: Zabudnuté heslo
+label_home: Domovská stránka
+label_my_page: Moja stránka
+label_my_account: Môj úÄet
+label_my_projects: Moje projekty
+label_administration: Administrácia
+label_login: Prihlásenie
+label_logout: Odhlásenie
+label_help: Nápoveda
+label_reported_issues: Nahlásené úlohy
+label_assigned_to_me_issues: Moje úlohy
+label_last_login: Posledné prihlásenie
+label_last_updates: Posledná zmena
+label_last_updates_plural: %d posledné zmeny
+label_registered_on: Registrovaný
+label_activity: Aktivita
+label_overall_activity: Celková aktivita
+label_new: Nový
+label_logged_as: Prihlásený ako
+label_environment: Prostredie
+label_authentication: Autentifikácia
+label_auth_source: Mód autentifikácie
+label_auth_source_new: Nový mód autentifikácie
+label_auth_source_plural: Módy autentifikácie
+label_subproject_plural: Podprojekty
+label_min_max_length: Min - Max dĺžka
+label_list: Zoznam
+label_date: Dátum
+label_integer: Celé Äíslo
+label_float: Desatinné Äíslo
+label_boolean: Ãno/Nie
+label_string: Text
+label_text: Dlhý text
+label_attribute: Atribut
+label_attribute_plural: Atributy
+label_download: %d Download
+label_download_plural: %d Downloady
+label_no_data: Žiadné položky
+label_change_status: Zmeniť stav
+label_history: História
+label_attachment: Súbor
+label_attachment_new: Nový súbor
+label_attachment_delete: Odstrániť súbor
+label_attachment_plural: Súbory
+label_file_added: Súbor pridaný
+label_report: Prehľad
+label_report_plural: Prehľady
+label_news: Novinky
+label_news_new: Pridať novinku
+label_news_plural: Novinky
+label_news_latest: Posledné novinky
+label_news_view_all: Zobrazit všetky novinky
+label_news_added: Novinka pridaná
+label_change_log: Protokol zmien
+label_settings: Nastavenie
+label_overview: Prehľad
+label_version: Verzia
+label_version_new: Nová verzia
+label_version_plural: Verzie
+label_confirmation: Potvrdenie
+label_export_to: 'Tiež k dispozícií:'
+label_read: NaÄíta sa...
+label_public_projects: Verejné projekty
+label_open_issues: Otvorený
+label_open_issues_plural: Otvorené
+label_closed_issues: Uzavrený
+label_closed_issues_plural: Uzavrené
+label_total: Celkovo
+label_permissions: Práva
+label_current_status: Aktuálny stav
+label_new_statuses_allowed: Nové povolené stavy
+label_all: všetko
+label_none: niÄ
+label_nobody: nikto
+label_next: Ďalší
+label_previous: Predchádzajúci
+label_used_by: Použité
+label_details: Detaily
+label_add_note: Pridať poznámku
+label_per_page: Na stránku
+label_calendar: Kalendár
+label_months_from: mesiacov od
+label_gantt: Ganttov graf
+label_internal: Interný
+label_last_changes: posledných %d zmien
+label_change_view_all: Zobraziť všetky zmeny
+label_personalize_page: Prispôsobiť túto stránku
+label_comment: Komentár
+label_comment_plural: Komentáre
+label_comment_add: Pridať komentár
+label_comment_added: Komentár pridaný
+label_comment_delete: Odstrániť komentár
+label_query: Užívateľský dotaz
+label_query_plural: Užívateľské dotazy
+label_query_new: Nový dotaz
+label_filter_add: Pridať filter
+label_filter_plural: Filtre
+label_equals: je
+label_not_equals: nieje
+label_in_less_than: je menší ako
+label_in_more_than: je väÄší ako
+label_in: v
+label_today: dnes
+label_all_time: vždy
+label_yesterday: vÄera
+label_this_week: tento týždeň
+label_last_week: minulý týždeň
+label_last_n_days: posledných %d dní
+label_this_month: tento mesiac
+label_last_month: minulý mesiac
+label_this_year: tento rok
+label_date_range: Časový rozsah
+label_less_than_ago: pred menej ako (dňami)
+label_more_than_ago: pred viac ako (dňami)
+label_ago: pred (dňami)
+label_contains: obsahuje
+label_not_contains: neobsahuje
+label_day_plural: dní
+label_repository: Repository
+label_repository_plural: Repository
+label_browse: Prechádzať
+label_modification: %d zmena
+label_modification_plural: %d zmien
+label_revision: Revízia
+label_revision_plural: Revízií
+label_associated_revisions: Súvisiace verzie
+label_added: pridané
+label_modified: zmenené
+label_deleted: odstránené
+label_latest_revision: Posledná revízia
+label_latest_revision_plural: Posledné revízie
+label_view_revisions: Zobraziť revízie
+label_max_size: Maximálna veľkosť
+label_on: 'z celkovo'
+label_sort_highest: Presunúť na zaÄiatok
+label_sort_higher: Presunúť navrch
+label_sort_lower: Presunúť dole
+label_sort_lowest: Presunúť na koniec
+label_roadmap: Plán
+label_roadmap_due_in: Zostáva %s
+label_roadmap_overdue: %s neskoro
+label_roadmap_no_issues: Pre túto verziu niesu žiadne úlohy
+label_search: Hľadať
+label_result_plural: Výsledky
+label_all_words: Všetky slova
+label_wiki: Wiki
+label_wiki_edit: Wiki úprava
+label_wiki_edit_plural: Wiki úpravy
+label_wiki_page: Wiki stránka
+label_wiki_page_plural: Wiki stránky
+label_index_by_title: Index podľa názvu
+label_index_by_date: Index podľa dátumu
+label_current_version: Aktuálna verzia
+label_preview: Náhľad
+label_feed_plural: Príspevky
+label_changes_details: Detail všetkých zmien
+label_issue_tracking: Sledovanie úloh
+label_spent_time: Strávený Äas
+label_f_hour: %.2f hodina
+label_f_hour_plural: %.2f hodín
+label_time_tracking: Sledovánie Äasu
+label_change_plural: Zmeny
+label_statistics: Å tatistiky
+label_commits_per_month: Úkony za mesiac
+label_commits_per_author: Úkony podľa autora
+label_view_diff: Zobrazit rozdiely
+label_diff_inline: vo vnútri
+label_diff_side_by_side: vedľa seba
+label_options: Nastavenie
+label_copy_workflow_from: Kopírovať workflow z
+label_permissions_report: Prehľad práv
+label_watched_issues: Sledované úlohy
+label_related_issues: Súvisiace úlohy
+label_applied_status: Použitý stav
+label_loading: Nahrávam ...
+label_relation_new: Nová súvislosť
+label_relation_delete: Odstrániť súvislosť
+label_relates_to: súvisiací s
+label_duplicates: duplicity
+label_blocks: blokovaný
+label_blocked_by: zablokovaný
+label_precedes: predcháza
+label_follows: následuje
+label_end_to_start: od konca na zaÄiatok
+label_end_to_end: od konca do konca
+label_start_to_start: od zaÄiatku do zaÄiatku
+label_start_to_end: od zaÄiatku do konca
+label_stay_logged_in: Zostať prihlásený
+label_disabled: zakazané
+label_show_completed_versions: UkázaÅ¥ dokonÄené verzie
+label_me: ja
+label_board: Fórum
+label_board_new: Nové fórum
+label_board_plural: Fóra
+label_topic_plural: Témy
+label_message_plural: Správy
+label_message_last: Posledná správa
+label_message_new: Nová správa
+label_message_posted: Správa pridaná
+label_reply_plural: Odpovede
+label_send_information: ZaslaÅ¥ informácie o úÄte užívateľa
+label_year: Rok
+label_month: Mesiac
+label_week: Týžden
+label_date_from: Od
+label_date_to: Do
+label_language_based: Podľa výchozieho jazyka
+label_sort_by: Zoradenie podľa %s
+label_send_test_email: Poslať testovací email
+label_feeds_access_key_created_on: Prístupový klÃºÄ pre RSS bol vytvorený pred %s
+label_module_plural: Moduly
+label_added_time_by: 'Pridané užívateľom %s pred %s'
+label_updated_time: 'Aktualizované pred %s'
+label_jump_to_a_project: Zvoliť projekt...
+label_file_plural: Súbory
+label_changeset_plural: Sady zmien
+label_default_columns: Východzie stĺpce
+label_no_change_option: (bez zmeny)
+label_bulk_edit_selected_issues: Skupinová úprava vybraných úloh
+label_theme: Téma
+label_default: Východzí
+label_search_titles_only: Vyhľadávať iba v názvoch
+label_user_mail_option_all: "Pre všetky události všetkých mojích projektov"
+label_user_mail_option_selected: "Pre všetky události vybraných projektov.."
+label_user_mail_option_none: "Len pre události, ktoré sledujem alebo sa ma týkajú"
+label_user_mail_no_self_notified: "Nezasielať informácie o mnou vytvorených zmenách"
+label_registration_activation_by_email: aktivácia úÄtu emailom
+label_registration_manual_activation: manuálna aktivácia úÄtu
+label_registration_automatic_activation: automatická aktivácia úÄtu
+label_display_per_page: '%s na stránku'
+label_age: Vek
+label_change_properties: Zmeniť vlastnosti
+label_general: Všeobecné
+label_more: Viac
+label_scm: SCM
+label_plugins: Pluginy
+label_ldap_authentication: Autentifikácia LDAP
+label_downloads_abbr: D/L
+label_optional_description: Voliteľný popis
+label_add_another_file: Pridať další súbor
+label_preferences: Nastavenia
+label_chronological_order: V chronologickom poradí
+label_reverse_chronological_order: V obrátenom chronologickom poradí
+
+button_login: Prihlásiť
+button_submit: Potvrdiť
+button_save: Uložiť
+button_check_all: OznaÄiÅ¥ vÅ¡etko
+button_uncheck_all: OdznaÄiÅ¥ vÅ¡etko
+button_delete: Odstrániť
+button_create: Vytvoriť
+button_test: Test
+button_edit: Upraviť
+button_add: Pridať
+button_change: Zmeniť
+button_apply: Použiť
+button_clear: Zmazať
+button_lock: Zamknúť
+button_unlock: Odomknúť
+button_download: Stiahnúť
+button_list: Vypísať
+button_view: Zobraziť
+button_move: Presunúť
+button_back: Naspäť
+button_cancel: Storno
+button_activate: Aktivovať
+button_sort: Zoradenie
+button_log_time: PridaÅ¥ Äas
+button_rollback: Naspäť k tejto verzii
+button_watch: Sledovať
+button_unwatch: Nesledovať
+button_reply: Odpovedať
+button_archive: Archivovať
+button_unarchive: Odarchivovať
+button_reset: Reset
+button_rename: Premenovať
+button_change_password: Zmeniť heslo
+button_copy: Kopírovať
+button_annotate: Komentovať
+button_update: Aktualizovať
+button_configure: Konfigurovať
+
+status_active: aktívny
+status_registered: registrovaný
+status_locked: uzamknutý
+
+text_select_mail_notifications: Vyberte akciu, pri ktorej bude zaslané upozornenie emailom
+text_regexp_info: napr. ^[A-Z0-9]+$
+text_min_max_length_info: 0 znamená bez limitu
+text_project_destroy_confirmation: Ste si istý, že chcete odstránit tento projekt a všetky súvisiace dáta ?
+text_workflow_edit: Vyberte rolu a frontu k editácii workflow
+text_are_you_sure: Ste si istý?
+text_journal_changed: zmenené z %s na %s
+text_journal_set_to: nastavené na %s
+text_journal_deleted: odstránené
+text_tip_task_begin_day: úloha zaÄína v tento deň
+text_tip_task_end_day: úloha konÄí v tento deň
+text_tip_task_begin_end_day: úloha zaÄína a konÄí v tento deň
+text_project_identifier_info: 'Povolené znaky sú malé písmena (a-z), Äísla a pomlÄka.<br />Po uložení už nieje možné identifikátor zmeniÅ¥.'
+text_caracters_maximum: %d znakov maximálne.
+text_caracters_minimum: Musí byť aspoň %d znaky/ov dlhé.
+text_length_between: Dĺžka medzi %d až %d znakmi.
+text_tracker_no_workflow: Pre tuto frontu nieje definovaný žiadný workflow
+text_unallowed_characters: Nepovolené znaky
+text_comma_separated: Je povolené viacero hodnôt (oddelené navzájom Äiarkou).
+text_issues_ref_in_commit_messages: Odkazovať a upravovať úlohy v správach s následovnym obsahom
+text_issue_added: úloha %s bola vytvorená užívateľom %s.
+text_issue_updated: Úloha %s byla aktualizovaná užívateľom %s.
+text_wiki_destroy_confirmation: Naozaj si prajete odstráni%t túto Wiki a celý jej obsah?
+text_issue_category_destroy_question: Niektoré úlohy (%d) sú priradené k tejto kategórii. Čo chtete s nimi spraviť?
+text_issue_category_destroy_assignments: Zrušiť priradenie ku kategórii
+text_issue_category_reassign_to: Priradiť úlohy do tejto kategórie
+text_user_mail_option: "U projektov, které neboli vybrané, budete dostávaÅ¥ oznamenie iba o vaÅ¡ich Äi o sledovaných položkách (napr. o položkách, ktorých ste autor, alebo ku ktorým ste priradený/á)."
+text_no_configuration_data: "Role, fronty, stavy úloh ani workflow neboli zatiaľ nakonfigurované.\nVelmi doporuÄujeme nahraÅ¥ východziu konfiguráciu. Potom si môžete vÅ¡etko upraviÅ¥"
+text_load_default_configuration: Nahrať východziu konfiguráciu
+text_status_changed_by_changeset: Aktualizované v sade zmien %s.
+text_issues_destroy_confirmation: 'Naozaj si prajete odstrániť všetky zvolené úlohy?'
+text_select_project_modules: 'Aktivne moduly v tomto projekte:'
+text_default_administrator_account_changed: Zmenené výchozie nastavenie administrátorského úÄtu
+text_file_repository_writable: Povolený zápis do repository
+text_rmagick_available: RMagick k dispozícií (voliteľné)
+text_destroy_time_entries_question: U úloh, které chcete odstraniť, je evidované %.02f práce. Čo chcete vykonať?
+text_destroy_time_entries: Odstrániť evidované hodiny.
+text_assign_time_entries_to_project: Priradiť evidované hodiny projektu
+text_reassign_time_entries: 'Preradiť evidované hodiny k tejto úlohe:'
+
+default_role_manager: Manažér
+default_role_developper: Vývojár
+default_role_reporter: Reportér
+default_tracker_bug: Chyba
+default_tracker_feature: Rozšírenie
+default_tracker_support: Podpora
+default_issue_status_new: Nový
+default_issue_status_assigned: Priradený
+default_issue_status_resolved: Vyriešený
+default_issue_status_feedback: Čaká sa
+default_issue_status_closed: Uzavrený
+default_issue_status_rejected: Odmietnutý
+default_doc_category_user: Užívateľská dokumentácia
+default_doc_category_tech: Technická dokumentácia
+default_priority_low: Nízká
+default_priority_normal: Normálna
+default_priority_high: Vysoká
+default_priority_urgent: Urgentná
+default_priority_immediate: Okamžitá
+default_activity_design: Design
+default_activity_development: Vývoj
+
+enumeration_issue_priorities: Priority úloh
+enumeration_doc_categories: Kategorie dokumentov
+enumeration_activities: Aktivity (sledovanie Äasu)
+error_scm_annotate: "Položka neexistuje alebo nemôže byť komentovaná."
+label_planning: Plánovanie
+text_subprojects_destroy_warning: 'Jeho podprojekt(y): %s budú takisto vymazané.'
+label_and_its_subprojects: %s a jeho podprojekty
+mail_body_reminder: "%d úloha(y), ktorá(é) je(sú) vám priradený(é), ma(jú) byť hotova(é) za %d dní:"
+mail_subject_reminder: "%d úloha(y) ma(jú) byť hotova(é) za pár dní"
+text_user_wrote: '%s napísal:'
+label_duplicated_by: duplikovaný
+setting_enabled_scm: Zapnúť SCM
+text_enumeration_category_reassign_to: 'Prenastaviť na túto hodnotu:'
+text_enumeration_destroy_question: '%d objekty sú nastavené na túto hodnotu.'
+label_incoming_emails: Príchádzajúce emaily
+label_generate_key: VygenerovaÅ¥ kľúÄ
+setting_mail_handler_api_enabled: Zapnúť WS pre príchodzie emaily
+setting_mail_handler_api_key: API kľúÄ
+text_email_delivery_not_configured: "DoruÄenie emailov nieje nastavené, notifikácie sú vypnuté.\nNastavte váš SMTP server v config/email.yml a reÅ¡tartnite aplikáciu pre aktiváciu funkcie."
+field_parent_title: Nadradená stránka
+label_issue_watchers: Pozorovatelia
+setting_commit_logs_encoding: Kódovanie prenášaných správ
+button_quote: Citácia
+setting_sequential_project_identifiers: GenerovaÅ¥ sekvenÄné identifikátory projektov
+notice_unable_delete_version: Verzia nemôže byť zmazaná
+label_renamed: premenované
+label_copied: kopírované
+setting_plain_text_mail: Len jednoduchý text (bez HTML)
+permission_view_files: Zobrazenie súborov
+permission_edit_issues: Editácia úloh
+permission_edit_own_time_entries: Editácia vlastných Äasových logov
+permission_manage_public_queries: Správa verejných dotazov
+permission_add_issues: Pridanie úlohy
+permission_log_time: Log stráveného Äasu
+permission_view_changesets: Zobrazenie sád zmien
+permission_view_time_entries: Zobrazenie stráveného Äasu
+permission_manage_versions: Správa verzií
+permission_manage_wiki: Správa Wiki
+permission_manage_categories: Správa kategórií úloh
+permission_protect_wiki_pages: Ochrana Wiki strániek
+permission_comment_news: Komentovanie noviniek
+permission_delete_messages: Mazanie správ
+permission_select_project_modules: Voľba projektových modulov
+permission_manage_documents: Správa dokumentov
+permission_edit_wiki_pages: Editácia Wiki strániek
+permission_add_issue_watchers: Pridanie pozorovateľov
+permission_view_gantt: Zobrazenie Ganttovho diagramu
+permission_move_issues: Presun úloh
+permission_manage_issue_relations: Správa vzťahov úloh
+permission_delete_wiki_pages: Mazanie Wiki strániek
+permission_manage_boards: Správa diskusií
+permission_delete_wiki_pages_attachments: Mazanie Wiki príloh
+permission_view_wiki_edits: Zobrazenie Wiki zmien
+permission_add_messages: Pridanie správ
+permission_view_messages: Zobrazenie správ
+permission_manage_files: Správa súborov
+permission_edit_issue_notes: Editácia poznámok úlohy
+permission_manage_news: Správa noviniek
+permission_view_calendar: Zobrazenie kalendára
+permission_manage_members: Správa Älenov
+permission_edit_messages: Editácia správ
+permission_delete_issues: Mazanie správ
+permission_view_issue_watchers: Zobrazenie zoznamu pozorovateľov
+permission_manage_repository: Správa repository
+permission_commit_access: Povoliť prístup
+permission_browse_repository: Prechádzanie (browse) repository
+permission_view_documents: Zobrazenie dokumentov
+permission_edit_project: Editovanie projektu
+permission_add_issue_notes: Pridanie poznámky úlohy
+permission_save_queries: Uloženie dotazov
+permission_view_wiki_pages: Zobrazenie Wiki strániek
+permission_rename_wiki_pages: Premenovanie Wiki strániek
+permission_edit_time_entries: Editácia Äasových záznamov
+permission_edit_own_issue_notes: Editácia vlastných poznámok úlohy
+setting_gravatar_enabled: Použitie užívateľských Gravatar ikon
+permission_edit_own_messages: Úprava vlastných správ
+permission_delete_own_messages: Mazanie vlastných správ
+text_repository_usernames_mapping: "Vyberte alebo doplňte užívateľov systému Redmine, ktorí majú byť namapovaní k užívateľským menám, nájdeným v logu repository.\nUžívatelia s rovnakým prihlasovacím menom alebo emailom v systéme Redmine a repository sú mapovaní automaticky."
+label_example: Príklad
+label_user_activity: "Aktivita užívateľa %s"
+label_updated_time_by: Aktualizované užívateľom %s pred %s
+text_diff_truncated: '... Tento rozdielový výpis bol skratený, pretože prekraÄuje maximálnu veľkosÅ¥, ktorá môže byÅ¥ zobrazená.'
+setting_diff_max_lines_displayed: Maximálne množstvo zobrazených riadkov rozdielového výpisu
+text_plugin_assets_writable: Adresár pre pluginy s možnosťou zápisu
+warning_attachments_not_saved: "%d súbor(y) nemohli byť uložené."
+field_editable: Editable
+label_display: Display
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/sl.yml b/lang/sl.yml
new file mode 100644
index 000000000..bd51bda35
--- /dev/null
+++ b/lang/sl.yml
@@ -0,0 +1,711 @@
+_gloc_rule_default: '|n| n==1 ? "" : "_plural" '
+
+actionview_datehelper_select_day_prefix:
+actionview_datehelper_select_month_names: Januar,Februar,Marec,April,Maj,Junij,Julij,Avgust,September,Oktober,November,December
+actionview_datehelper_select_month_names_abbr: Jan,Feb,Mar,Apr,Maj,Jun,Jul,Aug,Sep,Okt,Nov,Dec
+actionview_datehelper_select_month_prefix:
+actionview_datehelper_select_year_prefix:
+actionview_datehelper_time_in_words_day: 1 dan
+actionview_datehelper_time_in_words_day_plural: %d dni
+actionview_datehelper_time_in_words_hour_about: kakšno uro
+actionview_datehelper_time_in_words_hour_about_plural: kakšnih %d ur
+actionview_datehelper_time_in_words_hour_about_single: kakšno uro
+actionview_datehelper_time_in_words_minute: 1 minuta
+actionview_datehelper_time_in_words_minute_half: pol minute
+actionview_datehelper_time_in_words_minute_less_than: manj kot minuto
+actionview_datehelper_time_in_words_minute_plural: %d minut
+actionview_datehelper_time_in_words_minute_single: 1 minuta
+actionview_datehelper_time_in_words_second_less_than: manj kot sekunda
+actionview_datehelper_time_in_words_second_less_than_plural: manj kot %d sekund
+actionview_instancetag_blank_option: Prosimo izberite
+
+activerecord_error_inclusion: ni vkljuÄen na seznamu
+activerecord_error_exclusion: je rezerviran
+activerecord_error_invalid: je napaÄen
+activerecord_error_confirmation: ne ustreza potrdilu
+activerecord_error_accepted: mora biti sprejet
+activerecord_error_empty: ne sme biti prazen
+activerecord_error_blank: ne sme biti neizpolnjen
+activerecord_error_too_long: je predolg
+activerecord_error_too_short: je prekratek
+activerecord_error_wrong_length: je napaÄne dolžine
+activerecord_error_taken: je že zaseden
+activerecord_error_not_a_number: ni število
+activerecord_error_not_a_date: ni veljaven datum
+activerecord_error_greater_than_start_date: mora biti kasnejÅ¡i kot zaÄeten datum
+activerecord_error_not_same_project: ne pripada istemu projektu
+activerecord_error_circular_dependency: Ta odnos bi povzroÄil krožno odvisnost
+
+general_fmt_age: %d l
+general_fmt_age_plural: %d let
+general_fmt_date: %%m/%%d/%%Y
+general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p
+general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
+general_fmt_time: %%I:%%M %%p
+general_text_No: 'Ne'
+general_text_Yes: 'Da'
+general_text_no: 'ne'
+general_text_yes: 'da'
+general_lang_name: 'SlovenÅ¡Äina'
+general_csv_separator: ','
+general_csv_decimal_separator: '.'
+general_csv_encoding: ISO-8859-1
+general_pdf_encoding: ISO-8859-1
+general_day_names: Ponedeljek,Torek,Sreda,ÄŒetrtek,Petek,Sobota,Nedelja
+general_first_day_of_week: '1'
+
+notice_account_updated: RaÄun je bil uspeÅ¡no posodobljen.
+notice_account_invalid_creditentials: NapaÄno uporabniÅ¡ko ime ali geslo
+notice_account_password_updated: Geslo je bilo uspešno posodobljeno.
+notice_account_wrong_password: NapaÄno geslo
+notice_account_register_done: RaÄun je bil uspeÅ¡no ustvarjen. Za aktivacijo potrdite povezavo, ki vam je bila poslana v e-nabiralnik.
+notice_account_unknown_email: Neznan uporabnik.
+notice_can_t_change_password: Ta raÄun za overovljanje uporablja zunanji. Gesla ni mogoÄe spremeniti.
+notice_account_lost_email_sent: Poslano vam je bilo e-pismo z navodili za izbiro novega gesla.
+notice_account_activated: VaÅ¡ raÄun je bil aktiviran. Sedaj se lahko prijavite.
+notice_successful_create: Zapisovanje uspelo.
+notice_successful_update: Posodobitev uspela.
+notice_successful_delete: Izbris uspel.
+notice_successful_connection: Povezava uspela.
+notice_file_not_found: Stran na katero se želite povezati ne obstaja ali pa je bila umaknjena.
+notice_locking_conflict: Drug uporabnik je posodobil podatke.
+notice_not_authorized: Nimate privilegijev za dostop do te strani.
+notice_email_sent: E-poÅ¡tno sporoÄilo je bilo poslano %s
+notice_email_error: Ob poÅ¡iljanju e-sporoÄila je priÅ¡lo do napake (%s)
+notice_feeds_access_key_reseted: VaÅ¡ RSS dostopni kljuÄ je bil ponastavljen.
+notice_failed_to_save_issues: "Neuspelo shranjevanje %d zahtevka na %d izbranem: %s."
+notice_no_issue_selected: "Izbran ni noben zahtevek! Prosimo preverite zahtevke, ki jih želite urediti."
+notice_account_pending: "VaÅ¡ raÄun je bil ustvarjen in Äaka na potrditev s strani administratorja."
+notice_default_data_loaded: Privzete nastavitve so bile uspešno naložene.
+notice_unable_delete_version: Verzije ni bilo mogoÄe izbrisati.
+
+error_can_t_load_default_data: "Privzetih nastavitev ni bilo mogoÄe naložiti: %s"
+error_scm_not_found: "Vnos ali revizija v shrambi ni bila najdena ."
+error_scm_command_failed: "Med vzpostavljem povezave s shrambo je prišlo do napake: %s"
+error_scm_annotate: "Vnos ne obstaja ali pa ga ni mogoÄe komentirati."
+error_issue_not_found_in_project: 'Zahtevek ni bil najden ali pa ne pripada temu projektu'
+
+mail_subject_lost_password: Vaše %s geslo
+mail_body_lost_password: 'Za spremembo glesla kliknite na naslednjo povezavo:'
+mail_subject_register: Aktivacija %s vaÅ¡ega raÄuna
+mail_body_register: 'Za aktivacijo vaÅ¡ega raÄuna kliknite na naslednjo povezavo:'
+mail_body_account_information_external: Za prijavo lahko uporabite vaÅ¡ "%s" raÄun.
+mail_body_account_information: Informacije o vaÅ¡em raÄunu
+mail_subject_account_activation_request: %s zahtevek za aktivacijo raÄuna
+mail_body_account_activation_request: 'Registriral se je nov uporabnik (%s). RaÄun Äaka na vaÅ¡o odobritev:'
+mail_subject_reminder: "%d zahtevek(zahtevki) zapadejo v naslednjih dneh"
+mail_body_reminder: "%d zahtevek(zahtevki), ki so vam dodeljeni bodo zapadli v naslednjih %d dneh:"
+
+gui_validation_error: 1 napaka
+gui_validation_error_plural: %d napak
+
+field_name: Ime
+field_description: Opis
+field_summary: Povzetek
+field_is_required: Zahtevano
+field_firstname: Ime
+field_lastname: Priimek
+field_mail: E-naslov
+field_filename: Datoteka
+field_filesize: Velikost
+field_downloads: Prenosi
+field_author: Avtor
+field_created_on: Ustvarjen
+field_updated_on: Posodobljeno
+field_field_format: Format
+field_is_for_all: Za vse projekte
+field_possible_values: Možne vrednosti
+field_regexp: Regularni izraz
+field_min_length: Minimalna dolžina
+field_max_length: Maksimalna dolžina
+field_value: Vrednost
+field_category: Kategorija
+field_title: Naslov
+field_project: Projekt
+field_issue: Zahtevek
+field_status: Status
+field_notes: Zabeležka
+field_is_closed: Zahtevek zaprt
+field_is_default: Privzeta vrednost
+field_tracker: Vrsta zahtevka
+field_subject: Tema
+field_due_date: Do datuma
+field_assigned_to: Dodeljen
+field_priority: Prioriteta
+field_fixed_version: Ciljna verzija
+field_user: Uporabnik
+field_role: Vloga
+field_homepage: DomaÄa stran
+field_is_public: Javno
+field_parent: Podprojekt projekta
+field_is_in_chlog: Zahtevki prikazani v zapisu sprememb
+field_is_in_roadmap: Zahtevki prikazani na zemljevidu
+field_login: Prijava
+field_mail_notification: E-poštna oznanila
+field_admin: Administrator
+field_last_login_on: ZadnjiÄ povezan(a)
+field_language: Jezik
+field_effective_date: Datum
+field_password: Geslo
+field_new_password: Novo geslo
+field_password_confirmation: Potrditev
+field_version: Verzija
+field_type: Tip
+field_host: Gostitelj
+field_port: Vrata
+field_account: RaÄun
+field_base_dn: Bazni DN
+field_attr_login: Oznaka za prijavo
+field_attr_firstname: Oznaka za ime
+field_attr_lastname: Oznaka za priimek
+field_attr_mail: Oznaka za e-naslov
+field_onthefly: Sprotna izdelava uporabnikov
+field_start_date: ZaÄetek
+field_done_ratio: %% Narejeno
+field_auth_source: NaÄin overovljanja
+field_hide_mail: Skrij moj e-naslov
+field_comments: Komentar
+field_url: URL
+field_start_page: ZaÄetna stran
+field_subproject: Podprojekt
+field_hours: Ur
+field_activity: Aktivnost
+field_spent_on: Datum
+field_identifier: Identifikator
+field_is_filter: Uporabljen kot filter
+field_issue_to_id: Povezan zahtevek
+field_delay: Zamik
+field_assignable: Zahtevki so lahko dodeljeni tej vlogi
+field_redirect_existing_links: Preusmeri obstojeÄe povezave
+field_estimated_hours: Ocenjen Äas
+field_column_names: Stolpci
+field_time_zone: ÄŒasovni pas
+field_searchable: Zmožen iskanja
+field_default_value: Privzeta vrednost
+field_comments_sorting: Prikaži komentarje
+field_parent_title: MatiÄna stran
+
+setting_app_title: Naslov aplikacije
+setting_app_subtitle: Podnaslov aplikacije
+setting_welcome_text: Pozdravno besedilo
+setting_default_language: Privzeti jezik
+setting_login_required: Zahtevano overovljanje
+setting_self_registration: Samostojna registracija
+setting_attachment_max_size: Maksimalna velikost priponk
+setting_issues_export_limit: Skrajna meja za izvoz zahtevkov
+setting_mail_from: E-naslov za emisijo
+setting_bcc_recipients: Prejemniki slepih kopij (bcc)
+setting_plain_text_mail: navadno e-sporoÄilo (ne HTML)
+setting_host_name: Ime gostitelja in pot
+setting_text_formatting: Oblikovanje besedila
+setting_wiki_compression: Stiskanje Wiki zgodovine
+setting_feeds_limit: Meja obsega RSS virov
+setting_default_projects_public: Novi projekti so privzeto javni
+setting_autofetch_changesets: Samodejni izvleÄek zapisa sprememb
+setting_sys_api_enabled: OmogoÄi WS za upravljanje shrambe
+setting_commit_ref_keywords: Sklicne kljuÄne besede
+setting_commit_fix_keywords: Urejanje kljuÄne besede
+setting_autologin: Avtomatska prijava
+setting_date_format: Oblika datuma
+setting_time_format: Oblika Äasa
+setting_cross_project_issue_relations: Dovoli povezave zahtevkov med razliÄnimi projekti
+setting_issue_list_default_columns: Privzeti stolpci prikazani na seznamu zahtevkov
+setting_repositories_encodings: Kodiranje shrambe
+setting_commit_logs_encoding: Kodiranje sporoÄil ob predaji
+setting_emails_footer: Noga e-sporoÄil
+setting_protocol: Protokol
+setting_per_page_options: Å tevilo elementov na stran
+setting_user_format: Oblika prikaza uporabnikov
+setting_activity_days_default: Prikaz dni na aktivnost projekta
+setting_display_subprojects_issues: Privzeti prikaz zahtevkov podprojektov v glavnem projektu
+setting_enabled_scm: OmogoÄen SCM
+setting_mail_handler_api_enabled: OmogoÄi WS za prihajajoÄo e-poÅ¡to
+setting_mail_handler_api_key: API kljuÄ
+setting_sequential_project_identifiers: Generiraj projektne identifikatorje sekvenÄno
+setting_gravatar_enabled: Uporabljaj Gravatar ikone
+setting_diff_max_lines_displayed: Maksimalno Å¡tevilo prikazanih vrstic razliÄnosti
+
+permission_edit_project: Uredi projekt
+permission_select_project_modules: Izberi module projekta
+permission_manage_members: Uredi Älane
+permission_manage_versions: Uredi verzije
+permission_manage_categories: Urejanje kategorij zahtevkov
+permission_add_issues: Dodaj zahtevke
+permission_edit_issues: Uredi zahtevke
+permission_manage_issue_relations: Uredi odnose med zahtevki
+permission_add_issue_notes: Dodaj zabeležke
+permission_edit_issue_notes: Uredi zabeležke
+permission_edit_own_issue_notes: Uredi lastne zabeležke
+permission_move_issues: Premakni zahtevke
+permission_delete_issues: Izbriši zahtevke
+permission_manage_public_queries: Uredi javna povpraševanja
+permission_save_queries: Shrani povpraševanje
+permission_view_gantt: Poglej gantogram
+permission_view_calendar: Poglej koledar
+permission_view_issue_watchers: Oglej si listo spremeljevalcev
+permission_add_issue_watchers: Dodaj spremljevalce
+permission_log_time: Beleži porabljen Äas
+permission_view_time_entries: Poglej porabljen Äas
+permission_edit_time_entries: Uredi beležko Äasa
+permission_edit_own_time_entries: Uredi beležko lastnega Äasa
+permission_manage_news: Uredi novice
+permission_comment_news: Komentiraj novice
+permission_manage_documents: Uredi dokumente
+permission_view_documents: Poglej dokumente
+permission_manage_files: Uredi datoteke
+permission_view_files: Poglej datoteke
+permission_manage_wiki: Uredi wiki
+permission_rename_wiki_pages: Preimenuj wiki strani
+permission_delete_wiki_pages: Izbriši wiki strani
+permission_view_wiki_pages: Poglej wiki
+permission_view_wiki_edits: Poglej wiki zgodovino
+permission_edit_wiki_pages: Uredi wiki strani
+permission_delete_wiki_pages_attachments: Izbriši priponke
+permission_protect_wiki_pages: ZaÅ¡Äiti wiki strani
+permission_manage_repository: Uredi shrambo
+permission_browse_repository: Prebrskaj shrambo
+permission_view_changesets: Poglej zapis sprememb
+permission_commit_access: Dostop za predajo
+permission_manage_boards: Uredi table
+permission_view_messages: Poglej sporoÄila
+permission_add_messages: Objavi sporoÄila
+permission_edit_messages: Uredi sporoÄila
+permission_edit_own_messages: Uredi lastna sporoÄila
+permission_delete_messages: IzbriÅ¡i sporoÄila
+permission_delete_own_messages: IzbriÅ¡i lastna sporoÄila
+
+project_module_issue_tracking: Sledenje zahtevkom
+project_module_time_tracking: Sledenje Äasa
+project_module_news: Novice
+project_module_documents: Dokumenti
+project_module_files: Datoteke
+project_module_wiki: Wiki
+project_module_repository: Shramba
+project_module_boards: Table
+
+label_user: Uporabnik
+label_user_plural: Uporabniki
+label_user_new: Nov uporabnik
+label_project: Projekt
+label_project_new: Nov projekt
+label_project_plural: Projekti
+label_project_all: Vsi projekti
+label_project_latest: Zadnji projekti
+label_issue: Zahtevek
+label_issue_new: Nov zahtevek
+label_issue_plural: Zahtevki
+label_issue_view_all: Poglej vse zahtevke
+label_issues_by: Zahtevki od %s
+label_issue_added: Zahtevek dodan
+label_issue_updated: Zahtevek posodobljen
+label_document: Dokument
+label_document_new: Nov dokument
+label_document_plural: Dokumenti
+label_document_added: Dokument dodan
+label_role: Vloga
+label_role_plural: Vloge
+label_role_new: Nova vloga
+label_role_and_permissions: Vloge in dovoljenja
+label_member: ÄŒlan
+label_member_new: Nov Älan
+label_member_plural: ÄŒlani
+label_tracker: Vrsta zahtevka
+label_tracker_plural: Vrste zahtevkov
+label_tracker_new: Nova vrsta zahtevka
+label_workflow: Potek dela
+label_issue_status: Stanje zahtevka
+label_issue_status_plural: Stanje zahtevkov
+label_issue_status_new: Novo stanje
+label_issue_category: Kategorija zahtevka
+label_issue_category_plural: Kategorije zahtevkov
+label_issue_category_new: Nova kategorija
+label_custom_field: Polje po meri
+label_custom_field_plural: Polja po meri
+label_custom_field_new: Novo polje po meri
+label_enumerations: Seznami
+label_enumeration_new: Nova vrednost
+label_information: Informacija
+label_information_plural: Informacije
+label_please_login: Prosimo prijavite se
+label_register: Registracija
+label_password_lost: Izgubljeno geslo
+label_home: Domov
+label_my_page: Moja stran
+label_my_account: Moj raÄun
+label_my_projects: Moji projekti
+label_administration: Upravljanje
+label_login: Prijavi se
+label_logout: Odjavi se
+label_help: PomoÄ
+label_reported_issues: Prijavljeni zahtevki
+label_assigned_to_me_issues: Zahtevki dodeljeni meni
+label_last_login: Zadnja povezava
+label_last_updates: Zadnja posodobitev
+label_last_updates_plural: %d zadnje posodobitve
+label_registered_on: Registriran
+label_activity: Aktivnost
+label_overall_activity: Celotna aktivnost
+label_user_activity: "Aktivnost %s"
+label_new: Nov
+label_logged_as: Prijavljen(a) kot
+label_environment: Okolje
+label_authentication: Overovitev
+label_auth_source: NaÄin overovitve
+label_auth_source_new: Nov naÄin overovitve
+label_auth_source_plural: NaÄini overovitve
+label_subproject_plural: Podprojekti
+label_and_its_subprojects: %s in njegovi podprojekti
+label_min_max_length: Min - Max dolžina
+label_list: Seznam
+label_date: Datum
+label_integer: Celo število
+label_float: Decimalno število
+label_boolean: Boolean
+label_string: Besedilo
+label_text: Dolgo besedilo
+label_attribute: Lastnost
+label_attribute_plural: Lastnosti
+label_download: %d Prenos
+label_download_plural: %d Prenosi
+label_no_data: Ni podatkov za prikaz
+label_change_status: Spremeni stanje
+label_history: Zgodovina
+label_attachment: Datoteka
+label_attachment_new: Nova datoteka
+label_attachment_delete: Izbriši datoteko
+label_attachment_plural: Datoteke
+label_file_added: Datoteka dodana
+label_report: PoroÄilo
+label_report_plural: PoroÄila
+label_news: Novica
+label_news_new: Dodaj novico
+label_news_plural: Novice
+label_news_latest: Zadnje novice
+label_news_view_all: Poglej vse novice
+label_news_added: Dodane novice
+label_change_log: Zapisnik spremeb
+label_settings: Nastavitve
+label_overview: Pregled
+label_version: Verzija
+label_version_new: Nova verzija
+label_version_plural: Verzije
+label_confirmation: Potrditev
+label_export_to: 'Na razpolago tudi v:'
+label_read: Preberi...
+label_public_projects: Javni projekti
+label_open_issues: odpri zahtevek
+label_open_issues_plural: odpri zahtevke
+label_closed_issues: zapri zahtevek
+label_closed_issues_plural: zapri zahtevke
+label_total: Skupaj
+label_permissions: Dovoljenja
+label_current_status: Trenutno stanje
+label_new_statuses_allowed: Novi zahtevki dovoljeni
+label_all: vsi
+label_none: noben
+label_nobody: nihÄe
+label_next: Naslednji
+label_previous: Prejšnji
+label_used_by: V uporabi od
+label_details: Podrobnosti
+label_add_note: Dodaj zabeležko
+label_per_page: Na stran
+label_calendar: Koledar
+label_months_from: mesecev od
+label_gantt: Gantt
+label_internal: Notranji
+label_last_changes: zadnjih %d sprememb
+label_change_view_all: Poglej vse spremembe
+label_personalize_page: Individualiziraj to stran
+label_comment: Komentar
+label_comment_plural: Komentarji
+label_comment_add: Dodaj komentar
+label_comment_added: Komentar dodan
+label_comment_delete: Izbriši komentarje
+label_query: Iskanje po meri
+label_query_plural: Iskanja po meri
+label_query_new: Novo iskanje
+label_filter_add: Dodaj filter
+label_filter_plural: Filtri
+label_equals: je enako
+label_not_equals: ni enako
+label_in_less_than: v manj kot
+label_in_more_than: v veÄ kot
+label_in: v
+label_today: danes
+label_all_time: v vsem Äasu
+label_yesterday: vÄeraj
+label_this_week: ta teden
+label_last_week: pretekli teden
+label_last_n_days: zadnjih %d dni
+label_this_month: ta mesec
+label_last_month: zadnji mesec
+label_this_year: to leto
+label_date_range: Razpon datumov
+label_less_than_ago: manj kot dni nazaj
+label_more_than_ago: veÄ kot dni nazaj
+label_ago: dni nazaj
+label_contains: vsebuje
+label_not_contains: ne vsebuje
+label_day_plural: dni
+label_repository: Shramba
+label_repository_plural: Shrambe
+label_browse: Prebrskaj
+label_modification: %d sprememba
+label_modification_plural: %d spremembe
+label_revision: Revizija
+label_revision_plural: Revizije
+label_associated_revisions: Povezane revizije
+label_added: dodano
+label_modified: spremenjeno
+label_copied: kopirano
+label_renamed: preimenovano
+label_deleted: izbrisano
+label_latest_revision: Zadnja revizija
+label_latest_revision_plural: Zadnje revizije
+label_view_revisions: Poglej revizije
+label_max_size: NajveÄja velikost
+label_on: 'na'
+label_sort_highest: Premakni na vrh
+label_sort_higher: Premakni gor
+label_sort_lower: Premakni dol
+label_sort_lowest: Premakni na dno
+label_roadmap: NaÄrt
+label_roadmap_due_in: Do %s
+label_roadmap_overdue: %s zakasnel
+label_roadmap_no_issues: Ni zahtevkov za to verzijo
+label_search: IÅ¡Äi
+label_result_plural: Rezultati
+label_all_words: Vse besede
+label_wiki: Wiki
+label_wiki_edit: Wiki urejanje
+label_wiki_edit_plural: Wiki urejanja
+label_wiki_page: Wiki stran
+label_wiki_page_plural: Wiki strani
+label_index_by_title: Razvrsti po naslovu
+label_index_by_date: Razvrsti po datumu
+label_current_version: Trenutna verzija
+label_preview: Predogled
+label_feed_plural: RSS viri
+label_changes_details: Podrobnosti o vseh spremembah
+label_issue_tracking: Sledenje zahtevkom
+label_spent_time: Porabljen Äas
+label_f_hour: %.2f ura
+label_f_hour_plural: %.2f ur
+label_time_tracking: Sledenje Äasu
+label_change_plural: Spremembe
+label_statistics: Statistika
+label_commits_per_month: Predaj na mesec
+label_commits_per_author: Predaj na avtorja
+label_view_diff: Preglej razlike
+label_diff_inline: znotraj
+label_diff_side_by_side: vzporedno
+label_options: Možnosti
+label_copy_workflow_from: Kopiraj potek dela od
+label_permissions_report: PoroÄilo o dovoljenjih
+label_watched_issues: Spremljani zahtevki
+label_related_issues: Povezani zahtevki
+label_applied_status: Uveljavljeno stanje
+label_loading: Nalaganje...
+label_relation_new: Nova povezava
+label_relation_delete: Izbriši povezavo
+label_relates_to: povezan z
+label_duplicates: duplikati
+label_duplicated_by: dupliciral
+label_blocks: blok
+label_blocked_by: blokiral
+label_precedes: ima prednost pred
+label_follows: sledi
+label_end_to_start: konec na zaÄetek
+label_end_to_end: konec na konec
+label_start_to_start: zaÄetek na zaÄetek
+label_start_to_end: zaÄetek na konec
+label_stay_logged_in: Ostani prijavljen(a)
+label_disabled: onemogoÄi
+label_show_completed_versions: Prikaži zakljuÄene verzije
+label_me: jaz
+label_board: Forum
+label_board_new: Nov forum
+label_board_plural: Forumi
+label_topic_plural: Teme
+label_message_plural: SporoÄila
+label_message_last: Zadnje sporoÄilo
+label_message_new: Novo sporoÄilo
+label_message_posted: SporoÄilo dodano
+label_reply_plural: Odgovori
+label_send_information: PoÅ¡lji informacijo o raÄunu uporabniku
+label_year: Leto
+label_month: Mesec
+label_week: Teden
+label_date_from: Od
+label_date_to: Do
+label_language_based: Glede na uporabnikov jezik
+label_sort_by: Razporedi po %s
+label_send_test_email: Pošlji testno e-pismo
+label_feeds_access_key_created_on: RSS dostopni kljuÄ narejen %s nazaj
+label_module_plural: Moduli
+label_added_time_by: Dodal(a) %s %s nazaj
+label_updated_time_by: Posodobljen od %s %s nazaj
+label_updated_time: Posodobljen %s nazaj
+label_jump_to_a_project: SkoÄi na projekt...
+label_file_plural: Datoteke
+label_changeset_plural: Zapisi sprememb
+label_default_columns: Privzeti stolpci
+label_no_change_option: (Ni spremembe)
+label_bulk_edit_selected_issues: Uredi izbrane zahtevke skupaj
+label_theme: Tema
+label_default: Privzeto
+label_search_titles_only: PreiÅ¡Äi samo naslove
+label_user_mail_option_all: "Za vsak dogodek v vseh mojih projektih"
+label_user_mail_option_selected: "Za vsak dogodek samo na izbranih projektih..."
+label_user_mail_option_none: "Samo za zadeve ki jih spremljam ali sem v njih udeležen(a)"
+label_user_mail_no_self_notified: "Ne želim biti opozorjen(a) na spremembe, ki jih naredim sam(a)"
+label_registration_activation_by_email: aktivacija raÄuna po e-poÅ¡ti
+label_registration_manual_activation: roÄna aktivacija raÄuna
+label_registration_automatic_activation: samodejna aktivacija raÄuna
+label_display_per_page: 'Na stran: %s'
+label_age: Starost
+label_change_properties: Sprememba lastnosti
+label_general: Splošno
+label_more: VeÄ
+label_scm: SCM
+label_plugins: VtiÄniki
+label_ldap_authentication: LDAP overovljanje
+label_downloads_abbr: D/L
+label_optional_description: Neobvezen opis
+label_add_another_file: Dodaj še eno datoteko
+label_preferences: Preference
+label_chronological_order: Kronološko
+label_reverse_chronological_order: Obrnjeno kronološko
+label_planning: NaÄrtovanje
+label_incoming_emails: PrihajajoÄa e-poÅ¡ta
+label_generate_key: Ustvari kljuÄ
+label_issue_watchers: Spremljevalci
+label_example: Vzorec
+
+button_login: Prijavi se
+button_submit: Pošlji
+button_save: Shrani
+button_check_all: OznaÄi vse
+button_uncheck_all: OdznaÄi vse
+button_delete: Izbriši
+button_create: Ustvari
+button_test: Testiraj
+button_edit: Uredi
+button_add: Dodaj
+button_change: Spremeni
+button_apply: Uporabi
+button_clear: PoÄisti
+button_lock: Zakleni
+button_unlock: Odkleni
+button_download: Prenesi
+button_list: Seznam
+button_view: Pogled
+button_move: Premakni
+button_back: Nazaj
+button_cancel: PrekliÄi
+button_activate: Aktiviraj
+button_sort: Razvrsti
+button_log_time: Beleži Äas
+button_rollback: Povrni na to verzijo
+button_watch: Spremljaj
+button_unwatch: Ne spremljaj
+button_reply: Odgovori
+button_archive: Arhiviraj
+button_unarchive: Odarhiviraj
+button_reset: Ponastavi
+button_rename: Preimenuj
+button_change_password: Spremeni geslo
+button_copy: Kopiraj
+button_annotate: Zapiši pripombo
+button_update: Posodobi
+button_configure: Konfiguriraj
+button_quote: Citiraj
+
+status_active: aktivni
+status_registered: registriran
+status_locked: zaklenjen
+
+text_select_mail_notifications: Izberite dejanja za katera naj bodo poslana oznanila preko e-pošto.
+text_regexp_info: npr. ^[A-Z0-9]+$
+text_min_max_length_info: 0 pomeni brez omejitev
+text_project_destroy_confirmation: Ali ste prepriÄani da želite izbrisati izbrani projekt in vse z njim povezane podatke?
+text_subprojects_destroy_warning: 'Njegov(i) podprojekt(i): %s bodo prav tako izbrisani.'
+text_workflow_edit: Izberite vlogo in zahtevek za urejanje poteka dela
+text_are_you_sure: Ali ste prepriÄani?
+text_journal_changed: Spremenjen iz %s na %s
+text_journal_set_to: Nastavi na %s
+text_journal_deleted: izbrisan
+text_tip_task_begin_day: naloga z zaÄetkom na ta dan
+text_tip_task_end_day: naloga z zakljuÄkom na ta dan
+text_tip_task_begin_end_day: naloga ki se zaÄne in konÄa ta dan
+text_project_identifier_info: 'Dovoljene so samo male Ärke (a-z), Å¡tevilke in vezaji.<br />Enkrat shranjen identifikator ne more biti spremenjen.'
+text_caracters_maximum: najveÄ %d znakov.
+text_caracters_minimum: Mora biti dolgo vsaj %d znake.
+text_length_between: Dolžina med %d in %d znaki.
+text_tracker_no_workflow: Potek dela za to vrsto zahtevka ni doloÄen
+text_unallowed_characters: Nedovoljeni znaki
+text_comma_separated: Dovoljenih je veÄ vrednosti (loÄenih z vejico).
+text_issues_ref_in_commit_messages: Zahtevki sklicev in popravkov v sporoÄilu predaje
+text_issue_added: Zahtevek %s je sporoÄil(a) %s.
+text_issue_updated: Zahtevek %s je posodobil(a) %s.
+text_wiki_destroy_confirmation: Ali ste prepriÄani da želite izbrisati ta wiki in vso njegovo vsebino?
+text_issue_category_destroy_question: Nekateri zahtevki (%d) so dodeljeni tej kategoriji. Kaj želite storiti?
+text_issue_category_destroy_assignments: Odstrani naloge v kategoriji
+text_issue_category_reassign_to: Ponovno dodeli zahtevke tej kategoriji
+text_user_mail_option: "Na neizbrane projekte boste prejemali le obvestila o zadevah ki jih spremljate ali v katere ste vkljuÄeni (npr. zahtevki katerih avtor(ica) ste)"
+text_no_configuration_data: "Vloge, vrste zahtevkov, statusi zahtevkov in potek dela Å¡e niso bili doloÄeni. \nZelo priporoÄljivo je, da naložite privzeto konfiguracijo, ki jo lahko kasneje tudi prilagodite."
+text_load_default_configuration: Naloži privzeto konfiguracijo
+text_status_changed_by_changeset: Dodano v zapis sprememb %s.
+text_issues_destroy_confirmation: 'Ali ste prepriÄani, da želite izbrisati izbrani(e) zahtevek(ke)?'
+text_select_project_modules: 'Izberite module, ki jih želite omogoÄiti za ta projekt:'
+text_default_administrator_account_changed: Spremenjen privzeti administratorski raÄun
+text_file_repository_writable: OmogoÄeno pisanje v shrambo datotek
+text_rmagick_available: RMagick je na voljo(neobvezno)
+text_destroy_time_entries_question: %.02f ur je bilo opravljenih na zahtevku, ki ga želite izbrisati. Kaj želite storiti?
+text_destroy_time_entries: Izbriši opravljene ure
+text_assign_time_entries_to_project: Predaj opravljene ure projektu
+text_reassign_time_entries: 'Prenesi opravljene ure na ta zahtevek:'
+text_user_wrote: '%s je napisal(a):'
+text_enumeration_destroy_question: '%d objektov je doloÄenih tej vrednosti.'
+text_enumeration_category_reassign_to: 'Ponastavi jih na to vrednost:'
+text_email_delivery_not_configured: "E-poÅ¡tna dostava ni nastavljena in oznanila so onemogoÄena.\nNastavite vaÅ¡ SMTP strežnik v config/email.yml in ponovno zaženite aplikacijo da ga omogoÄite.\n"
+text_repository_usernames_mapping: "Izberite ali posodobite Redmine uporabnika dodeljenega vsakemu uporabniškemu imenu najdenemu v zapisniku shrambe.\n Uporabniki z enakim Redmine ali shrambinem uporabniškem imenu ali e-poštnem naslovu so samodejno dodeljeni."
+text_diff_truncated: '... Ta sprememba je bila odsekana ker presega najveÄjo velikost ki je lahko prikazana.'
+
+default_role_manager: Upravnik
+default_role_developper: Razvijalec
+default_role_reporter: PoroÄevalec
+default_tracker_bug: HroÅ¡Ä
+default_tracker_feature: Funkcija
+default_tracker_support: Podpora
+default_issue_status_new: Nov
+default_issue_status_assigned: Dodeljen
+default_issue_status_resolved: Rešen
+default_issue_status_feedback: Povratna informacija
+default_issue_status_closed: ZakljuÄen
+default_issue_status_rejected: Zavrnjen
+default_doc_category_user: Uporabniška dokumentacija
+default_doc_category_tech: TehniÄna dokumentacija
+default_priority_low: Nizka
+default_priority_normal: ObiÄajna
+default_priority_high: Visoka
+default_priority_urgent: Urgentna
+default_priority_immediate: Takojšnje ukrepanje
+default_activity_design: Oblikovanje
+default_activity_development: Razvoj
+
+enumeration_issue_priorities: Prioritete zahtevkov
+enumeration_doc_categories: Kategorije dokumentov
+enumeration_activities: Aktivnosti (sledenje Äasa)
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+warning_attachments_not_saved: "%d file(s) could not be saved."
+field_editable: Editable
+text_plugin_assets_writable: Plugin assets directory writable
+label_display: Display
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/sr.yml b/lang/sr.yml
index 3ac7b7cd9..7455d20e0 100644
--- a/lang/sr.yml
+++ b/lang/sr.yml
@@ -34,7 +34,7 @@ activerecord_error_not_a_number: nije broj
activerecord_error_not_a_date: nije datum
activerecord_error_greater_than_start_date: mora biti veći od poÄetnog datuma
activerecord_error_not_same_project: ne pripada istom projektu
-activerecord_error_circular_dependency: Ova relacija bi kreirala kružnu zavisnost
+activerecord_error_circular_dependency: Ova relacija bi napravila kružnu zavisnost
general_fmt_age: %d g
general_fmt_age_plural: %d god.
@@ -51,24 +51,24 @@ 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
+general_day_names: Ponedeljak, Utorak, Sreda, ÄŒetvrtak, Petak, Subota, Nedelja
general_first_day_of_week: '1'
-notice_account_updated: Nalog je uspešno izmenjen.
+notice_account_updated: Nalog je uspešno promenjen.
notice_account_invalid_creditentials: Pogrešan korisnik ili lozinka
-notice_account_password_updated: Lozinka je uspešno izmenjena.
+notice_account_password_updated: Lozinka je uspešno promenjena.
notice_account_wrong_password: Pogrešna lozinka
-notice_account_register_done: Nalog je uspešno kreiran. Da bi ste aktivirali vaš nalog kliknite na link koji vam je poslat.
+notice_account_register_done: Nalog je uspešno napravljen. Da bi ste aktivirali vaš nalog kliknite na link koji vam je poslat.
notice_account_unknown_email: Nepoznati korisnik.
notice_can_t_change_password: Ovaj nalog koristi eksterni izvor prijavljivanja. Ne mogu da promenim šifru.
notice_account_lost_email_sent: Email sa uputstvima o izboru nove šifre je poslat na vašu adresu.
notice_account_activated: Vaš nalog je aktiviran. Možete se ulogovati.
notice_successful_create: Uspešna kreacija.
-notice_successful_update: Uspešna izmena.
+notice_successful_update: Uspešna promena.
notice_successful_delete: Uspešno brisanje.
notice_successful_connection: Uspešna konekcija.
notice_file_not_found: Stranica kojoj pokušavate da pristupite ne postoji ili je uklonjena.
-notice_locking_conflict: Podaci su izmenjeni od strane drugog korisnika.
+notice_locking_conflict: Podaci su promenjeni od strane drugog korisnika.
notice_not_authorized: Niste ovlašćeni da pristupite ovoj stranici.
notice_email_sent: Email je poslat %s
notice_email_error: Došlo je do greške pri slanju maila (%s)
@@ -77,13 +77,13 @@ notice_failed_to_save_issues: "Neuspešno snimanje %d kartica na %d izabrano: %s
notice_no_issue_selected: "Nijedna kartica nije izabrana! Molim, izaberite kartice koje želite za editujete."
error_scm_not_found: "Unos i/ili revizija ne postoji u spremištu."
-error_scm_command_failed: "An error occurred when trying to access the repository: %s"
+error_scm_command_failed: "Došlo je do greške pri pristupanju spremištu: %s"
mail_subject_lost_password: Vaša %s lozinka
-mail_body_lost_password: 'Da biste izmenili vašu lozinku, kliknite na sledeći link:'
-mail_subject_register: Aktivacija %s naloga
+mail_body_lost_password: 'Da biste promenili vašu lozinku, kliknite na sledeći link:'
+mail_subject_register: Aktivacija naloga %s
mail_body_register: 'Da biste aktivirali vaš nalog, kliknite na sledeći link:'
-mail_body_account_information_external: Mozete koristiti vas "%s" nalog da bi ste se prikljucili.
+mail_body_account_information_external: Mozete koristiti vas nalog "%s" da bi ste se prikljucili.
mail_body_account_information: Informacije o vasem nalogu
gui_validation_error: 1 greška
@@ -96,12 +96,12 @@ field_is_required: Zahtevano
field_firstname: Ime
field_lastname: Prezime
field_mail: Email
-field_filename: File
+field_filename: Fajl
field_filesize: VeliÄina
-field_downloads: Downloads
+field_downloads: Preuzimanja
field_author: Autor
-field_created_on: Kreirano
-field_updated_on: Izmenjeno
+field_created_on: Postavljeno
+field_updated_on: Promenjeno
field_field_format: Format
field_is_for_all: Za sve projekte
field_possible_values: Moguće vrednosti
@@ -115,25 +115,25 @@ field_project: Projekat
field_issue: Kartica
field_status: Status
field_notes: Beleške
-field_is_closed: Greška zatvorena
+field_is_closed: Kartica zatvorena
field_is_default: Podrazumevana vrednost
-field_tracker: Tracker
-field_subject: Subjekat
+field_tracker: Vrsta
+field_subject: Tema
field_due_date: Do datuma
field_assigned_to: Dodeljeno
field_priority: Prioritet
-field_fixed_version: Target version
+field_fixed_version: Verzija
field_user: Korisnik
field_role: Uloga
field_homepage: Homepage
field_is_public: Javni
-field_parent: Podprojekat od
-field_is_in_chlog: Kartice se prikazuju u changelog-u
-field_is_in_roadmap: Kartice se prikazuju u roadmap-u
-field_login: Login
+field_parent: Potprojekat od
+field_is_in_chlog: Kartice se prikazuju u dnevniku promena
+field_is_in_roadmap: Kartice se prikazuju u Redosledu
+field_login: Korisnik
field_mail_notification: Obaveštavanje putem mail-a
field_admin: Administrator
-field_last_login_on: Poslednja konekcija
+field_last_login_on: Poslednje prijavljivanje
field_language: Jezik
field_effective_date: Datum
field_password: Lozinka
@@ -150,14 +150,14 @@ field_attr_firstname: Atribut imena
field_attr_lastname: Atribut prezimena
field_attr_mail: Atribut email-a
field_onthefly: Kreacija naloga "On-the-fly"
-field_start_date: Start
+field_start_date: PoÄetak
field_done_ratio: %% Završeno
field_auth_source: Vrsta prijavljivanja
field_hide_mail: Sakrij moju email adresu
field_comments: Komentar
field_url: URL
field_start_page: PoÄetna strana
-field_subproject: Podprojekat
+field_subproject: Potprojekat
field_hours: Sati
field_activity: Aktivnost
field_spent_on: Datum
@@ -175,7 +175,7 @@ setting_app_title: Naziv aplikacije
setting_app_subtitle: Podnaslov aplikacije
setting_welcome_text: Tekst dobrodošlice
setting_default_language: Podrazumevani jezik
-setting_login_required: Prijavljivanje obaveyno
+setting_login_required: Prijavljivanje je obavezno
setting_self_registration: Samoregistracija je dozvoljena
setting_attachment_max_size: Maksimalna velicina Attachment-a
setting_issues_export_limit: Max broj kartica u exportu
@@ -188,27 +188,28 @@ setting_autofetch_changesets: Autofetch commits
setting_sys_api_enabled: Ukljuci WS za menadžment spremišta
setting_commit_ref_keywords: Referentne kljuÄne reÄi
setting_commit_fix_keywords: Fiksne kljuÄne reÄi
-setting_autologin: Autologin
+setting_autologin: Automatsko prijavljivanje
setting_date_format: Format datuma
setting_cross_project_issue_relations: Dozvoli relacije kartica izmeÄ‘u razliÄitih projekata
setting_issue_list_default_columns: Podrazumevana kolona se prikazuje na listi kartica
setting_repositories_encodings: Kodna stranica spremišta
setting_emails_footer: Zaglavlje emaila
+label_example: Primer
label_user: Korisnik
label_user_plural: Korisnici
label_user_new: Novi korisnik
label_project: Projekat
label_project_new: Novi projekat
label_project_plural: Projekti
-label_project_all: Svi Projekti
+label_project_all: Svi projekti
label_project_latest: Poslednji projekat
label_issue: Kartica
label_issue_new: Nova kartica
label_issue_plural: Kartice
label_issue_view_all: Pregled svih kartica
-label_document: Dokumenat
-label_document_new: Novi dokumenat
+label_document: Dokument
+label_document_new: Novi dokument
label_document_plural: Dokumenti
label_role: Uloga
label_role_plural: Uloge
@@ -217,39 +218,39 @@ label_role_and_permissions: Uloge i prava
label_member: ÄŒlan
label_member_new: Novi Älan
label_member_plural: ÄŒlanovi
-label_tracker: Tracker
-label_tracker_plural: Trackers
-label_tracker_new: Novi tracker
+label_tracker: Vrsta
+label_tracker_plural: Vrste
+label_tracker_new: Nova vrsta
label_workflow: Tok rada
label_issue_status: Status kartice
label_issue_status_plural: Statusi kartica
label_issue_status_new: Novi status
-label_issue_category: Kategorij kartice
+label_issue_category: Kategorija kartice
label_issue_category_plural: Kategorije kartica
label_issue_category_new: Nova kategorija
label_custom_field: KorisniÄki definisano polje
label_custom_field_plural: KorisniÄki definisana polja
label_custom_field_new: Novo korisniÄki definisano polje
-label_enumerations: Enumeracije
+label_enumerations: Konstante
label_enumeration_new: Nova vrednost
label_information: Informacija
label_information_plural: Informacije
-label_please_login: Molim ulogujte se
+label_please_login: Molim prijavite se
label_register: Registracija
label_password_lost: Izgubljena lozinka
-label_home: Home
-label_my_page: Moja Stranica
+label_home: Naslovna stranica
+label_my_page: Moja stranica
label_my_account: Moj nalog
label_my_projects: Moji projekti
label_administration: Administracija
-label_login: Login
-label_logout: Logout
+label_login: Korisnik
+label_logout: Odjavi me
label_help: Pomoć
label_reported_issues: Prijavljene kartice
-label_assigned_to_me_issues: Kartice meni dodeljene
-label_last_login: Poslednja konekcija
-label_last_updates: Poslednje izmene
-label_last_updates_plural: %d poslednje izmenjene
+label_assigned_to_me_issues: Moje kartice
+label_last_login: Poslednje prijavljivanje
+label_last_updates: Poslednje promene
+label_last_updates_plural: %d poslednje promenjene
label_registered_on: Registrovano
label_activity: Aktivnost
label_new: Novo
@@ -259,8 +260,8 @@ label_authentication: Prijavljivanje
label_auth_source: NaÄin prijavljivanja
label_auth_source_new: Novi naÄin prijavljivanja
label_auth_source_plural: NaÄini prijavljivanja
-label_subproject_plural: Podprojekti
-label_min_max_length: Min - Max velicina
+label_subproject_plural: Potprojekti
+label_min_max_length: Min - Max veliÄina
label_list: Liste
label_date: Datum
label_integer: Integer
@@ -272,7 +273,7 @@ label_attribute_plural: Atributi
label_download: %d Download
label_download_plural: %d Downloads
label_no_data: Nema podataka za prikaz
-label_change_status: Izmena statusa
+label_change_status: Promena statusa
label_history: Istorija
label_attachment: Fajl
label_attachment_new: Novi fajl
@@ -281,13 +282,13 @@ label_attachment_plural: Fajlovi
label_report: Izveštaj
label_report_plural: Izveštaji
label_news: Novosti
-label_news_new: Dodaj novosti
+label_news_new: Dodaj novost
label_news_plural: Novosti
label_news_latest: Poslednje novosti
label_news_view_all: Pregled svih novosti
-label_change_log: Change log
+label_change_log: Dnevnik promena
label_settings: Podešavanja
-label_overview: Overview
+label_overview: Pregled
label_version: Verzija
label_version_new: Nova verzija
label_version_plural: Verzije
@@ -296,9 +297,9 @@ label_export_to: Izvoz u
label_read: ÄŒitaj...
label_public_projects: Javni projekti
label_open_issues: Otvoren
-label_open_issues_plural: Otvoreni
-label_closed_issues: Zatvoreni
-label_closed_issues_plural: Zatvoreni
+label_open_issues_plural: Otvoreno
+label_closed_issues: Zatvoren
+label_closed_issues_plural: Zatvoreno
label_total: Ukupno
label_permissions: Dozvole
label_current_status: Trenutni status
@@ -317,8 +318,8 @@ label_calendar: Kalendar
label_months_from: Meseci od
label_gantt: Gantt
label_internal: Interno
-label_last_changes: Poslednjih %d izmena
-label_change_view_all: Prikaz svih izmena
+label_last_changes: Poslednjih %d promena
+label_change_view_all: Prikaz svih promena
label_personalize_page: Personalizuj ovu stranicu
label_comment: Komentar
label_comment_plural: Komentari
@@ -332,26 +333,26 @@ label_filter_add: Dodaj filter
label_filter_plural: Filter
label_equals: je
label_not_equals: nije
-label_in_less_than: je manji od
-label_in_more_than: je veci od
-label_in: u
+label_in_less_than: za manje od
+label_in_more_than: za više od
+label_in: za taÄno
label_today: danas
label_this_week: ove nedelje
-label_less_than_ago: manje nego dana
-label_more_than_ago: više nego dana
-label_ago: pre dana
+label_less_than_ago: pre manje od
+label_more_than_ago: pre više od
+label_ago: pre taÄno
label_contains: Sadrži
label_not_contains: ne sadrži
label_day_plural: dana
label_repository: Spremište
label_browse: Pregled
-label_modification: %d izmena
-label_modification_plural: %d izmena
+label_modification: %d promena
+label_modification_plural: %d promena
label_revision: Revizija
label_revision_plural: Revizije
label_added: dodato
label_modified: modifikovano
-label_deleted: izmenjeno
+label_deleted: promenjeno
label_latest_revision: Poslednja revizija
label_latest_revision_plural: Poslednje revizije
label_view_revisions: Pregled revizija
@@ -361,7 +362,7 @@ label_sort_highest: Premesti na vrh
label_sort_higher: premesti na gore
label_sort_lower: Premesti na dole
label_sort_lowest: Premesti na dno
-label_roadmap: Roadmap
+label_roadmap: Redosled
label_roadmap_due_in: Završava se za %s
label_roadmap_overdue: %s kasni
label_roadmap_no_issues: Nema kartica za ovu verziju
@@ -369,8 +370,8 @@ label_search: Traži
label_result_plural: Rezultati
label_all_words: Sve reÄi
label_wiki: Wiki
-label_wiki_edit: Wiki izmena
-label_wiki_edit_plural: Wiki izmene
+label_wiki_edit: Wiki promena
+label_wiki_edit_plural: Wiki promene
label_wiki_page: Wiki stranica
label_wiki_page_plural: Wiki stranice
label_index_by_title: Indeks po naslovima
@@ -378,13 +379,13 @@ label_index_by_date: Indeks po datumu
label_current_version: Trenutna verzija
label_preview: Brzi pregled
label_feed_plural: Feeds
-label_changes_details: Detalji svih izmena
+label_changes_details: Detalji svih promena
label_issue_tracking: Praćenje kartica
-label_spent_time: Potrošeno vremena
+label_spent_time: Utrošeno vremena
label_f_hour: %.2f Äasa
label_f_hour_plural: %.2f Äasova
label_time_tracking: Praćenje vremena
-label_change_plural: Izmene
+label_change_plural: Promene
label_statistics: Statistika
label_commits_per_month: Commit-a po mesecu
label_commits_per_author: Commit-a po autoru
@@ -395,7 +396,7 @@ label_options: Opcije
label_copy_workflow_from: Kopiraj tok rada od
label_permissions_report: Izveštaj o dozvolama
label_watched_issues: Praćene kartice
-label_related_issues: Kartice u vezi
+label_related_issues: Povezane kartice
label_applied_status: Primenjen status
label_loading: UÄitavam...
label_relation_new: Nova relacija
@@ -429,18 +430,18 @@ label_week: Nedelja
label_date_from: Od
label_date_to: Do
label_language_based: Bazirano na jeziku
-label_sort_by: Sortiraj po %s
+label_sort_by: Uredi po %s
label_send_test_email: Pošalji probni email
-label_feeds_access_key_created_on: RSS kljuÄ za pristup je kreiran pre %s
-label_module_plural: Modulovi
+label_feeds_access_key_created_on: RSS kljuÄ za pristup je napravljen pre %s
+label_module_plural: Moduli
label_added_time_by: Dodato pre %s %s
-label_updated_time: Izmenjeno pre %s
+label_updated_time: Promenjeno pre %s
label_jump_to_a_project: Prebaci se na projekat...
label_file_plural: Fajlovi
-label_changeset_plural: Skupovi izmena
+label_changeset_plural: Skupovi promena
label_default_columns: Podrazumevane kolone
-label_no_change_option: (Bez izmena)
-label_bulk_edit_selected_issues: ZajedniÄka izmena izabranih kartica
+label_no_change_option: (Bez promena)
+label_bulk_edit_selected_issues: ZajedniÄka promena izabranih kartica
label_theme: Tema
label_default: Podrazumevana
label_search_titles_only: Pretraga samo naslova
@@ -448,39 +449,39 @@ label_user_mail_option_all: "Za bilo koji događaj na svim mojim projektima"
label_user_mail_option_selected: "Za bilo koji događaj za samo izabrane projekte..."
label_user_mail_option_none: "Samo za stvari koje pratim ili u kojima uÄestvujem"
-button_login: Login
+button_login: Prijavi
button_submit: Pošalji
-button_save: Snimi
+button_save: SaÄuvaj
button_check_all: OznaÄi sve
button_uncheck_all: IskljuÄi sve
-button_delete: Briši
-button_create: Kreiraj
-button_test: Testiraj
-button_edit: Izmene
-button_add: Dodavanje
-button_change: Izmena
-button_apply: Primena
-button_clear: Brisanje
-button_lock: ZakljuÄavanje
-button_unlock: OdkljuÄavanje
-button_download: Download
-button_list: Lista
+button_delete: Obriši
+button_create: Napravi
+button_test: Proveri
+button_edit: Menjanje
+button_add: Dodaj
+button_change: Promeni
+button_apply: Primeni
+button_clear: Poništi
+button_lock: ZakljuÄaj
+button_unlock: OtkljuÄaj
+button_download: Preuzmi
+button_list: Spisak
button_view: Pregled
-button_move: Premeštanje
+button_move: Premesti
button_back: Nazad
-button_cancel: Odustajanje
+button_cancel: Odustani
button_activate: Aktiviraj
-button_sort: Sortiranje
-button_log_time: Log time
+button_sort: Uredi
+button_log_time: Zapiši vreme
button_rollback: Izvrši rollback na ovu verziju
-button_watch: Praćenje
-button_unwatch: Prekid praćenja
-button_reply: Odgovor
-button_archive: Arhiviranje
-button_unarchive: Dearhiviranje
-button_reset: Reset
-button_rename: Promena imena
-button_change_password: Izmena lozinke
+button_watch: Prati
+button_unwatch: Prekini praćenje
+button_reply: Odgovori
+button_archive: Arhiviraj
+button_unarchive: Dearhiviraj
+button_reset: Poništi
+button_rename: Promeni ime
+button_change_password: Promeni lozinku
status_active: aktivan
status_registered: registrovan
@@ -492,7 +493,7 @@ text_min_max_length_info: 0 znaÄi bez restrikcija
text_project_destroy_confirmation: Da li ste sigurni da želite da izbrišete ovaj projekat i sve njegove podatke?
text_workflow_edit: Select a role and a tracker to edit the workflow
text_are_you_sure: Da li ste sigurni ?
-text_journal_changed: izmenjen iz %s u %s
+text_journal_changed: promenjen iz %s u %s
text_journal_set_to: postavi na %s
text_journal_deleted: izbrisano
text_tip_task_begin_day: Zadaci koji poÄinju ovog dana
@@ -506,12 +507,12 @@ text_unallowed_characters: Nedozvoljeni karakteri
text_comma_separated: Višestruke vrednosti su dozvoljene (razdvojene zarezom).
text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
text_issue_added: Kartica %s je prijavljena (by %s).
-text_issue_updated: Kartica %s je izmenjena (by %s).
+text_issue_updated: Kartica %s je promenjena (by %s).
text_wiki_destroy_confirmation: Da li ste sigurni da želite da izbrišete ovaj wiki i svu njegovu sadržinu ?
text_issue_category_destroy_question: Neke kartice (%d) su dodeljene ovoj kategoriji. Šta želite da uradite ?
text_issue_category_destroy_assignments: Ukloni dodeljivanje kategorija
text_issue_category_reassign_to: Ponovo dodeli kartice ovoj kategoriji
-text_user_mail_option: "Za neizabrane projekte, primaćete obaveÅ¡tenja samo o stvarima koje pratite ili u kojima uÄestvujete (npr. kartice koje ste vi kreirali ili koje su vama dodeljene)."
+text_user_mail_option: "Za neizabrane projekte, primaćete obaveÅ¡tenja samo o stvarima koje pratite ili u kojima uÄestvujete (npr. kartice koje ste vi napravili ili koje su vama dodeljene)."
default_role_manager: Menadžer
default_role_developper: Developer
@@ -528,7 +529,7 @@ default_issue_status_rejected: OdbaÄeno
default_doc_category_user: KorisniÄka dokumentacija
default_doc_category_tech: TehniÄka dokumentacija
default_priority_low: Nizak
-default_priority_normal: Normalan
+default_priority_normal: Redovan
default_priority_high: Visok
default_priority_urgent: Hitan
default_priority_immediate: Odmah
@@ -539,16 +540,16 @@ enumeration_issue_priorities: Prioriteti kartica
enumeration_doc_categories: Kategorija dokumenata
enumeration_activities: Aktivnosti (praćenje vremena))
label_float: Float
-button_copy: Copy
+button_copy: Iskopiraj
setting_protocol: Protocol
-label_user_mail_no_self_notified: "Ne želim da budem obaveštavan o izmenama koje sam pravim"
+label_user_mail_no_self_notified: "Ne želim da budem obaveštavan o promenama koje sam pravim"
setting_time_format: Format vremena
label_registration_activation_by_email: aktivacija naloga putem email-a
mail_subject_account_activation_request: %s zahtev za aktivacijom naloga
mail_body_account_activation_request: 'Novi korisnik (%s) se registrovao. Njegov nalog Äeka vaÅ¡e odobrenje:'
label_registration_automatic_activation: automatska aktivacija naloga
label_registration_manual_activation: ruÄna aktivacija naloga
-notice_account_pending: "VaÅ¡ nalog je kreiran i Äeka odobrenje administratora."
+notice_account_pending: "VaÅ¡ nalog je napravljen i Äeka odobrenje administratora."
field_time_zone: Vremenska zona
text_caracters_minimum: Mora biti minimum %d karaktera dugaÄka.
setting_bcc_recipients: '"Blind carbon copy" primaoci (bcc)'
@@ -562,23 +563,23 @@ 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
+button_update: Promeni
+label_change_properties: Promeni svojstva
+label_general: Opšte
+label_repository_plural: Spremišta
+label_associated_revisions: Dodeljene revizije
setting_user_format: Users display format
text_status_changed_by_changeset: Applied in changeset %s.
-label_more: More
+label_more: Još
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(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
+label_issue_added: Kartica dodata
+label_issue_updated: Kartica promenjena
+label_document_added: Dokument dodat
+label_message_posted: Poruka dodata
+label_file_added: Fajl dodat
+label_news_added: Novost dodata
project_module_boards: Boards
project_module_issue_tracking: Issue tracking
project_module_wiki: Wiki
@@ -594,52 +595,116 @@ button_configure: Configure
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
+label_this_month: ovog meseca
+label_last_n_days: poslednjih %d dana
+label_all_time: sva vremena
+label_this_year: ove godine
+label_date_range: Raspon datuma
+label_last_week: prošle nedelje
+label_yesterday: juÄe
+label_last_month: prošlog meseca
+label_add_another_file: Dodaj još jedan fajl
+label_optional_description: Opcioni opis
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
+label_chronological_order: U hronološkom redosledu
field_comments_sorting: Display comments
-label_reverse_chronological_order: In reverse chronological order
+label_reverse_chronological_order: U obrnutom hronološkom redosledu
label_preferences: Preferences
setting_display_subprojects_issues: Display subprojects issues on main projects by default
-label_overall_activity: Overall activity
+label_overall_activity: Ukupna aktivnost
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
+label_planning: Planiranje
+text_subprojects_destroy_warning: 'I potprojekti projekta: %s će takođe biti obrisani.'
+label_and_its_subprojects: %s i potprojekti
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
+label_duplicated_by: ponovljen kao
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
+label_incoming_emails: Dolazeće e-poruke
+label_generate_key: GeneriÅ¡i kljuÄ
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
+label_issue_watchers: PosmatraÄi
setting_commit_logs_encoding: Commit messages encoding
button_quote: Quote
setting_sequential_project_identifiers: Generate sequential project identifiers
notice_unable_delete_version: Unable to delete version
-label_renamed: renamed
-label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+label_renamed: preimenovano
+label_copied: iskopirano
+setting_plain_text_mail: plain text only (no HTML)
+permission_view_files: View files
+permission_edit_issues: Edit issues
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_public_queries: Manage public queries
+permission_add_issues: Add issues
+permission_log_time: Log spent time
+permission_view_changesets: View changesets
+permission_view_time_entries: View spent time
+permission_manage_versions: Manage versions
+permission_manage_wiki: Manage wiki
+permission_manage_categories: Manage issue categories
+permission_protect_wiki_pages: Protect wiki pages
+permission_comment_news: Comment news
+permission_delete_messages: Delete messages
+permission_select_project_modules: Select project modules
+permission_manage_documents: Manage documents
+permission_edit_wiki_pages: Edit wiki pages
+permission_add_issue_watchers: Add watchers
+permission_view_gantt: View gantt chart
+permission_move_issues: Move issues
+permission_manage_issue_relations: Manage issue relations
+permission_delete_wiki_pages: Delete wiki pages
+permission_manage_boards: Manage boards
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_view_wiki_edits: View wiki history
+permission_add_messages: Post messages
+permission_view_messages: View messages
+permission_manage_files: Manage files
+permission_edit_issue_notes: Edit notes
+permission_manage_news: Manage news
+permission_view_calendar: View calendrier
+permission_manage_members: Manage members
+permission_edit_messages: Edit messages
+permission_delete_issues: Delete issues
+permission_view_issue_watchers: View watchers list
+permission_manage_repository: Manage repository
+permission_commit_access: Commit access
+permission_browse_repository: Browse repository
+permission_view_documents: View documents
+permission_edit_project: Edit project
+permission_add_issue_notes: Add notes
+permission_save_queries: Save queries
+permission_view_wiki_pages: View wiki
+permission_rename_wiki_pages: Rename wiki pages
+permission_edit_time_entries: Edit time logs
+permission_edit_own_issue_notes: Edit own notes
+setting_gravatar_enabled: Use Gravatar user icons
+permission_edit_own_messages: Edit own messages
+permission_delete_own_messages: Delete own messages
+text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+label_user_activity: "%s's activity"
+label_updated_time_by: Updated by %s %s ago
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/sv.yml b/lang/sv.yml
index 5df46232d..884dd16a4 100644
--- a/lang/sv.yml
+++ b/lang/sv.yml
@@ -11,7 +11,7 @@ actionview_datehelper_time_in_words_hour_about: cirka en timme
actionview_datehelper_time_in_words_hour_about_plural: cirka %d timmar
actionview_datehelper_time_in_words_hour_about_single: cirka en timme
actionview_datehelper_time_in_words_minute: 1 minut
-actionview_datehelper_time_in_words_minute_half: en halv minute
+actionview_datehelper_time_in_words_minute_half: en halv minut
actionview_datehelper_time_in_words_minute_less_than: mindre än en minut
actionview_datehelper_time_in_words_minute_plural: %d minuter
actionview_datehelper_time_in_words_minute_single: 1 minut
@@ -22,19 +22,19 @@ actionview_instancetag_blank_option: Var god välj
activerecord_error_inclusion: finns inte i listan
activerecord_error_exclusion: är reserverad
activerecord_error_invalid: är ogiltig
-activerecord_error_confirmation: överränsstämmer inte med bekräftelsen
+activerecord_error_confirmation: överensstämmer inte med bekräftelsen
activerecord_error_accepted: måste accepteras
activerecord_error_empty: får inte vara tom
activerecord_error_blank: får inte vara tom
activerecord_error_too_long: är för lång
activerecord_error_too_short: är för kort
activerecord_error_wrong_length: har fel längd
-activerecord_error_taken: har redan blivit tagen
+activerecord_error_taken: har redan tagits
activerecord_error_not_a_number: är inte ett nummer
activerecord_error_not_a_date: är inte ett korrekt datum
activerecord_error_greater_than_start_date: måste vara senare än startdatumet
-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: tillhör inte samma projekt
+activerecord_error_circular_dependency: Denna relation skulle skapa ett cirkulärt beroende
general_fmt_age: %d år
general_fmt_age_plural: %d år
@@ -52,35 +52,51 @@ 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
-general_first_day_of_week: '7'
+general_first_day_of_week: '1'
notice_account_updated: Kontot har uppdaterats
notice_account_invalid_creditentials: Fel användarnamn eller lösenord
notice_account_password_updated: Lösenordet har uppdaterats
notice_account_wrong_password: Fel lösenord
-notice_account_register_done: Kontot har skapats.
-notice_account_unknown_email: Okäns användare.
-notice_can_t_change_password: Detta konto använder en extern authentikeringskälla. Det går inte att byta lösenord.
-notice_account_lost_email_sent: Ett email med instruktioner om hur man väljer ett nytt lösenord har skickats till dig.
+notice_account_register_done: Kontot har skapats. För att aktivera kontot, klicka på länken i mailet som skickades till dig.
+notice_account_unknown_email: Okänd användare.
+notice_can_t_change_password: Detta konto använder en extern autentiseringskälla. Det går inte att byta lösenord.
+notice_account_lost_email_sent: Ett mail med instruktioner om hur man väljer ett nytt lösenord har skickats till dig.
notice_account_activated: Ditt konto har blivit aktiverat. Du kan nu logga in.
-notice_successful_create: Lyckat skapande.
-notice_successful_update: Lyckad uppdatering.
-notice_successful_delete: Lyckad borttagning.
-notice_successful_connection: Lyckad uppkoppling.
-notice_file_not_found: Sidan du försökte komma åt existerar inte eller har blivit borttagen.
+notice_successful_create: Skapandet lyckades.
+notice_successful_update: Uppdatering lyckades.
+notice_successful_delete: Borttagning lyckades.
+notice_successful_connection: Uppkoppling lyckades.
+notice_file_not_found: Sidan du försökte komma åt existerar inte eller är borttagen.
notice_locking_conflict: Data har uppdaterats av en annan användare.
-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: Du saknar behörighet att komma åt den här sidan.
+notice_email_sent: Ett mail skickades till %s
+notice_email_error: Ett fel inträffade när mail skickades (%s)
+notice_feeds_access_key_reseted: Din RSS-nyckel återställdes.
+notice_failed_to_save_issues: "Misslyckades att spara %d ärende(n) på %d valt: %s."
+notice_no_issue_selected: "Inget ärende är markerat! Var vänlig, markera de ärenden du vill ändra."
+notice_account_pending: "Ditt konto skapades och avvaktar nu administratörens godkännande."
+notice_default_data_loaded: Standardkonfiguration inläst.
+notice_unable_delete_version: Denna version var inte möjlig att ta bort.
-error_scm_not_found: "Inlägg och/eller revision finns inte i repositoriet."
-error_scm_command_failed: "An error occurred when trying to access the repository: %s"
+error_can_t_load_default_data: "Standardkonfiguration gick inte att läsa in: %s"
+error_scm_not_found: "Inlägg och/eller revision finns inte i detta repository."
+error_scm_command_failed: "Ett fel inträffade vid försök att nå repositoryt: %s"
+error_scm_annotate: "Inlägget existerar inte eller kan inte kommenteras."
+error_issue_not_found_in_project: 'Ärendet hittades inte eller så tillhör det inte detta projekt'
+
+warning_attachments_not_saved: "%d fil(er) kunde inte sparas."
mail_subject_lost_password: Ditt %s lösenord
-mail_body_lost_password: 'För att ändra lösenord, följ denna länk:'
-mail_subject_register: Ditt %s kontoaktivering
-mail_body_register: 'För att aktivera ditt konto, använd följande länk.'
+mail_body_lost_password: 'För att ändra ditt lösenord, klicka på följande länk:'
+mail_subject_register: Din %s kontoaktivering
+mail_body_register: 'För att aktivera ditt konto, klicka på följande länk:'
+mail_body_account_information_external: Du kan använda ditt "%s"-konto för att logga in.
+mail_body_account_information: Din kontoinformation
+mail_subject_account_activation_request: %s begäran om kontoaktivering
+mail_body_account_activation_request: 'En ny användare (%s) har registrerat sig och avvaktar ditt godkännande:'
+mail_subject_reminder: "%d ärende(n) har deadline under de kommande dagarna"
+mail_body_reminder: "%d ärende(n) som är tilldelat dig har deadline under de %d dagarna:"
gui_validation_error: 1 fel
gui_validation_error_plural: %d fel
@@ -91,7 +107,7 @@ field_summary: Sammanfattning
field_is_required: Obligatorisk
field_firstname: Förnamn
field_lastname: Efternamn
-field_mail: Email
+field_mail: Mail
field_filename: Fil
field_filesize: Storlek
field_downloads: Nerladdningar
@@ -101,91 +117,174 @@ field_updated_on: Uppdaterad
field_field_format: Format
field_is_for_all: För alla projekt
field_possible_values: Möjliga värden
-field_regexp: Regular expression
+field_regexp: Reguljärt uttryck
field_min_length: Minimilängd
-field_max_length: Maximumlängd
+field_max_length: Maxlängd
field_value: Värde
field_category: Kategori
field_title: Titel
field_project: Projekt
-field_issue: Brist
+field_issue: Ärende
field_status: Status
field_notes: Anteckningar
-field_is_closed: Brist stängd
-field_is_default: Defaultstatus
-field_tracker: Tracker
-field_subject: Rubrik
-field_due_date: Färdigdatum
-field_assigned_to: Tilldelad
+field_is_closed: Ärendet är stängt
+field_is_default: Standardvärde
+field_tracker: Ärendetyp
+field_subject: Ämne
+field_due_date: Deadline
+field_assigned_to: Tilldelad till
field_priority: Prioritet
-field_fixed_version: Target version
+field_fixed_version: Versionsmål
field_user: Användare
field_role: Roll
field_homepage: Hemsida
-field_is_public: Offentlig
-field_parent: Delprojekt av
-field_is_in_chlog: Brister visade i ändringslogg
-field_is_in_roadmap: Bsiter visade i roadmap
-field_login: Inloggning
-field_mail_notification: Emailnotifieringar
+field_is_public: Publik
+field_parent: Underprojekt till
+field_is_in_chlog: Visa ärenden i ändringslogg
+field_is_in_roadmap: Visa ärenden i roadmap
+field_login: Användarnamn
+field_mail_notification: Mailnotifieringar
field_admin: Administratör
field_last_login_on: Senaste inloggning
field_language: Språk
field_effective_date: Datum
field_password: Lösenord
field_new_password: Nytt lösenord
-field_password_confirmation: Bekräfta
+field_password_confirmation: Bekräfta lösenord
field_version: Version
field_type: Typ
field_host: Värddator
field_port: Port
field_account: Konto
-field_base_dn: Bas DN
+field_base_dn: Bas-DN
field_attr_login: Inloggningsattribut
-field_attr_firstname: Förnamnattribut
-field_attr_lastname: Efternamnattribut
-field_attr_mail: Emailattribut
+field_attr_firstname: Förnamnsattribut
+field_attr_lastname: Efternamnsattribut
+field_attr_mail: Mailattribut
field_onthefly: On-the-fly användarskapning
field_start_date: Start
-field_done_ratio: %% Done
-field_auth_source: Authentikeringsläge
-field_hide_mail: Dölj min emailadress
-field_comment: Kommentar
+field_done_ratio: %% Klart
+field_auth_source: Autentiseringsläge
+field_hide_mail: Dölj min mailadress
+field_comments: Kommentar
field_url: URL
field_start_page: Startsida
-field_subproject: Delprojekt
+field_subproject: Underprojekt
field_hours: Timmar
field_activity: Aktivitet
field_spent_on: Datum
field_identifier: Identifierare
-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: Default value
+field_is_filter: Använd som filter
+field_issue_to_id: Relaterade ärenden
+field_delay: Fördröjning
+field_assignable: Ärenden kan tilldelas denna roll
+field_redirect_existing_links: Omdirigera existerande länkar
+field_estimated_hours: Estimerad tid
+field_column_names: Kolumner
+field_time_zone: Tidszon
+field_searchable: Sökbar
+field_default_value: Standardvärde
+field_comments_sorting: Visa kommentarer
+field_parent_title: Föräldersida
+field_editable: Redigerbar
-setting_app_title: Applikationstitel
-setting_app_subtitle: Applicationsunderrubrik
-setting_welcome_text: Välkommentext
-setting_default_language: Default språk
-setting_login_required: Authent. obligatoriskt
-setting_self_registration: Självregistrering påslaget
-setting_attachment_max_size: Bifogad maxstorlek
-setting_issues_export_limit: Brist exportgräns
-setting_mail_from: Emailavsändare
+setting_app_title: Applikationsrubrik
+setting_app_subtitle: Applikationsunderrubrik
+setting_welcome_text: Välkomsttext
+setting_default_language: Standardspråk
+setting_login_required: Kräver inloggning
+setting_self_registration: Självregistrering
+setting_attachment_max_size: Maxstorlek på bilaga
+setting_issues_export_limit: Exportgräns för ärenden
+setting_mail_from: Mailavsändare
+setting_bcc_recipients: Hemlig kopia (bcc) till mottagare
+setting_plain_text_mail: Oformaterad text i mail (ingen HTML)
setting_host_name: Värddatornamn
-setting_text_formatting: Textformattering
-setting_wiki_compression: Wiki historiekomprimering
-setting_feeds_limit: Feed innehållsgräns
+setting_text_formatting: Textformatering
+setting_wiki_compression: Komprimering av wikihistorik
+setting_feeds_limit: Innehållsgräns för Feed
+setting_default_projects_public: Nya projekt är publika som standard
setting_autofetch_changesets: Automatisk hämtning av commits
-setting_sys_api_enabled: Aktivera WS för repository management
-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_sys_api_enabled: Aktivera WS för repository-hantering
+setting_commit_ref_keywords: Referens-nyckelord
+setting_commit_fix_keywords: Fix-nyckelord
+setting_autologin: Automatisk inloggning
+setting_date_format: Datumformat
+setting_time_format: Tidsformat
+setting_cross_project_issue_relations: Tillåt ärenderelationer mellan projekt
+setting_issue_list_default_columns: Standardkolumner i ärendelistan
+setting_repositories_encodings: Teckenuppsättningar för repositoriy
+setting_commit_logs_encoding: Teckenuppsättning för commit-meddelanden
+setting_emails_footer: Mailsignatur
+setting_protocol: Protokoll
+setting_per_page_options: Alternativ, objekt per sida
+setting_user_format: Visningsformat för användare
+setting_activity_days_default: Dagar som visas på projektaktivitet
+setting_display_subprojects_issues: Visa ärenden från underprojekt i huvudprojekt som standard
+setting_enabled_scm: Aktivera SCM
+setting_mail_handler_api_enabled: Aktivera WS for inkommande mail
+setting_mail_handler_api_key: API-nyckel
+setting_sequential_project_identifiers: Generera projektidentifierare sekventiellt
+setting_gravatar_enabled: Använd Gravatar-avatarer
+setting_diff_max_lines_displayed: Maximalt antal synliga rader i diff
+setting_repository_log_display_limit: Maximalt antal revisioner i filloggen
+
+permission_edit_project: Ändra projekt
+permission_select_project_modules: Välja projektmoduler
+permission_manage_members: Hantera medlemmar
+permission_manage_versions: Hantera versioner
+permission_manage_categories: Hantera ärendekategorier
+permission_add_issues: Lägga till ärende
+permission_edit_issues: Ändra ärende
+permission_manage_issue_relations: Hantera ärenderelationer
+permission_add_issue_notes: Lägga till notering
+permission_edit_issue_notes: Ändra noteringar
+permission_edit_own_issue_notes: Ändra egna noteringar
+permission_move_issues: Flytta ärenden
+permission_delete_issues: Ta bort ärenden
+permission_manage_public_queries: Hantera publika frågor
+permission_save_queries: Spara frågor
+permission_view_gantt: Visa Gantt-schema
+permission_view_calendar: Visa kalender
+permission_view_issue_watchers: Visa bevakarlista
+permission_add_issue_watchers: Lägga till bevakare
+permission_log_time: Logga spenderad tid
+permission_view_time_entries: Visa spenderad tid
+permission_edit_time_entries: Ändra tidloggar
+permission_edit_own_time_entries: Ändra egna tidloggar
+permission_manage_news: Hantera nyheter
+permission_comment_news: Kommentera nyheter
+permission_manage_documents: Hantera dokument
+permission_view_documents: Visa dokument
+permission_manage_files: Hantera filer
+permission_view_files: Visa filer
+permission_manage_wiki: Hantera wiki
+permission_rename_wiki_pages: Byta namn på wikisidor
+permission_delete_wiki_pages: Ta bort wikisidor
+permission_view_wiki_pages: Visa wiki
+permission_view_wiki_edits: Visa wikihistorik
+permission_edit_wiki_pages: Ändra wikisidor
+permission_delete_wiki_pages_attachments: Ta bort bilagor
+permission_protect_wiki_pages: Skydda wikisidor
+permission_manage_repository: Hantera repository
+permission_browse_repository: Bläddra i repository
+permission_view_changesets: Visa changesets
+permission_commit_access: Commit-tillgång
+permission_manage_boards: Hantera forum
+permission_view_messages: Visa meddelanden
+permission_add_messages: Lägg till meddelanden
+permission_edit_messages: Ändra meddelanden
+permission_edit_own_messages: Ändra egna meddelanden
+permission_delete_messages: Ta bort meddelanden
+permission_delete_own_messages: Ta bort egna meddelanden
+project_module_issue_tracking: Ärendeuppföljning
+project_module_time_tracking: Tidsuppföljning
+project_module_news: Nyheter
+project_module_documents: Dokument
+project_module_files: Filer
+project_module_wiki: Wiki
+project_module_repository: Repository
+project_module_boards: Forum
label_user: Användare
label_user_plural: Användare
@@ -193,36 +292,40 @@ label_user_new: Ny användare
label_project: Projekt
label_project_new: Nytt projekt
label_project_plural: Projekt
-label_project_all: All Projects
+label_project_all: Alla projekt
label_project_latest: Senaste projekt
-label_issue: Brist
-label_issue_new: Ny brist
-label_issue_plural: Brister
-label_issue_view_all: Visa alla brister
+label_issue: Ärende
+label_issue_new: Nytt ärende
+label_issue_plural: Ärenden
+label_issue_view_all: Visa alla ärenden
+label_issues_by: Ärenden %s
+label_issue_added: Ärende tillagt
+label_issue_updated: Ärende uppdaterat
label_document: Dokument
label_document_new: Nytt dokument
label_document_plural: Dokument
+label_document_added: Dokument tillagt
label_role: Roll
label_role_plural: Roller
label_role_new: Ny roll
-label_role_and_permissions: Roller och rättigheter
+label_role_and_permissions: Roller och behörigheter
label_member: Medlem
label_member_new: Ny medlem
label_member_plural: Medlemmar
-label_tracker: Tracker
-label_tracker_plural: Trackers
-label_tracker_new: Ny tracker
-label_workflow: Workflow
-label_issue_status: Briststatus
-label_issue_status_plural: Briststatusar
+label_tracker: Ärendetyp
+label_tracker_plural: Ärendetyper
+label_tracker_new: Ny ärendetyp
+label_workflow: Arbetsflöde
+label_issue_status: Ärendestatus
+label_issue_status_plural: Ärendestatusar
label_issue_status_new: Ny status
-label_issue_category: Bristkategori
-label_issue_category_plural: Bristkategorier
+label_issue_category: Ärendekategori
+label_issue_category_plural: Ärendekategorier
label_issue_category_new: Ny kategori
label_custom_field: Användardefinerat fält
label_custom_field_plural: Användardefinerade fält
-label_custom_field_new: Nytt Användardefinerat fält
-label_enumerations: Uppräkningar
+label_custom_field_new: Nytt användardefinerat fält
+label_enumerations: Enumerationer
label_enumeration_new: Nytt värde
label_information: Information
label_information_plural: Information
@@ -237,28 +340,32 @@ label_administration: Administration
label_login: Logga in
label_logout: Logga ut
label_help: Hjälp
-label_reported_issues: Rapporterade brister
-label_assigned_to_me_issues: Brister tilldelade mig
+label_reported_issues: Rapporterade ärenden
+label_assigned_to_me_issues: Ärenden tilldelade till mig
label_last_login: Senaste inloggning
label_last_updates: Senast uppdaterad
label_last_updates_plural: %d senaste uppdateringarna
label_registered_on: Registrerad
label_activity: Aktivitet
+label_overall_activity: All aktivitet
+label_user_activity: "Aktiviteter för %s"
label_new: Ny
-label_logged_as: Loggad som
+label_logged_as: Inloggad som
label_environment: Miljö
-label_authentication: Authentikering
-label_auth_source: Authentikeringsläge
-label_auth_source_new: Nytt authentikeringsläge
-label_auth_source_plural: Authentikeringslägen
-label_subproject_plural: Delprojekt
-label_min_max_length: Min - Max längd
+label_authentication: Autentisering
+label_auth_source: Autentiseringsläge
+label_auth_source_new: Nytt autentiseringsläge
+label_auth_source_plural: Autentiseringslägen
+label_subproject_plural: Underprojekt
+label_and_its_subprojects: %s och dess underprojekt
+label_min_max_length: Min./Max.-längd
label_list: Lista
label_date: Datum
label_integer: Heltal
+label_float: Flyttal
label_boolean: Boolean
label_string: Text
-label_text: Long text
+label_text: LÃ¥ng text
label_attribute: Attribut
label_attribute_plural: Attribut
label_download: %d Nerladdning
@@ -270,13 +377,15 @@ label_attachment: Fil
label_attachment_new: Ny fil
label_attachment_delete: Ta bort fil
label_attachment_plural: Filer
+label_file_added: Fil tillagd
label_report: Rapport
label_report_plural: Rapporter
label_news: Nyhet
label_news_new: Lägg till nyhet
label_news_plural: Nyheter
-label_news_latest: Senaste neheten
+label_news_latest: Senaste nyheterna
label_news_view_all: Visa alla nyheter
+label_news_added: Nyhet tillagd
label_change_log: Ändringslogg
label_settings: Inställningar
label_overview: Överblick
@@ -286,17 +395,18 @@ label_version_plural: Versioner
label_confirmation: Bekräftelse
label_export_to: Exportera till
label_read: Läs...
-label_public_projects: Offentligt projekt
+label_public_projects: Publika projekt
label_open_issues: öppen
label_open_issues_plural: öppna
label_closed_issues: stängd
label_closed_issues_plural: stängda
label_total: Total
-label_permissions: Rättigheter
+label_permissions: Behörigheter
label_current_status: Nuvarande status
label_new_statuses_allowed: Nya statusar tillåtna
label_all: alla
-label_none: inga
+label_none: ingen
+label_nobody: ingen
label_next: Nästa
label_previous: Föregående
label_used_by: Använd av
@@ -322,124 +432,175 @@ label_filter_add: Lägg till filter
label_filter_plural: Filter
label_equals: är
label_not_equals: är inte
-label_in_less_than: i mindre än
-label_in_more_than: i mer än
-label_in: i
+label_in_less_than: om mindre än
+label_in_more_than: om mer än
+label_in: om
label_today: idag
-label_this_week: this week
+label_all_time: närsom
+label_yesterday: igår
+label_this_week: denna vecka
+label_last_week: senaste veckan
+label_last_n_days: senaste %d dagarna
+label_this_month: denna månad
+label_last_month: senaste månaden
+label_this_year: detta året
+label_date_range: Datumintervall
label_less_than_ago: mindre än dagar sedan
label_more_than_ago: mer än dagar sedan
label_ago: dagar sedan
label_contains: innehåller
label_not_contains: innehåller inte
label_day_plural: dagar
-label_repository: Repositorie
+label_repository: Repository
+label_repository_plural: Repositorys
label_browse: Bläddra
label_modification: %d ändring
label_modification_plural: %d ändringar
label_revision: Revision
label_revision_plural: Revisioner
+label_associated_revisions: Associerade revisioner
label_added: tillagd
label_modified: modifierad
+label_copied: kopierad
+label_renamed: omdöpt
label_deleted: borttagen
label_latest_revision: Senaste revisionen
label_latest_revision_plural: Senaste revisionerna
label_view_revisions: Visa revisioner
-label_max_size: Maximumstorlek
+label_max_size: Maxstorlek
label_on: 'på'
-label_sort_highest: Flytta till top
-label_sort_higher: Flytta up
+label_sort_highest: Flytta till toppen
+label_sort_higher: Flytta upp
label_sort_lower: Flytta ner
label_sort_lowest: Flytta till botten
label_roadmap: Roadmap
label_roadmap_due_in: Färdig om %s
-label_roadmap_overdue: %s late
-label_roadmap_no_issues: Inga brister för denna version
+label_roadmap_overdue: %s sen
+label_roadmap_no_issues: Inga ärenden för denna version
label_search: Sök
label_result_plural: Resultat
label_all_words: Alla ord
label_wiki: Wiki
-label_wiki_edit: Wiki editera
-label_wiki_edit_plural: Wiki editeringar
-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_wiki_edit: Wikiändring
+label_wiki_edit_plural: Wikiändringar
+label_wiki_page: Wikisida
+label_wiki_page_plural: Wikisidor
+label_index_by_title: Innehåll efter titel
+label_index_by_date: Innehåll efter datum
label_current_version: Nuvarande version
-label_preview: Preview
-label_feed_plural: Feeder
+label_preview: Förhandsgranska
+label_feed_plural: Feeds
label_changes_details: Detaljer om alla ändringar
-label_issue_tracking: Bristspårning
+label_issue_tracking: Ärendeuppföljning
label_spent_time: Spenderad tid
-label_f_hour: %.2f timmar
+label_f_hour: %.2f timme
label_f_hour_plural: %.2f timmar
-label_time_tracking: Tidsspårning
+label_time_tracking: Tidsuppföljning
label_change_plural: Ändringar
label_statistics: Statistik
-label_commits_per_month: Commit per månad
-label_commits_per_author: Commit per författare
+label_commits_per_month: Commits per månad
+label_commits_per_author: Commits per författare
label_view_diff: Visa skillnader
-label_diff_inline: inline
+label_diff_inline: i texten
label_diff_side_by_side: sida vid sida
label_options: Inställningar
-label_copy_workflow_from: Kopiera workflow från
-label_permissions_report: Rättighetsrapport
-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_copy_workflow_from: Kopiera arbetsflöde från
+label_permissions_report: Behörighetsrapport
+label_watched_issues: Bevakade ärenden
+label_related_issues: Relaterade ärenden
+label_applied_status: Tilldelad status
+label_loading: Laddar...
+label_relation_new: Ny relation
+label_relation_delete: Ta bort relation
+label_relates_to: relaterar till
+label_duplicates: kopierar
+label_duplicated_by: kopierad av
+label_blocks: blockerar
+label_blocked_by: blockerad av
+label_precedes: kommer före
+label_follows: följer
+label_end_to_start: slut till start
+label_end_to_end: slut till slut
+label_start_to_start: start till start
+label_start_to_end: start till slut
+label_stay_logged_in: Förbli inloggad
+label_disabled: inaktiverad
+label_show_completed_versions: Visa färdiga versioner
+label_me: mig
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: Nytt forum
+label_board_plural: Forum
+label_topic_plural: Ämnen
+label_message_plural: Meddelanden
+label_message_last: Senaste meddelande
+label_message_new: Nytt meddelande
+label_message_posted: Meddelande tillagt
+label_reply_plural: Svar
+label_send_information: Skicka kontoinformation till användaren
+label_year: Ã…r
+label_month: MÃ¥nad
+label_week: Vecka
+label_date_from: Från
+label_date_to: Till
+label_language_based: Språkbaserad
+label_sort_by: Sortera på %s
+label_send_test_email: Skicka testmail
+label_feeds_access_key_created_on: RSS-nyckel skapad för %s sedan
+label_module_plural: Moduler
+label_added_time_by: Tillagd av %s för %s sedan
+label_updated_time_by: Uppdaterad av %s för %s sedan
+label_updated_time: Uppdaterad för %s sedan
+label_jump_to_a_project: GÃ¥ till projekt...
+label_file_plural: Filer
+label_changeset_plural: Changesets
+label_default_columns: Standardkolumner
+label_no_change_option: (Ingen ändring)
+label_bulk_edit_selected_issues: Gemensam ändring av markerade ärenden
+label_theme: Tema
+label_default: Standard
+label_search_titles_only: Sök endast i titlar
+label_user_mail_option_all: "För alla händelser i mina projekt"
+label_user_mail_option_selected: "För alla händelser i markerade projekt..."
+label_user_mail_option_none: "Endast för saker jag bevakar eller är involverad i"
+label_user_mail_no_self_notified: "Jag vill inte bli notifierad om ändringar som jag har gjort"
+label_registration_activation_by_email: kontoaktivering med mail
+label_registration_manual_activation: manuell kontoaktivering
+label_registration_automatic_activation: automatisk kontoaktivering
+label_display_per_page: 'Per sida: %s'
+label_age: Ã…lder
+label_change_properties: Ändra inställningar
+label_general: Allmänt
+label_more: Mer
+label_scm: SCM
+label_plugins: Tillägg
+label_ldap_authentication: LDAP-autentisering
+label_downloads_abbr: Nerl.
+label_optional_description: Valfri beskrivning
+label_add_another_file: Lägg till ytterligare en fil
+label_preferences: Användarinställningar
+label_chronological_order: I kronologisk ordning
+label_reverse_chronological_order: I omvänd kronologisk ordning
+label_planning: Planering
+label_incoming_emails: Inkommande mail
+label_generate_key: Generera en nyckel
+label_issue_watchers: Bevakare
+label_example: Exempel
+label_display: Visa
button_login: Logga in
-button_submit: Skicka
+button_submit: Spara
button_save: Spara
button_check_all: Markera alla
button_uncheck_all: Avmarkera alla
button_delete: Ta bort
button_create: Skapa
+button_create_and_continue: Skapa och fortsätt
button_test: Testa
-button_edit: Editera
+button_edit: Ändra
button_add: Lägg till
button_change: Ändra
-button_apply: Värkställ
-button_clear: Rensa
+button_apply: Verkställ
+button_clear: Återställ
button_lock: LÃ¥s
button_unlock: LÃ¥s upp
button_download: Ladda ner
@@ -451,50 +612,79 @@ button_cancel: Avbryt
button_activate: Aktivera
button_sort: Sortera
button_log_time: Logga tid
-button_rollback: Rulla tillbaka till denna version
-button_watch: Watch
-button_unwatch: Unwatch
-button_reply: Reply
-button_archive: Archive
-button_unarchive: Unarchive
-button_reset: Reset
-button_rename: Rename
+button_rollback: Återställ till denna version
+button_watch: Bevaka
+button_unwatch: Stoppa bevakning
+button_reply: Svara
+button_archive: Arkivera
+button_unarchive: Ta bort från arkiv
+button_reset: Återställ
+button_rename: Byt namn
+button_change_password: Ändra lösenord
+button_copy: Kopiera
+button_annotate: Kommentera
+button_update: Uppdatera
+button_configure: Konfigurera
+button_quote: Citera
-status_active: activ
+status_active: aktiv
status_registered: registrerad
status_locked: låst
-text_select_mail_notifications: Väl action för vilka email ska skickas.
+text_select_mail_notifications: Välj för vilka händelser mail ska skickas.
text_regexp_info: eg. ^[A-Z0-9]+$
text_min_max_length_info: 0 betyder ingen gräns
text_project_destroy_confirmation: Är du säker på att du vill ta bort detta projekt och all relaterad data?
-text_workflow_edit: Väl en roll och en tracker för att editera workflow.
-text_are_you_sure: Är du säker?
+text_subprojects_destroy_warning: 'Alla underprojekt: %s kommer också tas bort.'
+text_workflow_edit: Välj en roll och en ärendetyp för att ändra arbetsflöde
+text_are_you_sure: Är du säker ?
text_journal_changed: ändrad från %s till %s
text_journal_set_to: satt till %s
text_journal_deleted: borttagen
-text_tip_task_begin_day: arbetsuppgift börjar denna dag
-text_tip_task_end_day: arbetsuppgift slutar denna dag
+text_tip_task_begin_day: arbetsuppgift som börjar denna dag
+text_tip_task_end_day: arbetsuppgift som slutar denna dag
text_tip_task_begin_end_day: arbetsuppgift börjar och slutar denna dag
text_project_identifier_info: 'Små bokstäver (a-z), siffror och streck tillåtna.<br />När den är sparad kan identifieraren inte ändras.'
-text_caracters_maximum: %d tecken maximum.
+text_caracters_maximum: max %d tecken.
+text_caracters_minimum: Måste vara minst %d tecken lång.
text_length_between: Längd mellan %d och %d tecken.
-text_tracker_no_workflow: Inget workflow definerat för denna tracker
-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: Brist %s har rapporterats (by %s).
-text_issue_updated: Brist %s har uppdaterats (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_tracker_no_workflow: Inget arbetsflöde definerat för denna ärendetyp
+text_unallowed_characters: Otillåtna tecken
+text_comma_separated: Flera värden tillåtna (kommaseparerade).
+text_issues_ref_in_commit_messages: Referera och fixa ärenden i commit-meddelanden
+text_issue_added: Ärende %s har rapporterats (av %s).
+text_issue_updated: Ärende %s har uppdaterats (av %s).
+text_wiki_destroy_confirmation: Är du säker på att du vill ta bort denna wiki och allt dess innehåll ?
+text_issue_category_destroy_question: Några ärenden (%d) är tilldelade till denna kategori. Vad vill du göra ?
+text_issue_category_destroy_assignments: Ta bort kategoritilldelningar
+text_issue_category_reassign_to: Återtilldela ärenden till denna kategori
+text_user_mail_option: "För omarkerade projekt kommer du bara få notifieringar om saker du bevakar eller är inblandad i (T.ex. ärenden du skapat eller tilldelats)."
+text_no_configuration_data: "Roller, ärendetyper, ärendestatusar och arbetsflöden har inte konfigurerats ännu.\nDet rekommenderas att läsa in standardkonfigurationen. Du kommer att kunna göra ändringar efter att den blivit inläst."
+text_load_default_configuration: Läs in standardkonfiguration
+text_status_changed_by_changeset: Tilldelad i changeset %s.
+text_issues_destroy_confirmation: 'Är du säker på att du vill radera markerade ärende(n) ?'
+text_select_project_modules: 'Välj vilka moduler som ska vara aktiva för projektet:'
+text_default_administrator_account_changed: Standardadministratörens konto ändrat
+text_file_repository_writable: Foldern för bifogade filer är skrivbar
+text_plugin_assets_writable: Foldern för plug-ins är skrivbar
+text_rmagick_available: RMagick tillgängligt (valfritt)
+text_destroy_time_entries_question: %.02f timmar har rapporterats på ärendena du är på väg att ta bort. Vad vill du göra ?
+text_destroy_time_entries: Ta bort rapporterade timmar
+text_assign_time_entries_to_project: Tilldela rapporterade timmar till projektet
+text_reassign_time_entries: 'Återtilldela rapporterade timmar till detta ärende:'
+text_user_wrote: '%s skrev:'
+text_enumeration_destroy_question: '%d objekt är tilldelade till detta värde.'
+text_enumeration_category_reassign_to: 'Återtilldela till detta värde:'
+text_email_delivery_not_configured: "Mailfunktionen har inte konfigurerats, och notifieringar är inaktiverade.\nKonfigurera din SMTP-server i config/email.yml och starta om applikationen för att aktivera dem."
+text_repository_usernames_mapping: "Välj eller uppdatera den Redmine-användare som är mappad till varje användarnamn i repository-loggen.\nAnvändare med samma användarnamn eller emailadress i både Redmine och repositoryt mappas automatiskt."
+text_diff_truncated: '... Denna diff har förminskats eftersom den överskrider den maximala storlek som kan visas.'
+text_custom_field_possible_values_info: 'En rad för varje värde'
-default_role_manager: Förvaltare
+default_role_manager: Projektledare
default_role_developper: Utvecklare
-default_role_reporter: Rapporterare
+default_role_reporter: Rapportör
default_tracker_bug: Bugg
-default_tracker_feature: Finess
+default_tracker_feature: Funktionalitet
default_tracker_support: Support
default_issue_status_new: Ny
default_issue_status_assigned: Tilldelad
@@ -507,139 +697,15 @@ default_doc_category_tech: Teknisk dokumentation
default_priority_low: LÃ¥g
default_priority_normal: Normal
default_priority_high: Hög
-default_priority_urgent: Bråttom
+default_priority_urgent: Brådskande
default_priority_immediate: Omedelbar
default_activity_design: Design
default_activity_development: Utveckling
-enumeration_issue_priorities: Bristprioriteringar
+enumeration_issue_priorities: Ärendeprioriteter
enumeration_doc_categories: Dokumentkategorier
-enumeration_activities: Aktiviteter (tidsspårning)
-field_comments: Comment
-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
-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) ?'
-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
-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
-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_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
-setting_sequential_project_identifiers: Generate sequential project identifiers
-notice_unable_delete_version: Unable to delete version
-label_renamed: renamed
-label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+enumeration_activities: Aktiviteter (tidsuppföljning)
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/th.yml b/lang/th.yml
index ca2db8bcc..7cb03a1b8 100644
--- a/lang/th.yml
+++ b/lang/th.yml
@@ -643,5 +643,70 @@ setting_sequential_project_identifiers: Generate sequential project identifiers
notice_unable_delete_version: Unable to delete version
label_renamed: renamed
label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+setting_plain_text_mail: plain text only (no HTML)
+permission_view_files: View files
+permission_edit_issues: Edit issues
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_public_queries: Manage public queries
+permission_add_issues: Add issues
+permission_log_time: Log spent time
+permission_view_changesets: View changesets
+permission_view_time_entries: View spent time
+permission_manage_versions: Manage versions
+permission_manage_wiki: Manage wiki
+permission_manage_categories: Manage issue categories
+permission_protect_wiki_pages: Protect wiki pages
+permission_comment_news: Comment news
+permission_delete_messages: Delete messages
+permission_select_project_modules: Select project modules
+permission_manage_documents: Manage documents
+permission_edit_wiki_pages: Edit wiki pages
+permission_add_issue_watchers: Add watchers
+permission_view_gantt: View gantt chart
+permission_move_issues: Move issues
+permission_manage_issue_relations: Manage issue relations
+permission_delete_wiki_pages: Delete wiki pages
+permission_manage_boards: Manage boards
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_view_wiki_edits: View wiki history
+permission_add_messages: Post messages
+permission_view_messages: View messages
+permission_manage_files: Manage files
+permission_edit_issue_notes: Edit notes
+permission_manage_news: Manage news
+permission_view_calendar: View calendrier
+permission_manage_members: Manage members
+permission_edit_messages: Edit messages
+permission_delete_issues: Delete issues
+permission_view_issue_watchers: View watchers list
+permission_manage_repository: Manage repository
+permission_commit_access: Commit access
+permission_browse_repository: Browse repository
+permission_view_documents: View documents
+permission_edit_project: Edit project
+permission_add_issue_notes: Add notes
+permission_save_queries: Save queries
+permission_view_wiki_pages: View wiki
+permission_rename_wiki_pages: Rename wiki pages
+permission_edit_time_entries: Edit time logs
+permission_edit_own_issue_notes: Edit own notes
+setting_gravatar_enabled: Use Gravatar user icons
+label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+permission_edit_own_messages: Edit own messages
+permission_delete_own_messages: Delete own messages
+label_user_activity: "%s's activity"
+label_updated_time_by: Updated by %s %s ago
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/tr.yml b/lang/tr.yml
index 56bf3311a..8766b1127 100644
--- a/lang/tr.yml
+++ b/lang/tr.yml
@@ -641,5 +641,70 @@ general_csv_decimal_separator: '.'
notice_unable_delete_version: Unable to delete version
label_renamed: renamed
label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+setting_plain_text_mail: plain text only (no HTML)
+permission_view_files: View files
+permission_edit_issues: Edit issues
+permission_edit_own_time_entries: Edit own time logs
+permission_manage_public_queries: Manage public queries
+permission_add_issues: Add issues
+permission_log_time: Log spent time
+permission_view_changesets: View changesets
+permission_view_time_entries: View spent time
+permission_manage_versions: Manage versions
+permission_manage_wiki: Manage wiki
+permission_manage_categories: Manage issue categories
+permission_protect_wiki_pages: Protect wiki pages
+permission_comment_news: Comment news
+permission_delete_messages: Delete messages
+permission_select_project_modules: Select project modules
+permission_manage_documents: Manage documents
+permission_edit_wiki_pages: Edit wiki pages
+permission_add_issue_watchers: Add watchers
+permission_view_gantt: View gantt chart
+permission_move_issues: Move issues
+permission_manage_issue_relations: Manage issue relations
+permission_delete_wiki_pages: Delete wiki pages
+permission_manage_boards: Manage boards
+permission_delete_wiki_pages_attachments: Delete attachments
+permission_view_wiki_edits: View wiki history
+permission_add_messages: Post messages
+permission_view_messages: View messages
+permission_manage_files: Manage files
+permission_edit_issue_notes: Edit notes
+permission_manage_news: Manage news
+permission_view_calendar: View calendrier
+permission_manage_members: Manage members
+permission_edit_messages: Edit messages
+permission_delete_issues: Delete issues
+permission_view_issue_watchers: View watchers list
+permission_manage_repository: Manage repository
+permission_commit_access: Commit access
+permission_browse_repository: Browse repository
+permission_view_documents: View documents
+permission_edit_project: Edit project
+permission_add_issue_notes: Add notes
+permission_save_queries: Save queries
+permission_view_wiki_pages: View wiki
+permission_rename_wiki_pages: Rename wiki pages
+permission_edit_time_entries: Edit time logs
+permission_edit_own_issue_notes: Edit own notes
+setting_gravatar_enabled: Use Gravatar user icons
+label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+permission_edit_own_messages: Edit own messages
+permission_delete_own_messages: Delete own messages
+label_user_activity: "%s's activity"
+label_updated_time_by: Updated by %s %s ago
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/uk.yml b/lang/uk.yml
index dd3954a90..235bbadb8 100644
--- a/lang/uk.yml
+++ b/lang/uk.yml
@@ -123,7 +123,7 @@ field_subject: Тема
field_due_date: Дата виконаннÑ
field_assigned_to: Призначена до
field_priority: Пріоритет
-field_fixed_version: Target version
+field_fixed_version: ВерÑÑ–Ñ
field_user: КориÑтувач
field_role: Роль
field_homepage: Ð”Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñторінка
@@ -156,7 +156,7 @@ field_done_ratio: %% зроблено
field_auth_source: Режим аутентифікації
field_hide_mail: Приховувати мій email
field_comments: Коментар
-field_url: URL
+field_url: ПоÑиланнÑ
field_start_page: Стартова Ñторінка
field_subproject: Підпроект
field_hours: Годин(и/а)
@@ -183,7 +183,7 @@ setting_attachment_max_size: МакÑимальний размір вкладеÐ
setting_issues_export_limit: ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð¿Ð¾ задачах, що екÑпортуютьÑÑ
setting_mail_from: email адреÑа Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ñ– інформації
setting_bcc_recipients: Отримувачі Ñліпої копії (bcc)
-setting_host_name: Им'Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð¸
+setting_host_name: Ім'Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð¸
setting_text_formatting: Ð¤Ð¾Ñ€Ð¼Ð°Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚ÐµÐºÑту
setting_wiki_compression: СтиÑÐ½ÐµÐ½Ð½Ñ Ñ–Ñторії Wiki
setting_feeds_limit: ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ñту подачі
@@ -209,7 +209,7 @@ label_project_plural: Проекти
label_project_all: УÑÑ– проекти
label_project_latest: ОÑтанні проекти
label_issue: ПитаннÑ
-label_issue_new: Ðові питаннÑ
+label_issue_new: Задати питаннÑ
label_issue_plural: ПитаннÑ
label_issue_view_all: ПроглÑнути вÑÑ– питаннÑ
label_issues_by: ÐŸÐ¸Ñ‚Ð°Ð½Ð½Ñ Ð·Ð° %s
@@ -511,7 +511,7 @@ 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), допуÑтимі цифри Ñ– дефіÑ.<br />Збережений ідентифікатор не може бути змінений.'
+text_project_identifier_info: 'РÑдкові букви (a-z), допуÑтимі цифри Ñ– дефіÑ.<br>Збережений ідентифікатор не може бути змінений.'
text_caracters_maximum: %d Ñимволів(а) макÑимум.
text_caracters_minimum: Повинно мати Ñкнайменше %d Ñимволів(а) у довжину.
text_length_between: Довжина між %d Ñ– %d Ñимволів.
@@ -552,95 +552,160 @@ default_activity_development: Розробка
enumeration_issue_priorities: Пріоритети питань
enumeration_doc_categories: Категорії документів
enumeration_activities: Дії (облік чаÑу)
-text_status_changed_by_changeset: Applied in changeset %s.
-label_display_per_page: 'Per page: %s'
-label_issue_added: Issue added
-label_issue_updated: Issue updated
-setting_per_page_options: Objects per page options
-notice_default_data_loaded: Default configuration successfully loaded.
-error_scm_not_found: "Entry and/or revision doesn't exist in the repository."
-label_associated_revisions: Associated revisions
-label_document_added: Document added
-label_message_posted: Message added
-text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
-error_scm_command_failed: "An error occurred when trying to access the repository: %s"
-setting_user_format: Users display format
-label_age: Age
-label_file_added: File added
-label_more: More
-field_default_value: Default value
-default_tracker_bug: Bug
-label_scm: SCM
-label_general: General
-button_update: Update
-text_select_project_modules: 'Select modules to enable for this project:'
-label_change_properties: Change properties
-text_load_default_configuration: Load the default configuration
+text_status_changed_by_changeset: Реалізовано в редакції %s.
+label_display_per_page: 'Ðа Ñторінку : %s'
+label_issue_added: ÐŸÐ¸Ñ‚Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ð½Ð¾
+label_issue_updated: ÐŸÐ¸Ñ‚Ð°Ð½Ð½Ñ Ð¿Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð¾
+setting_per_page_options: КількіÑть обєктів на Ñторінці
+notice_default_data_loaded: ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ð¿Ð¾ замовчуванню уÑпішно завантажена.
+error_scm_not_found: Репозиторій не міÑтить даних чи змін
+label_associated_revisions: ЗвÑзані редакції
+label_document_added: Документ додано
+label_message_posted: ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð´Ð¾Ð´Ð°Ð½Ð¾
+text_issues_destroy_confirmation: Ви впевнені що хочете видалити обрані задачі ?'
+error_scm_command_failed: "Помилка доÑтупу до репозиторію : %s"
+setting_user_format: Формат Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ–Ð¼ÐµÐ½Ñ–
+label_age: Вік
+label_file_added: Файл додано
+label_more: Більше/Далі
+field_default_value: Ð—Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾ замовчуванню
+default_tracker_bug: Помилка
+label_scm: Тип репозиторію
+label_general: Загальне
+button_update: Додати коментар
+text_select_project_modules: Виберіть модулі,Ñкі будуть викориÑтовуватиÑÑŒ у цьому проекті
+label_change_properties: Змінити влаÑтивоÑті
+text_load_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."
-label_news_added: News added
-label_repository_plural: Repositories
-error_can_t_load_default_data: "Default configuration could not be loaded: %s"
-project_module_boards: Boards
-project_module_issue_tracking: Issue tracking
+label_news_added: Додано новину
+label_repository_plural: Репозиторії
+error_can_t_load_default_data: "ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ð¿Ð¾ замовчуванню %s"
+project_module_boards: Форуми
+project_module_issue_tracking: ЗавданнÑ
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
+project_module_files: Файли
+project_module_documents: Документи
+project_module_repository: Репозиторії
+project_module_news: Ðовини
+project_module_time_tracking: Затрати чаÑу
+text_file_repository_writable: Репозиторій з доÑтупом запиÑу
text_default_administrator_account_changed: Default administrator account changed
text_rmagick_available: RMagick available (optional)
-button_configure: Configure
-label_plugins: Plugins
+button_configure: КонфігураціÑ
+label_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_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
-setting_sequential_project_identifiers: Generate sequential project identifiers
-notice_unable_delete_version: Unable to delete version
-label_renamed: renamed
-label_copied: copied
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+label_this_month: поточний міÑÑць
+label_last_n_days: оÑтанніх %d днів
+label_all_time: веÑÑŒ чаÑ
+label_this_year: поточний рік
+label_date_range: Діапазон дат
+label_last_week: ОÑтанній тиждень
+label_yesterday: Вчора
+label_last_month: оÑтанній міÑÑць
+label_add_another_file: Додати інший файл
+label_optional_description: Додатково
+text_destroy_time_entries_question: Видалити %.02f години з поточного Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ ?
+error_issue_not_found_in_project: 'Задача не знайдена чи не прикріплена до цього проекту'
+text_assign_time_entries_to_project: Прикріпити зареєÑтровані години до проекту
+text_destroy_time_entries: Видалити зареєÑтровані години
+text_reassign_time_entries: 'ПеренеÑти зареєÑтровані години на Ñлідуєче завданнÑ:'
+setting_activity_days_default: КількіÑть днів,Ñкі відображаютьÑÑ Ð² активноÑті
+label_chronological_order: Ð’ хронологічному порÑдку
+field_comments_sorting: Відобразити коментарі
+label_reverse_chronological_order: Ð’ зворотному порÑдку
+label_preferences: ВлаÑтивоÑті
+setting_display_subprojects_issues: Відображенні підпроектів по замовчуванню
+label_overall_activity: Загальна активніÑть
+setting_default_projects_public: Ðовий проект публічний за замовчуваннÑм
+error_scm_annotate: Дані відÑутні або не можуть бути підпиÑані
+label_planning: ПлануваннÑ
+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: "ДоÑтавка пошти не налаштована, функції повідомлень на пошту не активні.\nÐалаштуйте ваш SMTP Ñервер в файлі config/email.yml та перезапуÑтіть ÑиÑтему щоб дозволити ці функції."
+field_parent_title: ВищеÑтоÑча Ñторінка
+label_issue_watchers: ÐаглÑдачі
+setting_commit_logs_encoding: ÐšÐ¾Ð´ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ñ–Ð² в репозиторії
+button_quote: Цитувати
+setting_sequential_project_identifiers: Генерувати поÑлідовні ідентифікатори проектів
+notice_unable_delete_version: Ðеможливо видалити верÑÑ–ÑŽ
+label_renamed: перейменовано
+label_copied: копійовано
+setting_plain_text_mail: лише проÑтий текÑÑ‚ (без HTML)
+permission_view_files: ДивитиÑÑŒ файли
+permission_edit_issues: Редагувати завданнÑ
+permission_edit_own_time_entries: Редагувати влаÑний облік чаÑу
+permission_manage_public_queries: Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð·Ð°Ð³Ð°Ð»ÑŒÐ½Ð¸Ð¼Ð¸ запитами
+permission_add_issues: Додати завданнÑ
+permission_log_time: Облік затраченого чаÑу
+permission_view_changesets: ДивитиÑÑŒ зміни репозиторію
+permission_view_time_entries: ДивитиÑÑŒ затраченого чаÑу
+permission_manage_versions: Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð²ÐµÑ€ÑÑ–Ñми
+permission_manage_wiki: Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ wiki
+permission_manage_categories: Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ñ–Ñми завдань
+permission_protect_wiki_pages: Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ñторінок wiki
+permission_comment_news: Коментувати новини
+permission_delete_messages: ВидалÑти повідомленнÑ
+permission_select_project_modules: Вибір модулів Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ñƒ
+permission_manage_documents: Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ð¼Ð¸
+permission_edit_wiki_pages: Редагувати Ñторінки wiki
+permission_add_issue_watchers: Додавати наглÑдачів
+permission_view_gantt: ДивитиÑÑŒ діаграму Ганта
+permission_move_issues: Переміщати завданнÑ
+permission_manage_issue_relations: Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð·Ð²ÑзуваннÑм завдань
+permission_delete_wiki_pages: Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñторінок wiki
+permission_manage_boards: Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ñ„Ð¾Ñ€ÑƒÐ¼Ð¾Ð¼
+permission_delete_wiki_pages_attachments: Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ð½ÑŒ
+permission_view_wiki_edits: ДивитиÑÑŒ Ñ–Ñторію wiki
+permission_add_messages: Відправка повідомлень
+permission_view_messages: ДивитиÑÑŒ повідомленнÑ
+permission_manage_files: Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸
+permission_edit_issue_notes: Редагувати замітки
+permission_manage_news: Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð½Ð¾Ð²Ð¸Ð½Ð°Ð¼Ð¸
+permission_view_calendar: ДивитиÑÑŒ календар
+permission_manage_members: Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувачами
+permission_edit_messages: Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ
+permission_delete_issues: Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ð²Ð´Ð°Ð½ÑŒ
+permission_view_issue_watchers: ДивитиÑÑŒ ÑпиÑок наглÑдачів
+permission_manage_repository: Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ”Ð¼
+permission_commit_access: Дозволити фікÑацію змін
+permission_browse_repository: ДивитиÑÑŒ репозиторій
+permission_view_documents: ДивитиÑÑŒ документи
+permission_edit_project: Редагувати проекти
+permission_add_issue_notes: Додати замітку
+permission_save_queries: Зберігати запити
+permission_view_wiki_pages: ДивитиÑÑŒ wiki
+permission_rename_wiki_pages: ÐŸÐµÑ€ÐµÐ¹Ð¼ÐµÐ½ÑƒÐ²Ð°Ð½Ð½Ñ Ñторінок wiki
+permission_edit_time_entries: Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÑƒ чаÑу
+permission_edit_own_issue_notes: Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÑƒ чаÑу
+setting_gravatar_enabled: ВикориÑтовувати Gravatar аватари
+label_example: Приклад
+text_repository_usernames_mapping: "Виберіть чи поновіть кориÑтувача Redmine, звÑзаного з знайденими іменами в журналі репозиторію.\nКориÑтувачі з однаковими іменами чи email в Redmine Ñ– репозиторії звÑзуютьÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¾."
+permission_edit_own_messages: Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð»Ð°Ñних повідомлень
+permission_delete_own_messages: Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²Ð»Ð°Ñних повідомлень
+label_user_activity: "ÐктивніÑть кориÑтувача %s"
+label_updated_time_by: Поновлено %s %s тому
+text_diff_truncated: '... порівнÑÐ½Ð½Ñ Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð¾,так Ñк перевищує ліміт Ñтрічок Ð´Ð»Ñ Ð¿Ð¾Ñ€Ñ–Ð²Ð½ÑÐ½Ð½Ñ .'
+setting_diff_max_lines_displayed: МакÑимальна кількіÑть Ñтрічок Ð´Ð»Ñ Ð¿Ð¾Ñ€Ñ–Ð²Ð½ÑннÑ
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+warning_attachments_not_saved: "%d file(s) could not be saved."
+field_editable: Editable
+text_plugin_assets_writable: Plugin assets directory writable
+label_display: Display
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/vn.yml b/lang/vn.yml
new file mode 100644
index 000000000..e4f4fd47c
--- /dev/null
+++ b/lang/vn.yml
@@ -0,0 +1,712 @@
+_gloc_rule_default: '|n| n==1 ? "" : "_plural" '
+
+actionview_datehelper_select_day_prefix:
+actionview_datehelper_select_month_names: Tháng Giêng,Tháng Hai,Tháng Ba,Tháng Tư,Tháng Năm,Tháng Sáu,Tháng Bảy,Tháng Tám,Tháng Chín,Tháng Mưá»i,Tháng M.Má»™t,Tháng Chạp
+actionview_datehelper_select_month_names_abbr: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
+actionview_datehelper_select_month_prefix:
+actionview_datehelper_select_year_prefix:
+actionview_datehelper_time_in_words_day: 1 ngày
+actionview_datehelper_time_in_words_day_plural: %d ngày
+actionview_datehelper_time_in_words_hour_about: khoảng 1 giá»
+actionview_datehelper_time_in_words_hour_about_plural: khoảng %d giá»
+actionview_datehelper_time_in_words_hour_about_single: khoảng 1 giá»
+actionview_datehelper_time_in_words_minute: 1 phút
+actionview_datehelper_time_in_words_minute_half: nửa phút
+actionview_datehelper_time_in_words_minute_less_than: dưới một phút
+actionview_datehelper_time_in_words_minute_plural: %d phút
+actionview_datehelper_time_in_words_minute_single: 1 phút
+actionview_datehelper_time_in_words_second_less_than: cách vài giây
+actionview_datehelper_time_in_words_second_less_than_plural: cách %d giấy
+actionview_instancetag_blank_option: Vui lòng chá»n
+
+activerecord_error_inclusion: không chứa trong danh sách
+activerecord_error_exclusion: đã được dùng
+activerecord_error_invalid: không hợp lệ
+activerecord_error_confirmation: không khớp
+activerecord_error_accepted: phải được chấp nhận
+activerecord_error_empty: không thể để trống
+activerecord_error_blank: không thể để trống
+activerecord_error_too_long: quá dài
+activerecord_error_too_short: quá ngắn
+activerecord_error_wrong_length: độ dài không đúng
+activerecord_error_taken: đã được chá»n
+activerecord_error_not_a_number: không phải con số
+activerecord_error_not_a_date: không phải ngày hợp lệ
+activerecord_error_greater_than_start_date: phải đi sau ngày bắt đầu
+activerecord_error_not_same_project: không thuộc cùng dự án
+activerecord_error_circular_dependency: quan hệ có thể gây ra lặp vô tận
+
+general_fmt_age: %d năm
+general_fmt_age_plural: %d năm
+general_fmt_date: %%m/%%d/%%Y
+general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p
+general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
+general_fmt_time: %%I:%%M %%p
+general_text_No: 'Không'
+general_text_Yes: 'Có'
+general_text_no: 'không'
+general_text_yes: 'có'
+general_lang_name: 'Tiếng Việt'
+general_csv_separator: ','
+general_csv_decimal_separator: '.'
+general_csv_encoding: UTF-8
+general_pdf_encoding: UTF-8
+general_day_names: Hai,Ba,Tư,Năm,Sáu,Bảy,C.Nhật
+general_first_day_of_week: '1'
+
+notice_account_updated: Cập nhật tài khoản thành công.
+notice_account_invalid_creditentials: Tài khoản hoặc mật mã không hợp lệ
+notice_account_password_updated: Cập nhật mật mã thành công.
+notice_account_wrong_password: Sai mật mã
+notice_account_register_done: Tài khoản được tạo thành công. Äể kích hoạt vui lòng làm theo hướng dẫn trong email gá»­i đến bạn.
+notice_account_unknown_email: Không rõ tài khoản.
+notice_can_t_change_password: Tài khoản được chứng thực từ nguồn bên ngoài. Không thể đổi mật mã cho loại chứng thực này.
+notice_account_lost_email_sent: Thông tin để đổi mật mã mới đã gửi đến bạn qua email.
+notice_account_activated: Tài khoản vừa được kích hoạt. Bây giỠbạn có thể đăng nhập.
+notice_successful_create: Tạo thành công.
+notice_successful_update: Cập nhật thành công.
+notice_successful_delete: Xóa thành công.
+notice_successful_connection: Kết nối thành công.
+notice_file_not_found: Trang bạn cố xem không tồn tại hoặc đã chuyển.
+notice_locking_conflict: Thông tin Ä‘ang được cập nhật bởi ngưá»i khác. Hãy chép ná»™i dung cập nhật cá»§a bạn vào clipboard.
+notice_not_authorized: Bạn không có quyá»n xem trang này.
+notice_email_sent: Email đã được gửi tới %s
+notice_email_error: Lỗi xảy ra khi gửi email (%s)
+notice_feeds_access_key_reseted: Mã số chứng thực RSS đã được tạo lại.
+notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s."
+notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
+notice_account_pending: "Thông tin tài khoản đã được tạo ra và đang chỠchứng thực từ ban quản trị."
+notice_default_data_loaded: Äã nạp cấu hình mặc định.
+notice_unable_delete_version: Không thể xóa phiên bản.
+
+error_can_t_load_default_data: "Không thể nạp cấu hình mặc định: %s"
+error_scm_not_found: "The entry or revision was not found in the repository."
+error_scm_command_failed: "Lỗi xảy ra khi truy cập vào kho lưu trữ: %s"
+error_scm_annotate: "The entry does not exist or can not be annotated."
+error_issue_not_found_in_project: 'Vấn đỠkhông tồn tại hoặc không thuộc dự án'
+
+mail_subject_lost_password: "%s: mật mã của bạn"
+mail_body_lost_password: "Äể đổi mật mã, hãy click chuá»™t vào liên kết sau:"
+mail_subject_register: "%s: kích hoạt tài khoản"
+mail_body_register: "Äể kích hoạt tài khoản, hãy click chuá»™t vào liên kết sau:"
+mail_body_account_information_external: Bạn có thể dùng tài khoản "%s" để đăng nhập.
+mail_body_account_information: Thông tin vỠtài khoản
+mail_subject_account_activation_request: "%s: Yêu cầu chứng thực tài khoản"
+mail_body_account_activation_request: 'Ngưá»i dùng (%s) má»›i đăng ký và cần bạn xác nhận:'
+mail_subject_reminder: "%d vấn đỠhết hạn trong các ngày tới"
+mail_body_reminder: "%d vấn đỠgán cho bạn sẽ hết hạn trong %d ngày tới:"
+
+gui_validation_error: 1 lá»—i
+gui_validation_error_plural: %d lá»—i
+
+field_name: Tên
+field_description: Mô tả
+field_summary: Tóm tắt
+field_is_required: Bắt buộc
+field_firstname: Tên lót + Tên
+field_lastname: Há»
+field_mail: Email
+field_filename: Tập tin
+field_filesize: Cỡ
+field_downloads: Tải vá»
+field_author: Tác giả
+field_created_on: Tạo
+field_updated_on: Cập nhật
+field_field_format: Äịnh dạng
+field_is_for_all: Cho má»i dá»± án
+field_possible_values: Giá trị hợp lệ
+field_regexp: Biểu thức chính quy
+field_min_length: Chiá»u dài tối thiểu
+field_max_length: Chiá»u dài tối Ä‘a
+field_value: Giá trị
+field_category: Chá»§ Ä‘á»
+field_title: Tiêu Ä‘á»
+field_project: Dự án
+field_issue: Vấn Ä‘á»
+field_status: Trạng thái
+field_notes: Ghi chú
+field_is_closed: Vấn đỠđóng
+field_is_default: Giá trị mặc định
+field_tracker: Dòng vấn Ä‘á»
+field_subject: Chá»§ Ä‘á»
+field_due_date: Hết hạn
+field_assigned_to: Gán cho
+field_priority: Ưu tiên
+field_fixed_version: Phiên bản
+field_user: Ngưá»i dùng
+field_role: Quyá»n
+field_homepage: Trang chá»§
+field_is_public: Công cộng
+field_parent: Dự án con của
+field_is_in_chlog: Có thể thấy trong Thay đổi
+field_is_in_roadmap: Có thể thấy trong Kế hoạch
+field_login: Äăng nhập
+field_mail_notification: Thông báo qua email
+field_admin: Quản trị
+field_last_login_on: Kết nối cuối
+field_language: Ngôn ngữ
+field_effective_date: Ngày
+field_password: Mật mã
+field_new_password: Mật mã mới
+field_password_confirmation: Khẳng định lại
+field_version: Phiên bản
+field_type: Kiểu
+field_host: Host
+field_port: Port
+field_account: Tài khoản
+field_base_dn: Base DN
+field_attr_login: Login attribute
+field_attr_firstname: Firstname attribute
+field_attr_lastname: Lastname attribute
+field_attr_mail: Email attribute
+field_onthefly: On-the-fly user creation
+field_start_date: Bắt đầu
+field_done_ratio: Tiến độ
+field_auth_source: Authentication mode
+field_hide_mail: Không làm lộ email của bạn
+field_comments: Bình luận
+field_url: URL
+field_start_page: Trang bắt đầu
+field_subproject: Dự án con
+field_hours: Giá»
+field_activity: Hoạt động
+field_spent_on: Ngày
+field_identifier: Mã nhận dạng
+field_is_filter: Dùng như má»™t lá»c
+field_issue_to_id: Vấn Ä‘á»n liên quan
+field_delay: Äá»™ trá»…
+field_assignable: Vấn đỠcó thể gán cho vai trò này
+field_redirect_existing_links: Chuyển hướng trang đã có
+field_estimated_hours: Thá»i gian ước Ä‘oán
+field_column_names: Cá»™t
+field_time_zone: Múi giá»
+field_searchable: Tìm kiếm được
+field_default_value: Giá trị mặc định
+field_comments_sorting: Liệt kê bình luận
+field_parent_title: Trang mẹ
+
+setting_app_title: Tựa đỠứng dụng
+setting_app_subtitle: Tựa đỠnhỠcủa ứng dụng
+setting_welcome_text: Thông điệp chào mừng
+setting_default_language: Ngôn ngữ mặc định
+setting_login_required: Cần đăng nhập
+setting_self_registration: Tự chứng thực
+setting_attachment_max_size: Cỡ tối đa của tập tin đính kèm
+setting_issues_export_limit: Issues export limit
+setting_mail_from: Emission email address
+setting_bcc_recipients: Tạo bản CC bí mật (bcc)
+setting_host_name: Tên miá»n và đưá»ng dẫn
+setting_text_formatting: Äịnh dạng bài viết
+setting_wiki_compression: Wiki history compression
+setting_feeds_limit: Giới hạn nội dung của feed
+setting_default_projects_public: Dự án mặc định là công cộng
+setting_autofetch_changesets: Autofetch commits
+setting_sys_api_enabled: Enable WS for repository management
+setting_commit_ref_keywords: Từ khóa tham khảo
+setting_commit_fix_keywords: Từ khóa chỉ vấn đỠđã giải quyết
+setting_autologin: Tự động đăng nhập
+setting_date_format: Äịnh dạng ngày
+setting_time_format: Äịnh dạng giá»
+setting_cross_project_issue_relations: Cho phép quan hệ chéo giữa các dự án
+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: Chữ ký cuối thư
+setting_protocol: Giao thức
+setting_per_page_options: Objects per page options
+setting_user_format: Äịnh dạng hiển thị ngưá»i dùng
+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: Mã số API
+setting_sequential_project_identifiers: Tự sinh chuỗi ID dự án
+
+project_module_issue_tracking: Theo dõi vấn Ä‘á»
+project_module_time_tracking: Theo dõi thá»i gian
+project_module_news: Tin tức
+project_module_documents: Tài liệu
+project_module_files: Tập tin
+project_module_wiki: Wiki
+project_module_repository: Kho lưu trữ
+project_module_boards: Diễn đàn
+
+label_user: Tài khoản
+label_user_plural: Tài khoản
+label_user_new: Tài khoản mới
+label_project: Dự án
+label_project_new: Dự án mới
+label_project_plural: Dự án
+label_project_all: Má»i dá»± án
+label_project_latest: Dự án mới nhất
+label_issue: Vấn Ä‘á»
+label_issue_new: Tạo vấn đỠmới
+label_issue_plural: Vấn Ä‘á»
+label_issue_view_all: Tất cả vấn Ä‘á»
+label_issues_by: Vấn đỠcủa %s
+label_issue_added: Äã thêm vấn Ä‘á»
+label_issue_updated: Vấn đỠđược cập nhật
+label_document: Tài liệu
+label_document_new: Tài liệu mới
+label_document_plural: Tài liệu
+label_document_added: Äã thêm tài liệu
+label_role: Vai trò
+label_role_plural: Vai trò
+label_role_new: Vai trò mới
+label_role_and_permissions: Vai trò và Quyá»n hạn
+label_member: Thành viên
+label_member_new: Thành viên mới
+label_member_plural: Thành viên
+label_tracker: Dòng vấn Ä‘á»
+label_tracker_plural: Dòng vấn Ä‘á»
+label_tracker_new: Tạo dòng vấn đỠmới
+label_workflow: Workflow
+label_issue_status: Issue status
+label_issue_status_plural: Issue statuses
+label_issue_status_new: New status
+label_issue_category: Chá»§ Ä‘á»
+label_issue_category_plural: Chá»§ Ä‘á»
+label_issue_category_new: Chủ đỠmới
+label_custom_field: Custom field
+label_custom_field_plural: Custom fields
+label_custom_field_new: New custom field
+label_enumerations: Enumerations
+label_enumeration_new: New value
+label_information: Thông tin
+label_information_plural: Thông tin
+label_please_login: Vui lòng đăng nhập
+label_register: Äăng ký
+label_password_lost: Phục hồi mật mã
+label_home: Trang chính
+label_my_page: Trang riêng
+label_my_account: Cá nhân
+label_my_projects: Dự án của bạn
+label_administration: Quản trị
+label_login: Äăng nhập
+label_logout: Thoát
+label_help: Giúp đỡ
+label_reported_issues: Vấn đỠđã báo cáo
+label_assigned_to_me_issues: Vấn đỠgán cho bạn
+label_last_login: Kết nối cuối
+label_last_updates: Cập nhật cuối
+label_last_updates_plural: %d cập nhật cuối
+label_registered_on: Ngày tham gia
+label_activity: Hoạt động
+label_overall_activity: Tất cả hoạt động
+label_new: Má»›i
+label_logged_as: Tài khoản &raquo;
+label_environment: Environment
+label_authentication: Authentication
+label_auth_source: Authentication mode
+label_auth_source_new: New authentication mode
+label_auth_source_plural: Authentication modes
+label_subproject_plural: Dự án con
+label_and_its_subprojects: %s và dự án con
+label_min_max_length: Min - Max length
+label_list: List
+label_date: Ngày
+label_integer: Integer
+label_float: Float
+label_boolean: Boolean
+label_string: Text
+label_text: Long text
+label_attribute: Attribute
+label_attribute_plural: Attributes
+label_download: %d lần tải
+label_download_plural: %d lần tải
+label_no_data: Chưa có thông tin gì
+label_change_status: Äổi trạng thái
+label_history: Lược sử
+label_attachment: Tập tin
+label_attachment_new: Thêm tập tin mới
+label_attachment_delete: Xóa tập tin
+label_attachment_plural: Tập tin
+label_file_added: Äã thêm tập tin
+label_report: Báo cáo
+label_report_plural: Báo cáo
+label_news: Tin tức
+label_news_new: Thêm tin
+label_news_plural: Tin tức
+label_news_latest: Tin má»›i
+label_news_view_all: Xem má»i tin
+label_news_added: Äã thêm tin
+label_change_log: Nhật ký thay đổi
+label_settings: Thiết lập
+label_overview: Tóm tắt
+label_version: Phiên bản
+label_version_new: Phiên bản mới
+label_version_plural: Phiên bản
+label_confirmation: Khẳng định
+label_export_to: 'Äịnh dạng khác cá»§a trang này:'
+label_read: Read...
+label_public_projects: Các dự án công cộng
+label_open_issues: mở
+label_open_issues_plural: mở
+label_closed_issues: đóng
+label_closed_issues_plural: đóng
+label_total: Tổng cộng
+label_permissions: Quyá»n
+label_current_status: Trạng thái hiện tại
+label_new_statuses_allowed: Trạng thái mới được phép
+label_all: tất cả
+label_none: không
+label_nobody: Chẳng ai
+label_next: Sau
+label_previous: Trước
+label_used_by: Used by
+label_details: Chi tiết
+label_add_note: Thêm ghi chú
+label_per_page: Má»—i trang
+label_calendar: Lịch
+label_months_from: tháng từ
+label_gantt: Biểu đồ sự kiện
+label_internal: Ná»™i bá»™
+label_last_changes: %d thay đổi cuối
+label_change_view_all: Xem má»i thay đổi
+label_personalize_page: Äiá»u chỉnh trang này
+label_comment: Bình luận
+label_comment_plural: Bình luận
+label_comment_add: Thêm bình luận
+label_comment_added: Äã thêm bình luận
+label_comment_delete: Xóa bình luận
+label_query: Truy vấn riêng
+label_query_plural: Truy vấn riêng
+label_query_new: Truy vấn mới
+label_filter_add: Thêm lá»c
+label_filter_plural: Bá»™ lá»c
+label_equals: là
+label_not_equals: không là
+label_in_less_than: ít hơn
+label_in_more_than: nhiá»u hÆ¡n
+label_in: trong
+label_today: hôm nay
+label_all_time: má»i thá»i gian
+label_yesterday: hôm qua
+label_this_week: tuần này
+label_last_week: tuần trước
+label_last_n_days: %d ngày cuối
+label_this_month: tháng này
+label_last_month: tháng cuối
+label_this_year: năm này
+label_date_range: Thá»i gian
+label_less_than_ago: cách đây dưới
+label_more_than_ago: cách đây hơn
+label_ago: cách đây
+label_contains: chứa
+label_not_contains: không chứa
+label_day_plural: ngày
+label_repository: Kho lưu trữ
+label_repository_plural: Kho lưu trữ
+label_browse: Duyệt
+label_modification: %d thay đổi
+label_modification_plural: %d thay đổi
+label_revision: Bản Ä‘iá»u chỉnh
+label_revision_plural: Bản Ä‘iá»u chỉnh
+label_associated_revisions: Associated revisions
+label_added: thêm
+label_modified: đổi
+label_copied: chép
+label_renamed: đổi tên
+label_deleted: xóa
+label_latest_revision: Bản Ä‘iá»u chỉnh cuối cùng
+label_latest_revision_plural: Bản Ä‘iá»u chỉnh cuối cùng
+label_view_revisions: Xem các bản Ä‘iá»u chỉnh
+label_max_size: Dung lượng tối đa
+label_on: '/ tổng số'
+label_sort_highest: Lên trên cùng
+label_sort_higher: Dịch lên
+label_sort_lower: Dịch xuống
+label_sort_lowest: Xuống dưới cùng
+label_roadmap: Kế hoạch
+label_roadmap_due_in: Hết hạn trong %s
+label_roadmap_overdue: Trá»… %s
+label_roadmap_no_issues: Không có vấn đỠcho phiên bản này
+label_search: Tìm
+label_result_plural: Kết quả
+label_all_words: Má»i từ
+label_wiki: Wiki
+label_wiki_edit: Wiki edit
+label_wiki_edit_plural: Thay đổi wiki
+label_wiki_page: Trang wiki
+label_wiki_page_plural: Trang wiki
+label_index_by_title: Danh sách theo tên
+label_index_by_date: Danh sách theo ngày
+label_current_version: Bản hiện tại
+label_preview: Xem trước
+label_feed_plural: Feeds
+label_changes_details: Chi tiết cá»§a má»i thay đổi
+label_issue_tracking: Vấn Ä‘á»
+label_spent_time: Thá»i gian
+label_f_hour: %.2f giá»
+label_f_hour_plural: %.2f giá»
+label_time_tracking: Theo dõi thá»i gian
+label_change_plural: Thay đổi
+label_statistics: Thống kê
+label_commits_per_month: Commits per month
+label_commits_per_author: Commits per author
+label_view_diff: So sánh
+label_diff_inline: inline
+label_diff_side_by_side: side by side
+label_options: Tùy chá»n
+label_copy_workflow_from: Copy workflow from
+label_permissions_report: Thống kê các quyá»n
+label_watched_issues: Chủ đỠđang theo dõi
+label_related_issues: Liên quan
+label_applied_status: Trạng thái áp dụng
+label_loading: Äang xá»­ lý...
+label_relation_new: Quan hệ mới
+label_relation_delete: Xóa quan hệ
+label_relates_to: liên quan
+label_duplicates: trùng với
+label_duplicated_by: bị trùng bởi
+label_blocks: chặn
+label_blocked_by: chặn bởi
+label_precedes: đi trước
+label_follows: đi sau
+label_end_to_start: cuối tới đầu
+label_end_to_end: cuối tới cuối
+label_start_to_start: đầu tớ đầu
+label_start_to_end: đầu tới cuối
+label_stay_logged_in: Lưu thông tin đăng nhập
+label_disabled: bị vô hiệu
+label_show_completed_versions: Xem phiên bản đã xong
+label_me: tôi
+label_board: Diễn đàn
+label_board_new: Tạo diễn đàn mới
+label_board_plural: Diễn đàn
+label_topic_plural: Chá»§ Ä‘á»
+label_message_plural: Diễn đàn
+label_message_last: Bài cuối
+label_message_new: Tạo bài mới
+label_message_posted: Äã thêm bài viết
+label_reply_plural: Hồi âm
+label_send_information: Gá»­i thông tin đến ngưá»i dùng qua email
+label_year: Năm
+label_month: Tháng
+label_week: Tuần
+label_date_from: Từ
+label_date_to: Äến
+label_language_based: Theo ngôn ngữ ngưá»i dùng
+label_sort_by: Sắp xếp theo %s
+label_send_test_email: Send a test email
+label_feeds_access_key_created_on: "Mã chứng thực RSS được tạo ra cách đây %s"
+label_module_plural: Mô-đun
+label_added_time_by: thêm bởi %s cách đây %s
+label_updated_time: Cập nhật cách đây %s
+label_jump_to_a_project: Nhảy đến dự án...
+label_file_plural: Tập tin
+label_changeset_plural: Thay đổi
+label_default_columns: Cột mặc định
+label_no_change_option: (không đổi)
+label_bulk_edit_selected_issues: Sá»­a nhiá»u vấn Ä‘á»
+label_theme: Giao diện
+label_default: Mặc định
+label_search_titles_only: Chỉ tìm trong tá»±a Ä‘á»
+label_user_mail_option_all: "Má»i sá»± kiện trên má»i dá»± án cá»§a bạn"
+label_user_mail_option_selected: "Má»i sá»± kiện trên các dá»± án được chá»n..."
+label_user_mail_option_none: "Chỉ những vấn đỠbạn theo dõi hoặc được gán"
+label_user_mail_no_self_notified: "Äừng gá»­i email vá» các thay đổi do chính bạn thá»±c hiện"
+label_registration_activation_by_email: account activation by email
+label_registration_manual_activation: manual account activation
+label_registration_automatic_activation: automatic account activation
+label_display_per_page: 'má»—i trang: %s'
+label_age: Age
+label_change_properties: Thay đổi thuộc tính
+label_general: Tổng quan
+label_more: Chi tiết
+label_scm: SCM
+label_plugins: Mô-đun
+label_ldap_authentication: Chứng thực LDAP
+label_downloads_abbr: Tải vá»
+label_optional_description: Mô tả bổ sung
+label_add_another_file: Thêm tập tin khác
+label_preferences: Cấu hình
+label_chronological_order: Bài cũ xếp trước
+label_reverse_chronological_order: Bài mới xếp trước
+label_planning: Kế hoạch
+label_incoming_emails: Nhận mail
+label_generate_key: Tạo mã
+label_issue_watchers: Theo dõi
+
+button_login: Äăng nhập
+button_submit: Gá»­i
+button_save: Lưu
+button_check_all: Äánh dấu tất cả
+button_uncheck_all: BỠdấu tất cả
+button_delete: Xóa
+button_create: Tạo
+button_test: Kiểm tra
+button_edit: Sá»­a
+button_add: Thêm
+button_change: Äổi
+button_apply: Ãp dụng
+button_clear: Xóa
+button_lock: Khóa
+button_unlock: Mở khóa
+button_download: Tải vá»
+button_list: Liệt kê
+button_view: Xem
+button_move: Chuyển
+button_back: Quay lại
+button_cancel: Bá» qua
+button_activate: Kích hoạt
+button_sort: Sắp xếp
+button_log_time: Thêm thá»i gian
+button_rollback: Quay trở lại phiên bản này
+button_watch: Theo dõi
+button_unwatch: BỠtheo dõi
+button_reply: Trả lá»i
+button_archive: Äóng băng
+button_unarchive: Xả băng
+button_reset: Tạo lại
+button_rename: Äổi tên
+button_change_password: Äổi mật mã
+button_copy: Chép
+button_annotate: Chú giải
+button_update: Cập nhật
+button_configure: Cấu hình
+button_quote: Trích dẫn
+
+status_active: hoạt động
+status_registered: đăng ký
+status_locked: khóa
+
+text_select_mail_notifications: Chá»n hành động đối vá»›i má»—i email thông báo sẽ gá»­i.
+text_regexp_info: eg. ^[A-Z0-9]+$
+text_min_max_length_info: 0 để chỉ không hạn chế
+text_project_destroy_confirmation: Are you sure you want to delete this project and related data ?
+text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.'
+text_workflow_edit: Select a role and a tracker to edit the workflow
+text_are_you_sure: Bạn chắc chứ?
+text_journal_changed: đổi từ %s sang %s
+text_journal_set_to: đặt thành %s
+text_journal_deleted: xóa
+text_tip_task_begin_day: ngày bắt đầu
+text_tip_task_end_day: ngày kết thúc
+text_tip_task_begin_end_day: bắt đầu và kết thúc cùng ngày
+text_project_identifier_info: 'Chỉ cho phép chữ cái thưá»ng (a-z), con số và dấu gạch ngang.<br />Sau khi lưu, chỉ số ID không thể thay đổi.'
+text_caracters_maximum: Tối đa %d ký tự.
+text_caracters_minimum: Phải gồm ít nhất %d ký tự.
+text_length_between: Length between %d and %d characters.
+text_tracker_no_workflow: No workflow defined for this tracker
+text_unallowed_characters: Ký tự không hợp lệ
+text_comma_separated: Multiple values allowed (comma separated).
+text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
+text_issue_added: Issue %s has been reported by %s.
+text_issue_updated: Issue %s has been updated 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: Reassign issues to this category
+text_user_mail_option: "Vá»›i các dá»± án không được chá»n, bạn chỉ có thể nhận được thông báo vá» các vấn đỠbạn đăng ký theo dõi hoặc có liên quan đến bạn (chẳng hạn, vấn đỠđược gán cho bạn)."
+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_load_default_configuration: Load the default configuration
+text_status_changed_by_changeset: Applied in changeset %s.
+text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
+text_select_project_modules: 'Chá»n các mô-Ä‘un cho dá»± án:'
+text_default_administrator_account_changed: Default administrator account changed
+text_file_repository_writable: File repository writable
+text_rmagick_available: RMagick available (optional)
+text_destroy_time_entries_question: %.02f hours were reported on the issues you are about to delete. What do you want to do ?
+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: Äiá»u hành
+default_role_developper: Phát triển
+default_role_reporter: Báo cáo
+default_tracker_bug: Lá»—i
+default_tracker_feature: Tính năng
+default_tracker_support: Hỗ trợ
+default_issue_status_new: Má»›i
+default_issue_status_assigned: Äã gán
+default_issue_status_resolved: Quyết tâm
+default_issue_status_feedback: Phản hồi
+default_issue_status_closed: Äóng
+default_issue_status_rejected: Từ chối
+default_doc_category_user: Tài liệu ngưá»i dùng
+default_doc_category_tech: Tài liệu kỹ thuật
+default_priority_low: Thấp
+default_priority_normal: Bình thưá»ng
+default_priority_high: Cao
+default_priority_urgent: Khẩn cấp
+default_priority_immediate: Trung bình
+default_activity_design: Thiết kế
+default_activity_development: Phát triển
+
+enumeration_issue_priorities: Mức độ ưu tiên vấn Ä‘á»
+enumeration_doc_categories: Chủ đỠtài liệu
+enumeration_activities: Hoạt động (theo dõi thá»i gian)
+
+setting_plain_text_mail: mail dạng text đơn giản (không dùng HTML)
+setting_gravatar_enabled: Dùng biểu tượng Gravatar
+permission_edit_project: Chỉnh dự án
+permission_select_project_modules: Chá»n mô-Ä‘un
+permission_manage_members: Quản lý thành viên
+permission_manage_versions: Quản lý phiên bản
+permission_manage_categories: Quản lý chá»§ Ä‘á»
+permission_add_issues: Thêm vấn Ä‘á»
+permission_edit_issues: Sá»­a vấn Ä‘á»
+permission_manage_issue_relations: Quản lý quan hệ vấn Ä‘á»
+permission_add_issue_notes: Thêm chú thích
+permission_edit_issue_notes: Sửa chú thích
+permission_edit_own_issue_notes: Sửa chú thích cá nhân
+permission_move_issues: Chuyển vấn Ä‘á»
+permission_delete_issues: Xóa vấn Ä‘á»
+permission_manage_public_queries: Quản lý truy cấn công cộng
+permission_save_queries: Lưu truy vấn
+permission_view_gantt: Xem biểu đồ sự kiện
+permission_view_calendar: Xem lịch
+permission_view_issue_watchers: Xem các ngưá»i theo dõi
+permission_add_issue_watchers: Thêm ngưá»i theo dõi
+permission_log_time: Lưu thá»i gian đã tốn
+permission_view_time_entries: Xem thá»i gian đã tốn
+permission_edit_time_entries: Xem nhật ký thá»i gian
+permission_edit_own_time_entries: Sá»­a thá»i gian đã lưu
+permission_manage_news: Quản lý tin mới
+permission_comment_news: Chú thích vào tin mới
+permission_manage_documents: Quản lý tài liệu
+permission_view_documents: Xem tài liệu
+permission_manage_files: Quản lý tập tin
+permission_view_files: Xem tập tin
+permission_manage_wiki: Quản lý wiki
+permission_rename_wiki_pages: Äổi tên trang wiki
+permission_delete_wiki_pages: Xóa trang wiki
+permission_view_wiki_pages: Xem wiki
+permission_view_wiki_edits: Xem lược sử trang wiki
+permission_edit_wiki_pages: Sá»­a trang wiki
+permission_delete_wiki_pages_attachments: Xóa tệp đính kèm
+permission_protect_wiki_pages: Bảo vệ trang wiki
+permission_manage_repository: Quản lý kho lưu trữ
+permission_browse_repository: Duyệt kho lưu trữ
+permission_view_changesets: Xem các thay đổi
+permission_commit_access: Truy cập commit
+permission_manage_boards: Quản lý diễn đàn
+permission_view_messages: Xem bài viết
+permission_add_messages: Gửi bài viết
+permission_edit_messages: Sửa bài viết
+permission_edit_own_messages: Sửa bài viết cá nhân
+permission_delete_messages: Xóa bài viết
+permission_delete_own_messages: Xóa bài viết cá nhân
+label_example: Ví dụ
+text_repository_usernames_mapping: "Chá»n hoặc cập nhật ánh xạ ngưá»i dùng hệ thống vá»›i ngưá»i dùng trong kho lưu trữ.\nNhững trưá»ng hợp trùng hợp vá» tên và email sẽ được tá»± động ánh xạ."
+permission_delete_own_messages: Delete own messages
+label_user_activity: "%s's activity"
+label_updated_time_by: Updated by %s %s ago
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
+setting_diff_max_lines_displayed: Max number of diff lines displayed
+text_plugin_assets_writable: Plugin assets directory writable
+warning_attachments_not_saved: "%d file(s) could not be saved."
+button_create_and_continue: Create and continue
+text_custom_field_possible_values_info: 'One line for each value'
+label_display: Display
+field_editable: Editable
+setting_repository_log_display_limit: Maximum number of revisions displayed on file log
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/zh-tw.yml b/lang/zh-tw.yml
index 3ddf1402e..9f3788a85 100644
--- a/lang/zh-tw.yml
+++ b/lang/zh-tw.yml
@@ -85,6 +85,8 @@ error_scm_command_failed: "嘗試存å–儲存庫時發生錯誤: %s"
error_scm_annotate: "SCM 儲存庫中無此項目或此項目無法被加註。"
error_issue_not_found_in_project: '該項目ä¸å­˜åœ¨æˆ–ä¸å±¬æ–¼æ­¤å°ˆæ¡ˆ'
+warning_attachments_not_saved: "%d 個附加檔案無法儲存。"
+
mail_subject_lost_password: 您的 Redmine 網站密碼
mail_body_lost_password: '欲變更您的 Redmine 網站密碼, 請點é¸ä»¥ä¸‹éˆçµ:'
mail_subject_register: 啟用您的 Redmine 帳號
@@ -184,6 +186,7 @@ field_searchable: å¯ç”¨åšæœå°‹æ¢ä»¶
field_default_value: é è¨­å€¼
field_comments_sorting: 註解排åº
field_parent_title: 父é é¢
+field_editable: å¯ç·¨è¼¯
setting_app_title: 標題
setting_app_subtitle: 副標題
@@ -195,6 +198,7 @@ setting_attachment_max_size: 附件大å°é™åˆ¶
setting_issues_export_limit: 項目匯出é™åˆ¶
setting_mail_from: 寄件者電å­éƒµä»¶
setting_bcc_recipients: 使用密件副本 (BCC)
+setting_plain_text_mail: 純文字郵件 (ä¸å« HTML)
setting_host_name: 主機å稱
setting_text_formatting: 文字格å¼
setting_wiki_compression: 壓縮 Wiki æ­·å²æ–‡ç« 
@@ -221,6 +225,58 @@ setting_enabled_scm: 啟用的 SCM
setting_mail_handler_api_enabled: 啟用處ç†å‚³å…¥é›»å­éƒµä»¶çš„æœå‹™
setting_mail_handler_api_key: API 金鑰
setting_sequential_project_identifiers: 循åºç”¢ç”Ÿå°ˆæ¡ˆè­˜åˆ¥ç¢¼
+setting_gravatar_enabled: 啟用 Gravatar å…¨çƒèªè­‰å¤§é ­åƒ
+setting_diff_max_lines_displayed: 差異顯示行數之最大值
+setting_repository_log_display_limit: 版次顯示數目之最大值
+
+permission_edit_project: 編輯專案
+permission_select_project_modules: 鏿“‡å°ˆæ¡ˆæ¨¡çµ„
+permission_manage_members: ç®¡ç†æˆå“¡
+permission_manage_versions: 管ç†ç‰ˆæœ¬
+permission_manage_categories: 管ç†é …目分類
+permission_add_issues: 新增項目
+permission_edit_issues: 編輯項目
+permission_manage_issue_relations: 管ç†é …目關è¯
+permission_add_issue_notes: 新增筆記
+permission_edit_issue_notes: 編輯筆記
+permission_edit_own_issue_notes: 編輯自己的筆記
+permission_move_issues: æ¬ç§»é …ç›®
+permission_delete_issues: 刪除項目
+permission_manage_public_queries: 管ç†å…¬é–‹æŸ¥è©¢
+permission_save_queries: 儲存查詢
+permission_view_gantt: 檢視甘特圖
+permission_view_calendar: 檢視日曆
+permission_view_issue_watchers: 檢視觀察者清單
+permission_add_issue_watchers: 增加觀察者
+permission_log_time: 紀錄耗用工時
+permission_view_time_entries: 檢視耗用工時
+permission_edit_time_entries: 編輯工時紀錄
+permission_edit_own_time_entries: 編輯自己的工時記錄
+permission_manage_news: ç®¡ç†æ–°èž
+permission_comment_news: 註解新èž
+permission_manage_documents: ç®¡ç†æ–‡ä»¶
+permission_view_documents: 檢視文件
+permission_manage_files: ç®¡ç†æª”案
+permission_view_files: 檢視檔案
+permission_manage_wiki: ç®¡ç† wiki
+permission_rename_wiki_pages: 釿–°å‘½å wiki é é¢
+permission_delete_wiki_pages: 刪除 wiki é é¢
+permission_view_wiki_pages: 檢視 wiki
+permission_view_wiki_edits: 檢視 wiki æ­·å²
+permission_edit_wiki_pages: 編輯 wiki é é¢
+permission_delete_wiki_pages_attachments: 刪除附件
+permission_protect_wiki_pages: 專案 wiki é é¢
+permission_manage_repository: 管ç†ç‰ˆæœ¬åº«
+permission_browse_repository: ç€è¦½ç‰ˆæœ¬åº«
+permission_view_changesets: 檢視變更集
+permission_commit_access: å­˜å–é€äº¤ä¹‹è®Šæ›´
+permission_manage_boards: 管ç†è¨Žè«–版
+permission_view_messages: 檢視訊æ¯
+permission_add_messages: 新增訊æ¯
+permission_edit_messages: 編輯訊æ¯
+permission_edit_own_messages: 編輯自己的訊æ¯
+permission_delete_messages: 刪除訊æ¯
+permission_delete_own_messages: 刪除自己的訊æ¯
project_module_issue_tracking: 項目追蹤
project_module_time_tracking: 工時追蹤
@@ -293,6 +349,7 @@ label_last_updates_plural: %d 個最近更新
label_registered_on: 註冊於
label_activity: 活動
label_overall_activity: 檢視所有活動
+label_user_activity: "%s 的活動"
label_new: 建立新的...
label_logged_as: ç›®å‰ç™»å…¥
label_environment: 環境
@@ -418,7 +475,7 @@ label_sort_higher: 往上移動
label_sort_lower: 往下移動
label_sort_lowest: 移動至çµå°¾
label_roadmap: 版本è—圖
-label_roadmap_due_in: 倒數天數 %s
+label_roadmap_due_in: 倒數天數:
label_roadmap_overdue: %s 逾期
label_roadmap_no_issues: 此版本尚未包å«ä»»ä½•é …ç›®
label_search: æœå°‹
@@ -492,6 +549,7 @@ label_send_test_email: 坄逿¸¬è©¦éƒµä»¶
label_feeds_access_key_created_on: RSS å­˜å–éµå»ºç«‹æ–¼ %s 之å‰
label_module_plural: 模組
label_added_time_by: 是由 %s æ–¼ %s å‰åŠ å…¥
+label_updated_time_by: 是由 %s æ–¼ %s 剿›´æ–°
label_updated_time: æ–¼ %s 剿›´æ–°
label_jump_to_a_project: 鏿“‡æ¬²å‰å¾€çš„專案...
label_file_plural: 檔案清單
@@ -503,7 +561,7 @@ label_theme: ç•«é¢ä¸»é¡Œ
label_default: é è¨­
label_search_titles_only: 僅æœå°‹æ¨™é¡Œ
label_user_mail_option_all: "æé†’與我的專案有關的所有事件"
-label_user_mail_option_selected: "åªåœé†’æˆ‘æ‰€é¸æ“‡å°ˆæ¡ˆä¸­çš„事件..."
+label_user_mail_option_selected: "åªæé†’æˆ‘æ‰€é¸æ“‡å°ˆæ¡ˆä¸­çš„事件..."
label_user_mail_option_none: "åªæé†’æˆ‘è§€å¯Ÿä¸­æˆ–åƒèˆ‡ä¸­çš„事件"
label_user_mail_no_self_notified: "ä¸æé†’æˆ‘è‡ªå·±æ‰€åšçš„變更"
label_registration_activation_by_email: é€éŽé›»å­éƒµä»¶å•Ÿç”¨å¸³æˆ¶
@@ -527,6 +585,8 @@ label_planning: 計劃表
label_incoming_emails: 傳入的電å­éƒµä»¶
label_generate_key: 產生金鑰
label_issue_watchers: 觀察者
+label_example: 範例
+label_display: 顯示
button_login: 登入
button_submit: é€å‡º
@@ -535,6 +595,7 @@ button_check_all: å…¨é¸
button_uncheck_all: å…¨ä¸é¸
button_delete: 刪除
button_create: 建立
+button_create_and_continue: 繼續建立
button_test: 測試
button_edit: 編輯
button_add: 新增
@@ -605,7 +666,8 @@ text_status_changed_by_changeset: 已套用至變更集 %s.
text_issues_destroy_confirmation: 'ç¢ºå®šåˆªé™¤å·²é¸æ“‡çš„項目?'
text_select_project_modules: '鏿“‡æ­¤å°ˆæ¡ˆå¯ä½¿ç”¨ä¹‹æ¨¡çµ„:'
text_default_administrator_account_changed: 已變更é è¨­ç®¡ç†å“¡å¸³è™Ÿå…§å®¹
-text_file_repository_writable: å¯å¯«å…¥æª”案
+text_file_repository_writable: å¯å¯«å…¥é™„加檔案目錄
+text_plugin_assets_writable: å¯å¯«å…¥é™„加元件目錄
text_rmagick_available: å¯ä½¿ç”¨ RMagick (é¸é…)
text_destroy_time_entries_question: 您å³å°‡åˆªé™¤çš„項目已報工 %.02f å°æ™‚. æ‚¨çš„é¸æ“‡æ˜¯ï¼Ÿ
text_destroy_time_entries: 刪除已報工的時數
@@ -615,6 +677,9 @@ 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,以啟用電å­éƒµä»¶æé†’é¸é …。"
+text_repository_usernames_mapping: "鏿“‡æˆ–æ›´æ–° Redmine ä½¿ç”¨è€…èˆ‡ç‰ˆæœ¬åº«ä½¿ç”¨è€…ä¹‹å°æ‡‰é—œä¿‚。\n版本庫中之使用者帳號或電å­éƒµä»¶ä¿¡ç®±ï¼Œèˆ‡ Redmine 設定相åŒè€…ï¼Œå°‡è‡ªå‹•ç”¢ç”Ÿå°æ‡‰é—œä¿‚。"
+text_diff_truncated: '... 這份差異已被截短以符åˆé¡¯ç¤ºè¡Œæ•¸ä¹‹æœ€å¤§å€¼'
+text_custom_field_possible_values_info: '一列輸入一個值'
default_role_manager: 管ç†äººå“¡
default_role_developper: 開發人員
@@ -641,5 +706,7 @@ default_activity_development: 開發
enumeration_issue_priorities: 項目優先權
enumeration_doc_categories: 文件分類
enumeration_activities: 活動 (時間追蹤)
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lang/zh.yml b/lang/zh.yml
index cd2d3e407..4be80b3bb 100644
--- a/lang/zh.yml
+++ b/lang/zh.yml
@@ -85,6 +85,8 @@ error_scm_command_failed: "访问版本库时å‘生错误:%s"
error_scm_annotate: "该æ¡ç›®ä¸å­˜åœ¨æˆ–无法追溯。"
error_issue_not_found_in_project: '问题ä¸å­˜åœ¨æˆ–ä¸å±žäºŽæ­¤é¡¹ç›®'
+warning_attachments_not_saved: "%d 个文件ä¿å­˜å¤±è´¥ã€‚"
+
mail_subject_lost_password: 您的 %s 密ç 
mail_body_lost_password: '请点击以下链接æ¥ä¿®æ”¹æ‚¨çš„密ç ï¼š'
mail_subject_register: %så¸å·æ¿€æ´»
@@ -184,6 +186,7 @@ field_searchable: å¯ç”¨ä½œæœç´¢æ¡ä»¶
field_default_value: 默认值
field_comments_sorting: 显示注释
field_parent_title: 上级页é¢
+field_editable: å¯ç¼–辑
setting_app_title: åº”ç”¨ç¨‹åºæ ‡é¢˜
setting_app_subtitle: 应用程åºå­æ ‡é¢˜
@@ -195,6 +198,7 @@ setting_attachment_max_size: 附件大å°é™åˆ¶
setting_issues_export_limit: 问题输出æ¡ç›®çš„é™åˆ¶
setting_mail_from: 邮件å‘件人地å€
setting_bcc_recipients: ä½¿ç”¨å¯†ä»¶æŠ„é€ (bcc)
+setting_plain_text_mail: 纯文本(无HTML)
setting_host_name: 主机åç§°
setting_text_formatting: 文本格å¼
setting_wiki_compression: 压缩WikiåŽ†å²æ–‡æ¡£
@@ -218,9 +222,61 @@ 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_enabled: å¯ç”¨ç”¨äºŽæŽ¥æ”¶é‚®ä»¶çš„æœåŠ¡
setting_mail_handler_api_key: API key
setting_sequential_project_identifiers: 顺åºäº§ç”Ÿé¡¹ç›®æ ‡è¯†
+setting_gravatar_enabled: 使用Gravatar用户头åƒ
+setting_diff_max_lines_displayed: 查看差别页é¢ä¸Šæ˜¾ç¤ºçš„æœ€å¤§è¡Œæ•°
+setting_repository_log_display_limit: åœ¨æ–‡ä»¶å˜æ›´è®°å½•页é¢ä¸Šæ˜¾ç¤ºçš„æœ€å¤§ä¿®è®¢ç‰ˆæœ¬æ•°é‡
+
+permission_edit_project: 编辑项目
+permission_select_project_modules: 选择项目模å—
+permission_manage_members: ç®¡ç†æˆå‘˜
+permission_manage_versions: 管ç†ç‰ˆæœ¬
+permission_manage_categories: 管ç†é—®é¢˜ç±»åˆ«
+permission_add_issues: 新建问题
+permission_edit_issues: 更新问题
+permission_manage_issue_relations: 管ç†é—®é¢˜å…³è”
+permission_add_issue_notes: 添加说明
+permission_edit_issue_notes: 编辑说明
+permission_edit_own_issue_notes: 编辑自己的说明
+permission_move_issues: 移动问题
+permission_delete_issues: 删除问题
+permission_manage_public_queries: 管ç†å…¬å¼€çš„æŸ¥è¯¢
+permission_save_queries: ä¿å­˜æŸ¥è¯¢
+permission_view_gantt: 查看甘特图
+permission_view_calendar: 查看日历
+permission_view_issue_watchers: 查看跟踪者列表
+permission_add_issue_watchers: 添加跟踪者
+permission_log_time: 登记工时
+permission_view_time_entries: 查看耗时
+permission_edit_time_entries: 编辑耗时
+permission_edit_own_time_entries: 编辑自己的耗时
+permission_manage_news: ç®¡ç†æ–°é—»
+permission_comment_news: 为新闻添加评论
+permission_manage_documents: ç®¡ç†æ–‡æ¡£
+permission_view_documents: 查看文档
+permission_manage_files: ç®¡ç†æ–‡ä»¶
+permission_view_files: 查看文件
+permission_manage_wiki: 管ç†Wiki
+permission_rename_wiki_pages: é‡å‘½åWiki页é¢
+permission_delete_wiki_pages: 删除Wiki页é¢
+permission_view_wiki_pages: 查看Wiki
+permission_view_wiki_edits: 查看Wiki历å²è®°å½•
+permission_edit_wiki_pages: 编辑Wiki页é¢
+permission_delete_wiki_pages_attachments: 删除附件
+permission_protect_wiki_pages: ä¿æŠ¤Wiki页é¢
+permission_manage_repository: 管ç†ç‰ˆæœ¬åº“
+permission_browse_repository: æµè§ˆç‰ˆæœ¬åº“
+permission_view_changesets: æŸ¥çœ‹å˜æ›´
+permission_commit_access: 访问æäº¤ä¿¡æ¯
+permission_manage_boards: 管ç†è®¨è®ºåŒº
+permission_view_messages: 查看帖å­
+permission_add_messages: å‘表帖å­
+permission_edit_messages: 编辑帖å­
+permission_edit_own_messages: 编辑自己的帖å­
+permission_delete_messages: 删除帖å­
+permission_delete_own_messages: 删除自己的帖å­
project_module_issue_tracking: 问题跟踪
project_module_time_tracking: 时间跟踪
@@ -293,6 +349,7 @@ label_last_updates_plural: %d æœ€åŽæ›´æ–°
label_registered_on: 注册于
label_activity: 活动
label_overall_activity: 全部活动
+label_user_activity: "%s 的活动"
label_new: 新建
label_logged_as: 登录为
label_environment: 环境
@@ -492,7 +549,8 @@ label_send_test_email: å‘逿µ‹è¯•邮件
label_feeds_access_key_created_on: RSS å­˜å–键是在 %s 之å‰å»ºç«‹çš„
label_module_plural: 模å—
label_added_time_by: ç”± %s 在 %s 之剿·»åŠ 
-label_updated_time: 更新于 %s å‰
+label_updated_time: 更新于 %s 之å‰
+label_updated_time_by: ç”± %s 更新于 %s 之å‰
label_jump_to_a_project: 选择一个项目...
label_file_plural: 文件
label_changeset_plural: å˜æ›´
@@ -527,6 +585,8 @@ label_planning: 计划
label_incoming_emails: 接收邮件
label_generate_key: 生æˆä¸€ä¸ªkey
label_issue_watchers: 跟踪者
+label_example: 示例
+label_display: 显示
button_login: 登录
button_submit: æäº¤
@@ -535,6 +595,7 @@ button_check_all: 全选
button_uncheck_all: 清除
button_delete: 删除
button_create: 创建
+button_create_and_continue: 创建并继续
button_test: 测试
button_edit: 编辑
button_add: 新增
@@ -605,7 +666,8 @@ text_status_changed_by_changeset: å·²åº”ç”¨åˆ°å˜æ›´åˆ—表 %s.
text_issues_destroy_confirmation: '您确定è¦åˆ é™¤é€‰ä¸­çš„问题å—?'
text_select_project_modules: '请选择此项目å¯ä»¥ä½¿ç”¨çš„æ¨¡å—:'
text_default_administrator_account_changed: 默认的管ç†å‘˜å¸å·å·²æ”¹å˜
-text_file_repository_writable: 文件版本库å¯ä¿®æ”¹
+text_file_repository_writable: 附件路径å¯å†™
+text_plugin_assets_writable: æ’件的附件路径å¯å†™
text_rmagick_available: RMagick å¯ç”¨ï¼ˆå¯é€‰çš„)
text_destroy_time_entries_question: 您è¦åˆ é™¤çš„问题已ç»ä¸ŠæŠ¥äº† %.02f å°æ—¶çš„工作é‡ã€‚æ‚¨æƒ³è¿›è¡Œé‚£ç§æ“作?
text_destroy_time_entries: 删除上报的工作é‡
@@ -615,6 +677,9 @@ text_user_wrote: '%s 写到:'
text_enumeration_category_reassign_to: '将它们关è”到新的枚举值:'
text_enumeration_destroy_question: '%d 个对象被关è”到了这个枚举值。'
text_email_delivery_not_configured: "邮件傿•°å°šæœªé…置,因此邮件通知功能已被ç¦ç”¨ã€‚\n请在config/email.yml中é…置您的SMTPæœåŠ¡å™¨ä¿¡æ¯å¹¶é‡æ–°å¯åŠ¨ä»¥ä½¿å…¶ç”Ÿæ•ˆã€‚"
+text_repository_usernames_mapping: "选择或更新与版本库中的用户å对应的Redmine用户。\n版本库中与Redmine中的åŒå用户将被自动对应。"
+text_diff_truncated: '... å·®åˆ«å†…å®¹è¶…è¿‡äº†å¯æ˜¾ç¤ºçš„æœ€å¤§è¡Œæ•°å¹¶å·²è¢«æˆªæ–­'
+text_custom_field_possible_values_info: 'æ¯é¡¹æ•°å€¼ä¸€è¡Œ'
default_role_manager: 管ç†äººå‘˜
default_role_developper: å¼€å‘人员
@@ -641,5 +706,7 @@ default_activity_development: å¼€å‘
enumeration_issue_priorities: 问题优先级
enumeration_doc_categories: 文档类别
enumeration_activities: 活动(时间跟踪)
-setting_repositories_cache_directory: Cache directory for repositories
-field_cache: Local cache
+field_identity_url: OpenID URL
+setting_openid: Allow OpenID login and registration
+label_login_with_open_id_option: or login with OpenID
+field_watcher: Watcher
diff --git a/lib/generators/redmine_plugin/redmine_plugin_generator.rb b/lib/generators/redmine_plugin/redmine_plugin_generator.rb
index 666386abd..bd161a5d7 100644
--- a/lib/generators/redmine_plugin/redmine_plugin_generator.rb
+++ b/lib/generators/redmine_plugin/redmine_plugin_generator.rb
@@ -22,10 +22,10 @@ class RedminePluginGenerator < Rails::Generator::NamedBase
m.directory "#{plugin_path}/lang"
m.directory "#{plugin_path}/test"
- m.template 'README', "#{plugin_path}/README"
- m.template 'init.rb', "#{plugin_path}/init.rb"
+ m.template 'README.rdoc', "#{plugin_path}/README.rdoc"
+ m.template 'init.rb.erb', "#{plugin_path}/init.rb"
m.template 'en.yml', "#{plugin_path}/lang/en.yml"
- m.template 'test_helper.rb', "#{plugin_path}/test/test_helper.rb"
+ m.template 'test_helper.rb.erb', "#{plugin_path}/test/test_helper.rb"
end
end
end
diff --git a/lib/generators/redmine_plugin/templates/README b/lib/generators/redmine_plugin/templates/README.rdoc
index 7a31a890e..7a31a890e 100644
--- a/lib/generators/redmine_plugin/templates/README
+++ b/lib/generators/redmine_plugin/templates/README.rdoc
diff --git a/lib/generators/redmine_plugin/templates/init.rb b/lib/generators/redmine_plugin/templates/init.rb.erb
index 1ffcd7bb8..1ffcd7bb8 100644
--- a/lib/generators/redmine_plugin/templates/init.rb
+++ b/lib/generators/redmine_plugin/templates/init.rb.erb
diff --git a/lib/generators/redmine_plugin/templates/test_helper.rb b/lib/generators/redmine_plugin/templates/test_helper.rb.erb
index bd1ed0c5d..bd1ed0c5d 100644
--- a/lib/generators/redmine_plugin/templates/test_helper.rb
+++ b/lib/generators/redmine_plugin/templates/test_helper.rb.erb
diff --git a/lib/generators/redmine_plugin_controller/redmine_plugin_controller_generator.rb b/lib/generators/redmine_plugin_controller/redmine_plugin_controller_generator.rb
index 533d65ce8..efe28c5da 100644
--- a/lib/generators/redmine_plugin_controller/redmine_plugin_controller_generator.rb
+++ b/lib/generators/redmine_plugin_controller/redmine_plugin_controller_generator.rb
@@ -15,4 +15,40 @@ class RedminePluginControllerGenerator < ControllerGenerator
def destination_root
File.join(RAILS_ROOT, plugin_path)
end
+
+ def manifest
+ record do |m|
+ # Check for class naming collisions.
+ m.class_collisions class_path, "#{class_name}Controller", "#{class_name}ControllerTest", "#{class_name}Helper"
+
+ # Controller, helper, views, and test directories.
+ m.directory File.join('app/controllers', class_path)
+ m.directory File.join('app/helpers', class_path)
+ m.directory File.join('app/views', class_path, file_name)
+ m.directory File.join('test/functional', class_path)
+
+ # Controller class, functional test, and helper class.
+ m.template 'controller.rb.erb',
+ File.join('app/controllers',
+ class_path,
+ "#{file_name}_controller.rb")
+
+ m.template 'functional_test.rb.erb',
+ File.join('test/functional',
+ class_path,
+ "#{file_name}_controller_test.rb")
+
+ m.template 'helper.rb.erb',
+ File.join('app/helpers',
+ class_path,
+ "#{file_name}_helper.rb")
+
+ # View template for each action.
+ actions.each do |action|
+ path = File.join('app/views', class_path, file_name, "#{action}.html.erb")
+ m.template 'view.html.erb', path,
+ :assigns => { :action => action, :path => path }
+ end
+ end
+ end
end
diff --git a/lib/generators/redmine_plugin_controller/templates/controller.rb b/lib/generators/redmine_plugin_controller/templates/controller.rb.erb
index 615986d9e..615986d9e 100644
--- a/lib/generators/redmine_plugin_controller/templates/controller.rb
+++ b/lib/generators/redmine_plugin_controller/templates/controller.rb.erb
diff --git a/lib/generators/redmine_plugin_controller/templates/functional_test.rb b/lib/generators/redmine_plugin_controller/templates/functional_test.rb.erb
index 876bd79e2..876bd79e2 100644
--- a/lib/generators/redmine_plugin_controller/templates/functional_test.rb
+++ b/lib/generators/redmine_plugin_controller/templates/functional_test.rb.erb
diff --git a/lib/generators/redmine_plugin_controller/templates/helper.rb b/lib/generators/redmine_plugin_controller/templates/helper.rb.erb
index 3fe2ecdc7..3fe2ecdc7 100644
--- a/lib/generators/redmine_plugin_controller/templates/helper.rb
+++ b/lib/generators/redmine_plugin_controller/templates/helper.rb.erb
diff --git a/lib/generators/redmine_plugin_model/redmine_plugin_model_generator.rb b/lib/generators/redmine_plugin_model/redmine_plugin_model_generator.rb
index b712d9b07..059a028d6 100644
--- a/lib/generators/redmine_plugin_model/redmine_plugin_model_generator.rb
+++ b/lib/generators/redmine_plugin_model/redmine_plugin_model_generator.rb
@@ -15,4 +15,30 @@ class RedminePluginModelGenerator < ModelGenerator
def destination_root
File.join(RAILS_ROOT, plugin_path)
end
+
+ def manifest
+ record do |m|
+ # Check for class naming collisions.
+ m.class_collisions class_path, class_name, "#{class_name}Test"
+
+ # Model, test, and fixture directories.
+ m.directory File.join('app/models', class_path)
+ m.directory File.join('test/unit', class_path)
+ m.directory File.join('test/fixtures', class_path)
+
+ # Model class, unit test, and fixtures.
+ m.template 'model.rb.erb', File.join('app/models', class_path, "#{file_name}.rb")
+ m.template 'unit_test.rb.erb', File.join('test/unit', class_path, "#{file_name}_test.rb")
+
+ unless options[:skip_fixture]
+ m.template 'fixtures.yml', File.join('test/fixtures', "#{table_name}.yml")
+ end
+
+ unless options[:skip_migration]
+ m.migration_template 'migration.rb.erb', 'db/migrate', :assigns => {
+ :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
+ }, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
+ end
+ end
+ end
end
diff --git a/lib/generators/redmine_plugin_model/templates/migration.rb b/lib/generators/redmine_plugin_model/templates/migration.rb.erb
index 2a305a6a9..2a305a6a9 100644
--- a/lib/generators/redmine_plugin_model/templates/migration.rb
+++ b/lib/generators/redmine_plugin_model/templates/migration.rb.erb
diff --git a/lib/generators/redmine_plugin_model/templates/model.rb b/lib/generators/redmine_plugin_model/templates/model.rb.erb
index 8d4c89e91..8d4c89e91 100644
--- a/lib/generators/redmine_plugin_model/templates/model.rb
+++ b/lib/generators/redmine_plugin_model/templates/model.rb.erb
diff --git a/lib/generators/redmine_plugin_model/templates/unit_test.rb b/lib/generators/redmine_plugin_model/templates/unit_test.rb.erb
index cc8b2e591..cc8b2e591 100644
--- a/lib/generators/redmine_plugin_model/templates/unit_test.rb
+++ b/lib/generators/redmine_plugin_model/templates/unit_test.rb.erb
diff --git a/lib/redcloth3.rb b/lib/redcloth3.rb
index a5e63262c..9e63bf5ac 100644
--- a/lib/redcloth3.rb
+++ b/lib/redcloth3.rb
@@ -272,7 +272,7 @@ class RedCloth3 < String
@shelf = []
textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
:block_textile_prefix, :inline_textile_image, :inline_textile_link,
- :inline_textile_code, :inline_textile_span]
+ :inline_textile_code, :inline_textile_span, :glyphs_textile]
markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
:block_markdown_bq, :block_markdown_lists,
:inline_markdown_reflink, :inline_markdown_link]
@@ -341,7 +341,7 @@ class RedCloth3 < String
A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
A_VLGN = /[\-^~]/
C_CLAS = '(?:\([^)]+\))'
- C_LNGE = '(?:\[[^\]]+\])'
+ C_LNGE = '(?:\[[^\[\]]+\])'
C_STYL = '(?:\{[^}]+\})'
S_CSPN = '(?:\\\\\d+)'
S_RSPN = '(?:/\d+)'
@@ -382,14 +382,14 @@ class RedCloth3 < String
(#{rcq})
(#{C})
(?::(\S+?))?
- ([^\s\-].*?[^\s\-]|\w)
+ (\w|[^\s\-].*?[^\s\-])
#{rcq}
(?=[[:punct:]]|\s|\)|$)/x
else
/(#{rcq})
(#{C})
(?::(\S+))?
- ([^\s\-].*?[^\s\-]|\w)
+ (\w|[^\s\-].*?[^\s\-])
#{rcq}/xm
end
[rc, ht, re, rtype]
@@ -401,22 +401,22 @@ class RedCloth3 < String
# [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
# [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
# [ /\'/, '&#8216;' ], # single opening
- [ /</, '&lt;' ], # less-than
- [ />/, '&gt;' ], # greater-than
+ # [ /</, '&lt;' ], # less-than
+ # [ />/, '&gt;' ], # greater-than
# [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
# [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
# [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
# [ /"/, '&#8220;' ], # double opening
- [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
- [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
- [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
- [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
- [ /\s->\s/, ' &rarr; ' ], # right arrow
- [ /\s-\s/, ' &#8211; ' ], # en dash
- [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
- [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
- [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
- [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
+ # [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
+ # [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
+ # [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
+ # [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
+ # [ /\s->\s/, ' &rarr; ' ], # right arrow
+ # [ /\s-\s/, ' &#8211; ' ], # en dash
+ # [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
+ # [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
+ # [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
+ # [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
]
H_ALGN_VALS = {
@@ -435,19 +435,25 @@ class RedCloth3 < String
#
# Flexible HTML escaping
#
- def htmlesc( str, mode )
+ def htmlesc( str, mode=:Quotes )
+ if str
str.gsub!( '&', '&amp;' )
str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
str.gsub!( "'", '&#039;' ) if mode == :Quotes
str.gsub!( '<', '&lt;')
str.gsub!( '>', '&gt;')
+ end
+ str
end
# Search and replace for Textile glyphs (quotes, dashes, other symbols)
def pgl( text )
- GLYPHS.each do |re, resub, tog|
- next if tog and method( tog ).call
- text.gsub! re, resub
+ #GLYPHS.each do |re, resub, tog|
+ # next if tog and method( tog ).call
+ # text.gsub! re, resub
+ #end
+ text.gsub!(/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/) do |m|
+ "<acronym title=\"#{htmlesc $2}\">#{$1}</acronym>"
end
end
@@ -464,8 +470,7 @@ class RedCloth3 < String
style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
end
- style << "#{ $1 };" if not filter_styles and
- text.sub!( /\{([^}]*)\}/, '' )
+ style << "#{ htmlesc $1 };" if text.sub!( /\{([^}]*)\}/, '' ) && !filter_styles
lang = $1 if
text.sub!( /\[([^)]+?)\]/, '' )
@@ -684,7 +689,7 @@ class RedCloth3 < String
alias textile_h6 textile_p
def textile_fn_( tag, num, atts, cite, content )
- atts << " id=\"fn#{ num }\""
+ atts << " id=\"fn#{ num }\" class=\"footnote\""
content = "<sup>#{ num }</sup> #{ content }"
atts = shelve( atts ) if atts
"\t<p#{ atts }>#{ content }</p>"
@@ -786,7 +791,10 @@ class RedCloth3 < String
\s?
(?:\(([^)]+?)\)(?="))? # $title
":
- ([\w\/]\S+?) # $url
+ ( # $url
+ (\/|[a-zA-Z]+:\/\/|www\.) # $proto
+ [\w\/]\S+?
+ )
(\/)? # $slash
([^\w\=\/;\(\)]*?) # $post
(?=<|\s|$)
@@ -794,7 +802,7 @@ class RedCloth3 < String
#"
def inline_textile_link( text )
text.gsub!( LINK_RE ) do |m|
- pre,atts,text,title,url,slash,post = $~[1..7]
+ pre,atts,text,title,url,proto,slash,post = $~[1..8]
url, url_title = check_refs( url )
title ||= url_title
@@ -807,7 +815,7 @@ class RedCloth3 < String
end
atts = pba( atts )
atts = " href=\"#{ url }#{ slash }\"#{ atts }"
- atts << " title=\"#{ title }\"" if title
+ atts << " title=\"#{ htmlesc title }\"" if title
atts = shelve( atts ) if atts
external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
@@ -914,6 +922,7 @@ class RedCloth3 < String
def inline_textile_image( text )
text.gsub!( IMAGE_RE ) do |m|
stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
+ htmlesc title
atts = pba( atts )
atts = " src=\"#{ url }\"#{ atts }"
atts << " title=\"#{ title }\"" if title
@@ -1045,17 +1054,21 @@ class RedCloth3 < String
codepre += 1
used_offtags[offtag] = true
if codepre - used_offtags.length > 0
- htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
+ htmlesc( line, :NoQuotes )
@pre_list.last << line
line = ""
else
- htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
+ htmlesc( aftertag, :NoQuotes ) if aftertag
line = "<redpre##{ @pre_list.length }>"
- @pre_list << "#{ $3 }#{ aftertag }"
+ $3.match(/<#{ OFFTAGS }([^>]*)>/)
+ tag = $1
+ $2.to_s.match(/(class\=\S+)/i)
+ tag << " #{$1}" if $1
+ @pre_list << "<#{ tag }>#{ aftertag }"
end
elsif $1 and codepre > 0
if codepre - used_offtags.length > 0
- htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
+ htmlesc( line, :NoQuotes )
@pre_list.last << line
line = ""
end
diff --git a/lib/redmine.rb b/lib/redmine.rb
index 5529e0bf5..c8d64b8c3 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -6,6 +6,7 @@ require 'redmine/core_ext'
require 'redmine/themes'
require 'redmine/hook'
require 'redmine/plugin'
+require 'redmine/wiki_formatting'
begin
require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
@@ -34,7 +35,7 @@ Redmine::AccessControl.map do |map|
:queries => :index,
:reports => :issue_report}, :public => true
map.permission :add_issues, {:issues => :new}
- map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :destroy_attachment]}
+ map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit]}
map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
map.permission :add_issue_notes, {:issues => [:edit, :reply]}
map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
@@ -66,12 +67,12 @@ Redmine::AccessControl.map do |map|
end
map.project_module :documents do |map|
- map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
+ map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
map.permission :view_documents, :documents => [:index, :show, :download]
end
map.project_module :files do |map|
- map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
+ map.permission :manage_files, {:projects => :add_file}, :require => :loggedin
map.permission :view_files, :projects => :list_files, :versions => :download
end
@@ -81,12 +82,13 @@ Redmine::AccessControl.map do |map|
map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
map.permission :view_wiki_pages, :wiki => [:index, :special]
map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
- map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
+ map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment]
+ map.permission :delete_wiki_pages_attachments, {}
map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
end
map.project_module :repository do |map|
- map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
+ map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
map.permission :commit_access, {}
@@ -97,29 +99,35 @@ Redmine::AccessControl.map do |map|
map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
map.permission :add_messages, {:messages => [:new, :reply, :quote]}
map.permission :edit_messages, {:messages => :edit}, :require => :member
+ map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
map.permission :delete_messages, {:messages => :destroy}, :require => :member
+ map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
end
end
Redmine::MenuManager.map :top_menu do |menu|
- 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? }, :last => true
- menu.push :help, Redmine::Info.help_url, :html => { :class => 'help' }, :last => true
+ menu.push :home, :home_path
+ menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
+ menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
+ menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
+ menu.push :help, Redmine::Info.help_url, :last => true
end
Redmine::MenuManager.map :account_menu do |menu|
- 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_path, :html => { :class => 'logout' }, :if => Proc.new { User.current.logged? }
+ menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
+ menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
+ menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
+ menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
end
Redmine::MenuManager.map :application_menu do |menu|
# Empty
end
+Redmine::MenuManager.map :admin_menu do |menu|
+ # Empty
+end
+
Redmine::MenuManager.map :project_menu do |menu|
menu.push :overview, { :controller => 'projects', :action => 'show' }
menu.push :activity, { :controller => 'projects', :action => 'activity' }
@@ -149,3 +157,7 @@ Redmine::Activity.map do |activity|
activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
activity.register :messages, :default => false
end
+
+Redmine::WikiFormatting.map do |format|
+ format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
+end
diff --git a/lib/redmine/access_control.rb b/lib/redmine/access_control.rb
index f5b25f277..25cf63d61 100644
--- a/lib/redmine/access_control.rb
+++ b/lib/redmine/access_control.rb
@@ -30,8 +30,15 @@ module Redmine
@permissions
end
+ # Returns the permission of given name or nil if it wasn't found
+ # Argument should be a symbol
+ def permission(name)
+ permissions.detect {|p| p.name == name}
+ end
+
+ # Returns the actions that are allowed by the permission of given name
def allowed_actions(permission_name)
- perm = @permissions.detect {|p| p.name == permission_name}
+ perm = permission(permission_name)
perm ? perm.actions : []
end
@@ -94,6 +101,7 @@ module Redmine
@actions << "#{controller}/#{actions}"
end
end
+ @actions.flatten!
end
def public?
diff --git a/lib/redmine/activity/fetcher.rb b/lib/redmine/activity/fetcher.rb
index adaead564..1d0bd8a16 100644
--- a/lib/redmine/activity/fetcher.rb
+++ b/lib/redmine/activity/fetcher.rb
@@ -25,7 +25,7 @@ module Redmine
@@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)
+ options.assert_valid_keys(:project, :with_subprojects, :author)
@user = user
@project = options[:project]
@options = options
@@ -48,8 +48,16 @@ module Redmine
end
# Sets the scope
+ # Argument can be :all, :default or an array of event types
def scope=(s)
- @scope = s & event_types
+ case s
+ when :all
+ @scope = event_types
+ when :default
+ default_scope!
+ else
+ @scope = s & event_types
+ end
end
# Resets the scope to the default scope
@@ -58,14 +66,20 @@ module Redmine
end
# Returns an array of events for the given date range
- def events(from, to)
+ def events(from = nil, to = nil, options={})
e = []
+ @options[:limit] = options[:limit]
@scope.each do |event_type|
constantized_providers(event_type).each do |provider|
e += provider.find_events(event_type, @user, from, to, @options)
end
end
+
+ if options[:limit]
+ e.sort! {|a,b| b.event_date <=> a.event_date}
+ e = e.slice(0, options[:limit])
+ end
e
end
diff --git a/lib/redmine/core_ext/string/conversions.rb b/lib/redmine/core_ext/string/conversions.rb
index 41149f5ea..68fbcde75 100644
--- a/lib/redmine/core_ext/string/conversions.rb
+++ b/lib/redmine/core_ext/string/conversions.rb
@@ -24,7 +24,9 @@ module Redmine #:nodoc:
def to_hours
s = self.dup
s.strip!
- unless s =~ %r{^[\d\.,]+$}
+ if s =~ %r{^(\d+([.,]\d+)?)h?$}
+ s = $1
+ else
# 2:30 => 2.5
s.gsub!(%r{^(\d+):(\d+)$}) { $1.to_i + $2.to_i / 60.0 }
# 2h30, 2h, 30m => 2.5, 2, 0.5
diff --git a/lib/redmine/default_data/loader.rb b/lib/redmine/default_data/loader.rb
index 1c3b1f939..b7cab56ca 100644
--- a/lib/redmine/default_data/loader.rb
+++ b/lib/redmine/default_data/loader.rb
@@ -65,6 +65,7 @@ module Redmine
:edit_wiki_pages,
:delete_wiki_pages,
:add_messages,
+ :edit_own_messages,
:view_files,
:manage_files,
:browse_repository,
@@ -85,6 +86,7 @@ module Redmine
:view_wiki_pages,
:view_wiki_edits,
:add_messages,
+ :edit_own_messages,
:view_files,
:browse_repository,
:view_changesets]
diff --git a/lib/redmine/export/pdf.rb b/lib/redmine/export/pdf.rb
new file mode 100644
index 000000000..5918b6d37
--- /dev/null
+++ b/lib/redmine/export/pdf.rb
@@ -0,0 +1,462 @@
+# Redmine - project management software
+# Copyright (C) 2006-2009 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require 'iconv'
+require 'rfpdf/fpdf'
+require 'rfpdf/chinese'
+
+module Redmine
+ module Export
+ module PDF
+ include ActionView::Helpers::NumberHelper
+
+ class IFPDF < FPDF
+ include GLoc
+ attr_accessor :footer_date
+
+ def initialize(lang)
+ super()
+ set_language_if_valid lang
+ case current_language.to_s
+ when 'ja'
+ extend(PDF_Japanese)
+ AddSJISFont()
+ @font_for_content = 'SJIS'
+ @font_for_footer = 'SJIS'
+ when 'zh'
+ extend(PDF_Chinese)
+ AddGBFont()
+ @font_for_content = 'GB'
+ @font_for_footer = 'GB'
+ when 'zh-tw'
+ extend(PDF_Chinese)
+ AddBig5Font()
+ @font_for_content = 'Big5'
+ @font_for_footer = 'Big5'
+ else
+ @font_for_content = 'Arial'
+ @font_for_footer = 'Helvetica'
+ end
+ SetCreator(Redmine::Info.app_name)
+ SetFont(@font_for_content)
+ end
+
+ def SetFontStyle(style, size)
+ SetFont(@font_for_content, style, size)
+ end
+
+ def SetTitle(txt)
+ txt = begin
+ utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
+ hextxt = "<FEFF" # FEFF is BOM
+ hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
+ hextxt << ">"
+ rescue
+ txt
+ end || ''
+ super(txt)
+ end
+
+ def textstring(s)
+ # Format a text string
+ if s =~ /^</ # This means the string is hex-dumped.
+ return s
+ else
+ return '('+escape(s)+')'
+ end
+ end
+
+ def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
+ @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
+ # these quotation marks are not correctly rendered in the pdf
+ txt = txt.gsub(/[“�]/, '"') if txt
+ txt = begin
+ # 0x5c char handling
+ txtar = txt.split('\\')
+ txtar << '' if txt[-1] == ?\\
+ txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\")
+ rescue
+ txt
+ end || ''
+ super w,h,txt,border,ln,align,fill,link
+ end
+
+ def Footer
+ SetFont(@font_for_footer, 'I', 8)
+ SetY(-15)
+ SetX(15)
+ Cell(0, 5, @footer_date, 0, 0, 'L')
+ SetY(-15)
+ SetX(-30)
+ Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
+ end
+ end
+
+ # Returns a PDF string of a list of issues
+ def issues_to_pdf(issues, project)
+ pdf = IFPDF.new(current_language)
+ title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
+ pdf.SetTitle(title)
+ pdf.AliasNbPages
+ pdf.footer_date = format_date(Date.today)
+ pdf.AddPage("L")
+ row_height = 7
+
+ # title
+ pdf.SetFontStyle('B',11)
+ pdf.Cell(190,10, title)
+ pdf.Ln
+
+ # headers
+ pdf.SetFontStyle('B',10)
+ pdf.SetFillColor(230, 230, 230)
+ pdf.Cell(15, row_height, "#", 0, 0, 'L', 1)
+ pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1)
+ pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1)
+ pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1)
+ pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1)
+ pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1)
+ pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1)
+ pdf.Line(10, pdf.GetY, 287, pdf.GetY)
+ pdf.Ln
+ pdf.Line(10, pdf.GetY, 287, pdf.GetY)
+ pdf.SetY(pdf.GetY() + 1)
+
+ # rows
+ pdf.SetFontStyle('',9)
+ pdf.SetFillColor(255, 255, 255)
+ issues.each do |issue|
+ pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1)
+ pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1)
+ pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1)
+ pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1)
+ pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1)
+ pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1)
+ pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}"))
+ pdf.Line(10, pdf.GetY, 287, pdf.GetY)
+ pdf.SetY(pdf.GetY() + 1)
+ end
+ pdf.Output
+ end
+
+ # Returns a PDF string of a single issue
+ def issue_to_pdf(issue)
+ pdf = IFPDF.new(current_language)
+ pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
+ pdf.AliasNbPages
+ pdf.footer_date = format_date(Date.today)
+ pdf.AddPage
+
+ 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.to_s,"RT")
+ pdf.SetFontStyle('B',9)
+ pdf.Cell(35,5, l(:field_priority) + ":","LT")
+ pdf.SetFontStyle('',9)
+ pdf.Cell(60,5, issue.priority.to_s,"RT")
+ pdf.Ln
+
+ pdf.SetFontStyle('B',9)
+ pdf.Cell(35,5, l(:field_author) + ":","L")
+ pdf.SetFontStyle('',9)
+ pdf.Cell(60,5, issue.author.to_s,"R")
+ pdf.SetFontStyle('B',9)
+ pdf.Cell(35,5, l(:field_category) + ":","L")
+ pdf.SetFontStyle('',9)
+ pdf.Cell(60,5, issue.category.to_s,"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.to_s,"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.author.to_s)
+ 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
+ end
+
+ # Returns a PDF string of a gantt chart
+ def gantt_to_pdf(gantt, project)
+ pdf = IFPDF.new(current_language)
+ pdf.SetTitle("#{l(:label_gantt)} #{project}")
+ pdf.AliasNbPages
+ pdf.footer_date = format_date(Date.today)
+ pdf.AddPage("L")
+ pdf.SetFontStyle('B',12)
+ pdf.SetX(15)
+ pdf.Cell(70, 20, project.to_s)
+ pdf.Ln
+ pdf.SetFontStyle('B',9)
+
+ subject_width = 70
+ header_heigth = 5
+
+ headers_heigth = header_heigth
+ show_weeks = false
+ show_days = false
+
+ if gantt.months < 7
+ show_weeks = true
+ headers_heigth = 2*header_heigth
+ if gantt.months < 3
+ show_days = true
+ headers_heigth = 3*header_heigth
+ end
+ end
+
+ g_width = 210
+ zoom = (g_width) / (gantt.date_to - gantt.date_from + 1)
+ g_height = 120
+ t_height = g_height + headers_heigth
+
+ y_start = pdf.GetY
+
+ # Months headers
+ month_f = gantt.date_from
+ left = subject_width
+ height = header_heigth
+ gantt.months.times do
+ width = ((month_f >> 1) - month_f) * zoom
+ pdf.SetY(y_start)
+ pdf.SetX(left)
+ pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
+ left = left + width
+ month_f = month_f >> 1
+ end
+
+ # Weeks headers
+ if show_weeks
+ left = subject_width
+ height = header_heigth
+ if gantt.date_from.cwday == 1
+ # gantt.date_from is monday
+ week_f = gantt.date_from
+ else
+ # find next monday after gantt.date_from
+ week_f = gantt.date_from + (7 - gantt.date_from.cwday + 1)
+ width = (7 - gantt.date_from.cwday + 1) * zoom-1
+ pdf.SetY(y_start + header_heigth)
+ pdf.SetX(left)
+ pdf.Cell(width + 1, height, "", "LTR")
+ left = left + width+1
+ end
+ while week_f <= gantt.date_to
+ width = (week_f + 6 <= gantt.date_to) ? 7 * zoom : (gantt.date_to - week_f + 1) * zoom
+ pdf.SetY(y_start + header_heigth)
+ pdf.SetX(left)
+ pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
+ left = left + width
+ week_f = week_f+7
+ end
+ end
+
+ # Days headers
+ if show_days
+ left = subject_width
+ height = header_heigth
+ wday = gantt.date_from.cwday
+ pdf.SetFontStyle('B',7)
+ (gantt.date_to - gantt.date_from + 1).to_i.times do
+ width = zoom
+ pdf.SetY(y_start + 2 * header_heigth)
+ pdf.SetX(left)
+ pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
+ left = left + width
+ wday = wday + 1
+ wday = 1 if wday > 7
+ end
+ end
+
+ pdf.SetY(y_start)
+ pdf.SetX(15)
+ pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
+
+ # Tasks
+ top = headers_heigth + y_start
+ pdf.SetFontStyle('B',7)
+ gantt.events.each do |i|
+ pdf.SetY(top)
+ pdf.SetX(15)
+
+ if i.is_a? Issue
+ pdf.Cell(subject_width-15, 5, "#{i.tracker} #{i.id}: #{i.subject}".sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)'), "LR")
+ else
+ pdf.Cell(subject_width-15, 5, "#{l(:label_version)}: #{i.name}", "LR")
+ end
+
+ pdf.SetY(top)
+ pdf.SetX(subject_width)
+ pdf.Cell(g_width, 5, "", "LR")
+
+ pdf.SetY(top+1.5)
+
+ if i.is_a? Issue
+ i_start_date = (i.start_date >= gantt.date_from ? i.start_date : gantt.date_from )
+ i_end_date = (i.due_before <= gantt.date_to ? i.due_before : gantt.date_to )
+
+ i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
+ i_done_date = (i_done_date <= gantt.date_from ? gantt.date_from : i_done_date )
+ i_done_date = (i_done_date >= gantt.date_to ? gantt.date_to : i_done_date )
+
+ i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
+
+ i_left = ((i_start_date - gantt.date_from)*zoom)
+ i_width = ((i_end_date - i_start_date + 1)*zoom)
+ d_width = ((i_done_date - i_start_date)*zoom)
+ l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date
+ l_width ||= 0
+
+ pdf.SetX(subject_width + i_left)
+ pdf.SetFillColor(200,200,200)
+ pdf.Cell(i_width, 2, "", 0, 0, "", 1)
+
+ if l_width > 0
+ pdf.SetY(top+1.5)
+ pdf.SetX(subject_width + i_left)
+ pdf.SetFillColor(255,100,100)
+ pdf.Cell(l_width, 2, "", 0, 0, "", 1)
+ end
+ if d_width > 0
+ pdf.SetY(top+1.5)
+ pdf.SetX(subject_width + i_left)
+ pdf.SetFillColor(100,100,255)
+ pdf.Cell(d_width, 2, "", 0, 0, "", 1)
+ end
+
+ pdf.SetY(top+1.5)
+ pdf.SetX(subject_width + i_left + i_width)
+ pdf.Cell(30, 2, "#{i.status} #{i.done_ratio}%")
+ else
+ i_left = ((i.start_date - gantt.date_from)*zoom)
+
+ pdf.SetX(subject_width + i_left)
+ pdf.SetFillColor(50,200,50)
+ pdf.Cell(2, 2, "", 0, 0, "", 1)
+
+ pdf.SetY(top+1.5)
+ pdf.SetX(subject_width + i_left + 3)
+ pdf.Cell(30, 2, "#{i.name}")
+ end
+
+ top = top + 5
+ pdf.SetDrawColor(200, 200, 200)
+ pdf.Line(15, top, subject_width+g_width, top)
+ if pdf.GetY() > 180
+ pdf.AddPage("L")
+ top = 20
+ pdf.Line(15, top, subject_width+g_width, top)
+ end
+ pdf.SetDrawColor(0, 0, 0)
+ end
+
+ pdf.Line(15, top, subject_width+g_width, top)
+ pdf.Output
+ end
+ end
+ end
+end
diff --git a/lib/redmine/hook.rb b/lib/redmine/hook.rb
index 9bf92b3e7..bcd23b64f 100644
--- a/lib/redmine/hook.rb
+++ b/lib/redmine/hook.rb
@@ -17,6 +17,8 @@
module Redmine
module Hook
+ include ActionController::UrlWriter
+
@@listener_classes = []
@@listeners = nil
@@hook_listeners = {}
@@ -55,17 +57,29 @@ module Redmine
# Calls a hook.
# Returns the listeners response.
def call_hook(hook, context={})
- response = ''
- hook_listeners(hook).each do |listener|
- response << listener.send(hook, context).to_s
+ returning [] do |response|
+ hls = hook_listeners(hook)
+ if hls.any?
+ request = context[:request]
+ if request
+ default_url_options[:host] ||= request.env["SERVER_NAME"]
+ # Only set port if it's requested and isn't port 80. Otherwise a url
+ # like: +http://example.com:/url+ may be generated
+ if request.env["SERVER_PORT"] && request.env["SERVER_PORT"] != 80
+ default_url_options[:port] ||= request.env["SERVER_PORT"]
+ end
+ default_url_options[:protocol] ||= request.protocol
+ end
+ hls.each {|listener| response << listener.send(hook, context)}
+ end
end
- response
end
end
# Base class for hook listeners.
class Listener
include Singleton
+ include GLoc
# Registers the listener
def self.inherited(child)
@@ -90,20 +104,52 @@ module Redmine
include ActionView::Helpers::TextHelper
include ActionController::UrlWriter
include ApplicationHelper
+
+ # Helper method to directly render a partial using the context:
+ #
+ # class MyHook < Redmine::Hook::ViewListener
+ # render_on :view_issues_show_details_bottom, :partial => "show_more_data"
+ # end
+ #
+ def self.render_on(hook, options={})
+ define_method hook do |context|
+ context[:controller].send(:render_to_string, {:locals => context}.merge(options))
+ end
+ end
end
- # Helper module included in ApplicationHelper so that hooks can be called
- # in views like this:
+ # Helper module included in ApplicationHelper and ActionControllerso that
+ # hooks can be called in views like this:
+ #
# <%= call_hook(:some_hook) %>
# <%= call_hook(:another_hook, :foo => 'bar' %>
#
- # Current project is automatically added to the call context.
+ # Or in controllers like:
+ # call_hook(:some_hook)
+ # call_hook(:another_hook, :foo => 'bar'
+ #
+ # Hooks added to views will be concatenated into a string. Hooks added to
+ # controllers will return an array of results.
+ #
+ # Several objects are automatically added to the call context:
+ #
+ # * project => current project
+ # * request => Request instance
+ # * controller => current Controller instance
+ #
module Helper
def call_hook(hook, context={})
- Redmine::Hook.call_hook(hook, {:project => @project}.merge(context))
+ if is_a?(ActionController::Base)
+ default_context = {:controller => self, :project => @project, :request => request}
+ Redmine::Hook.call_hook(hook, default_context.merge(context))
+ else
+ default_context = {:controller => controller, :project => @project, :request => request}
+ Redmine::Hook.call_hook(hook, default_context.merge(context)).join(' ')
+ end
end
end
end
end
ApplicationHelper.send(:include, Redmine::Hook::Helper)
+ActionController::Base.send(:include, Redmine::Hook::Helper)
diff --git a/lib/redmine/imap.rb b/lib/redmine/imap.rb
index a6cd958cd..383d82f23 100644
--- a/lib/redmine/imap.rb
+++ b/lib/redmine/imap.rb
@@ -1,4 +1,4 @@
-# redMine - project management software
+# Redmine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
@@ -33,9 +33,18 @@ module Redmine
msg = imap.fetch(message_id,'RFC822')[0].attr['RFC822']
logger.debug "Receiving message #{message_id}" if logger && logger.debug?
if MailHandler.receive(msg, options)
+ logger.debug "Message #{message_id} successfully received" if logger && logger.debug?
+ if imap_options[:move_on_success]
+ imap.copy(message_id, imap_options[:move_on_success])
+ end
imap.store(message_id, "+FLAGS", [:Seen, :Deleted])
else
+ logger.debug "Message #{message_id} can not be processed" if logger && logger.debug?
imap.store(message_id, "+FLAGS", [:Seen])
+ if imap_options[:move_on_failure]
+ imap.copy(message_id, imap_options[:move_on_failure])
+ imap.store(message_id, "+FLAGS", [:Deleted])
+ end
end
end
imap.expunge
diff --git a/lib/redmine/menu_manager.rb b/lib/redmine/menu_manager.rb
index f6431928e..7a89a32b9 100644
--- a/lib/redmine/menu_manager.rb
+++ b/lib/redmine/menu_manager.rb
@@ -52,8 +52,19 @@ module Redmine
# Returns the menu item name according to the current action
def current_menu_item
- menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
- menu_items[controller_name.to_sym][:default]
+ @current_menu_item ||= menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
+ menu_items[controller_name.to_sym][:default]
+ end
+
+ # Redirects user to the menu item of the given project
+ # Returns false if user is not authorized
+ def redirect_to_project_menu_item(project, name)
+ item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name.to_s == name.to_s}
+ if item && User.current.allowed_to?(item.url, project) && (item.condition.nil? || item.condition.call(project))
+ redirect_to({item.param => project}.merge(item.url))
+ return true
+ end
+ false
end
end
@@ -70,6 +81,15 @@ module Redmine
def render_menu(menu, project=nil)
links = []
+ menu_items_for(menu, project) do |item, caption, url, selected|
+ links << content_tag('li',
+ link_to(h(caption), url, item.html_options(:selected => selected)))
+ end
+ links.empty? ? nil : content_tag('ul', links.join("\n"))
+ end
+
+ def menu_items_for(menu, project=nil)
+ items = []
Redmine::MenuManager.allowed_items(menu, User.current, project).each do |item|
unless item.condition && !item.condition.call(project)
url = case item.url
@@ -82,11 +102,14 @@ module Redmine
end
caption = item.caption(project)
caption = l(caption) if caption.is_a?(Symbol)
- links << content_tag('li',
- link_to(h(caption), url, (current_menu_item == item.name ? item.html_options.merge(:class => 'selected') : item.html_options)))
+ if block_given?
+ yield item, caption, url, (current_menu_item == item.name)
+ else
+ items << [item, caption, url, (current_menu_item == item.name)]
+ end
end
end
- links.empty? ? nil : content_tag('ul', links.join("\n"))
+ return block_given? ? nil : items
end
end
@@ -94,7 +117,11 @@ module Redmine
def map(menu_name)
@items ||= {}
mapper = Mapper.new(menu_name.to_sym, @items)
- yield mapper
+ if block_given?
+ yield mapper
+ else
+ mapper
+ end
end
def items(menu_name)
@@ -152,7 +179,7 @@ module Redmine
class MenuItem
include GLoc
- attr_reader :name, :url, :param, :condition, :html_options
+ attr_reader :name, :url, :param, :condition
def initialize(name, url, options)
raise "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
@@ -163,6 +190,8 @@ module Redmine
@param = options[:param] || :id
@caption = options[:caption]
@html_options = options[:html] || {}
+ # Adds a unique class to each menu item based on its name
+ @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
end
def caption(project=nil)
@@ -175,6 +204,16 @@ module Redmine
@caption_key ||= (@caption || (l_has_string?("label_#{@name}".to_sym) ? "label_#{@name}".to_sym : @name.to_s.humanize))
end
end
+
+ def html_options(options={})
+ if options[:selected]
+ o = @html_options.dup
+ o[:class] += ' selected'
+ o
+ else
+ @html_options
+ end
+ end
end
end
end
diff --git a/lib/redmine/plugin.rb b/lib/redmine/plugin.rb
index cf6c194a2..0fc6985f4 100644
--- a/lib/redmine/plugin.rb
+++ b/lib/redmine/plugin.rb
@@ -17,6 +17,9 @@
module Redmine #:nodoc:
+ class PluginNotFound < StandardError; end
+ class PluginRequirementError < StandardError; end
+
# Base class for Redmine plugins.
# Plugins are registered using the <tt>register</tt> class method that acts as the public constructor.
#
@@ -55,13 +58,75 @@ module Redmine #:nodoc:
end
end
end
- def_field :name, :description, :author, :version, :settings
-
+ def_field :name, :description, :url, :author, :author_url, :version, :settings
+ attr_reader :id
+
# Plugin constructor
- def self.register(name, &block)
- p = new
+ def self.register(id, &block)
+ p = new(id)
p.instance_eval(&block)
- Plugin.registered_plugins[name] = p
+ # Set a default name if it was not provided during registration
+ p.name(id.to_s.humanize) if p.name.nil?
+ registered_plugins[id] = p
+ end
+
+ # Returns an array off all registered plugins
+ def self.all
+ registered_plugins.values.sort
+ end
+
+ # Finds a plugin by its id
+ # Returns a PluginNotFound exception if the plugin doesn't exist
+ def self.find(id)
+ registered_plugins[id.to_sym] || raise(PluginNotFound)
+ end
+
+ # Clears the registered plugins hash
+ # It doesn't unload installed plugins
+ def self.clear
+ @registered_plugins = {}
+ end
+
+ def initialize(id)
+ @id = id.to_sym
+ end
+
+ def <=>(plugin)
+ self.id.to_s <=> plugin.id.to_s
+ end
+
+ # Sets a requirement on Redmine version
+ # Raises a PluginRequirementError exception if the requirement is not met
+ #
+ # Examples
+ # # Requires Redmine 0.7.3 or higher
+ # requires_redmine :version_or_higher => '0.7.3'
+ # requires_redmine '0.7.3'
+ #
+ # # Requires a specific Redmine version
+ # requires_redmine :version => '0.7.3' # 0.7.3 only
+ # requires_redmine :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0
+ def requires_redmine(arg)
+ arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
+ arg.assert_valid_keys(:version, :version_or_higher)
+
+ current = Redmine::VERSION.to_a
+ arg.each do |k, v|
+ v = [] << v unless v.is_a?(Array)
+ versions = v.collect {|s| s.split('.').collect(&:to_i)}
+ case k
+ when :version_or_higher
+ raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1
+ unless (current <=> versions.first) >= 0
+ raise PluginRequirementError.new("#{id} plugin requires Redmine #{v} or higher but current is #{current.join('.')}")
+ end
+ when :version
+ unless versions.include?(current.slice(0,3))
+ raise PluginRequirementError.new("#{id} plugin requires one the following Redmine versions: #{v.join(', ')} but current is #{current.join('.')}")
+ end
+ end
+ end
+ true
end
# Adds an item to the given +menu+.
@@ -70,8 +135,14 @@ module Redmine #:nodoc:
#
# +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu
#
- def menu(name, item, url, options={})
- Redmine::MenuManager.map(name) {|menu| menu.push item, url, options}
+ def menu(menu, item, url, options={})
+ Redmine::MenuManager.map(menu).push(item, url, options)
+ end
+ alias :add_menu_item :menu
+
+ # Removes +item+ from the given +menu+.
+ def delete_menu_item(menu, item)
+ Redmine::MenuManager.map(menu).delete(item)
end
# Defines a permission called +name+ for the given +actions+.
@@ -142,6 +213,16 @@ module Redmine #:nodoc:
def activity_provider(*args)
Redmine::Activity.register(*args)
end
+
+ # Registers a wiki formatter.
+ #
+ # Parameters:
+ # * +name+ - human-readable name
+ # * +formatter+ - formatter class, which should have an instance method +to_html+
+ # * +helper+ - helper module, which will be included by wiki pages
+ def wiki_format_provider(name, formatter, helper)
+ Redmine::WikiFormatting.register(name, formatter, helper)
+ end
# Returns +true+ if the plugin can be configured.
def configurable?
diff --git a/lib/redmine/scm/adapters/abstract_adapter.rb b/lib/redmine/scm/adapters/abstract_adapter.rb
index 7e09d1611..cb8e7674e 100644
--- a/lib/redmine/scm/adapters/abstract_adapter.rb
+++ b/lib/redmine/scm/adapters/abstract_adapter.rb
@@ -192,6 +192,10 @@ module Redmine
def self.shellout(cmd, &block)
logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
+ if Rails.env == 'development'
+ # Capture stderr when running in dev environment
+ cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log"
+ end
begin
IO.popen(cmd, "r+") do |io|
io.close_write
diff --git a/lib/redmine/scm/adapters/bazaar_adapter.rb b/lib/redmine/scm/adapters/bazaar_adapter.rb
index ff69e3e6b..39afb0565 100644
--- a/lib/redmine/scm/adapters/bazaar_adapter.rb
+++ b/lib/redmine/scm/adapters/bazaar_adapter.rb
@@ -50,7 +50,8 @@ module Redmine
path ||= ''
entries = Entries.new
cmd = "#{BZR_BIN} ls -v --show-ids"
- cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
+ identifier = -1 unless identifier && identifier.to_i > 0
+ cmd << " -r#{identifier.to_i}"
cmd << " #{target(path)}"
shellout(cmd) do |io|
prefix = "#{url}/#{path}".gsub('\\', '/')
diff --git a/lib/redmine/scm/adapters/cvs_adapter.rb b/lib/redmine/scm/adapters/cvs_adapter.rb
index 089a6b153..fc8d56f83 100644
--- a/lib/redmine/scm/adapters/cvs_adapter.rb
+++ b/lib/redmine/scm/adapters/cvs_adapter.rb
@@ -63,7 +63,7 @@ module Redmine
logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
path_with_project="#{url}#{with_leading_slash(path)}"
entries = Entries.new
- cmd = "#{CVS_BIN} -d #{root_url} rls -ed"
+ cmd = "#{CVS_BIN} -d #{root_url} rls -e"
cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
cmd << " #{shell_quote path_with_project}"
shellout(cmd) do |io|
@@ -133,8 +133,7 @@ module Redmine
if state=="entry_start"
branch_map=Hash.new
- # gsub(/^:.*@[^:]+:\d*/, '') is here to remove :pserver:anonymous@foo.bar: string if present in the url
- if /^RCS file: #{Regexp.escape(root_url.gsub(/^:.*@[^:]+:\d*/, ''))}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
+ if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
entry_path = normalize_cvs_path($1)
entry_name = normalize_path(File.basename($1))
logger.debug("Path #{entry_path} <=> Name #{entry_name}")
@@ -273,6 +272,13 @@ module Redmine
end
private
+
+ # Returns the root url without the connexion string
+ # :pserver:anonymous@foo.bar:/path => /path
+ # :ext:cvsservername:/path => /path
+ def root_url_path
+ root_url.to_s.gsub(/^:.+:\d*/, '')
+ end
# convert a date/time into the CVS-format
def time_to_cvstime(time)
diff --git a/lib/redmine/scm/adapters/darcs_adapter.rb b/lib/redmine/scm/adapters/darcs_adapter.rb
index 4a5183f79..1cf792fb8 100644
--- a/lib/redmine/scm/adapters/darcs_adapter.rb
+++ b/lib/redmine/scm/adapters/darcs_adapter.rb
@@ -67,8 +67,8 @@ module Redmine
path = '.' if path.blank?
entries = Entries.new
cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output"
- cmd << " --match \"hash #{identifier}\"" if identifier
- cmd << " #{path}"
+ cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
+ cmd << " #{shell_quote path}"
shellout(cmd) do |io|
begin
doc = REXML::Document.new(io)
@@ -84,14 +84,14 @@ module Redmine
end
end
return nil if $? && $?.exitstatus != 0
- entries.sort_by_name
+ entries.compact.sort_by_name
end
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path = '.' if path.blank?
revisions = Revisions.new
cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
- cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from
+ cmd << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from
cmd << " --last #{options[:limit].to_i}" if options[:limit]
shellout(cmd) do |io|
begin
@@ -118,12 +118,12 @@ module Redmine
path = '*' if path.blank?
cmd = "#{DARCS_BIN} diff --repodir #{@url}"
if identifier_to.nil?
- cmd << " --match \"hash #{identifier_from}\""
+ cmd << " --match #{shell_quote("hash #{identifier_from}")}"
else
- cmd << " --to-match \"hash #{identifier_from}\""
- cmd << " --from-match \"hash #{identifier_to}\""
+ cmd << " --to-match #{shell_quote("hash #{identifier_from}")}"
+ cmd << " --from-match #{shell_quote("hash #{identifier_to}")}"
end
- cmd << " -u #{path}"
+ cmd << " -u #{shell_quote path}"
diff = []
shellout(cmd) do |io|
io.each_line do |line|
@@ -136,7 +136,7 @@ module Redmine
def cat(path, identifier=nil)
cmd = "#{DARCS_BIN} show content --repodir #{@url}"
- cmd << " --match \"hash #{identifier}\"" if identifier
+ cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
cmd << " #{shell_quote path}"
cat = nil
shellout(cmd) do |io|
@@ -148,15 +148,22 @@ module Redmine
end
private
-
+
+ # Returns an Entry from the given XML element
+ # or nil if the entry was deleted
def entry_from_xml(element, path_prefix)
+ modified_element = element.elements['modified']
+ if modified_element.elements['modified_how'].text.match(/removed/)
+ return nil
+ end
+
Entry.new({:name => element.attributes['name'],
:path => path_prefix + element.attributes['name'],
:kind => element.name == 'file' ? 'file' : 'dir',
:size => nil,
:lastrev => Revision.new({
:identifier => nil,
- :scmid => element.elements['modified'].elements['patch'].attributes['hash']
+ :scmid => modified_element.elements['patch'].attributes['hash']
})
})
end
@@ -164,7 +171,7 @@ module Redmine
# Retrieve changed paths for a single patch
def get_paths_for_patch(hash)
cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
- cmd << " --match \"hash #{hash}\" "
+ cmd << " --match #{shell_quote("hash #{hash}")} "
paths = []
shellout(cmd) do |io|
begin
diff --git a/lib/redmine/scm/adapters/git_adapter.rb b/lib/redmine/scm/adapters/git_adapter.rb
index c1f3e335b..85418133f 100644
--- a/lib/redmine/scm/adapters/git_adapter.rb
+++ b/lib/redmine/scm/adapters/git_adapter.rb
@@ -34,10 +34,10 @@ module Redmine
def get_rev (rev,path)
if rev != 'latest' && !rev.nil?
- cmd="#{GIT_BIN} --git-dir #{target('')} show #{shell_quote rev} -- #{shell_quote path}"
+ cmd="#{GIT_BIN} --git-dir #{target('')} show --date=iso --pretty=fuller #{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}"
+ @branch ||= shellout("#{GIT_BIN} --git-dir #{target('')} branch") { |io| io.grep(/\*/)[0].strip.match(/\* (.*)/)[1] }
+ cmd="#{GIT_BIN} --git-dir #{target('')} log --date=iso --pretty=fuller -1 #{@branch} -- #{shell_quote path}"
end
rev=[]
i=0
@@ -68,7 +68,7 @@ module Redmine
value = $2
if key == "Author"
changeset[:author] = value
- elsif key == "Date"
+ elsif key == "CommitDate"
changeset[:date] = value
end
elsif (parsing_descr == 0) && line.chomp.to_s == ""
@@ -101,7 +101,6 @@ module Redmine
return rev
end
-
def info
revs = revisions(url,nil,nil,{:limit => 1})
if revs && revs.any?
@@ -143,7 +142,7 @@ module Redmine
def revisions(path, identifier_from, identifier_to, options={})
revisions = Revisions.new
- cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw "
+ cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw --date=iso --pretty=fuller"
cmd << " --reverse" if options[:reverse]
cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit]
cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
@@ -182,7 +181,7 @@ module Redmine
value = $2
if key == "Author"
changeset[:author] = value
- elsif key == "Date"
+ elsif key == "CommitDate"
changeset[:date] = value
end
elsif (parsing_descr == 0) && line.chomp.to_s == ""
diff --git a/lib/redmine/scm/adapters/subversion_adapter.rb b/lib/redmine/scm/adapters/subversion_adapter.rb
index bd04f582b..e59022582 100644
--- a/lib/redmine/scm/adapters/subversion_adapter.rb
+++ b/lib/redmine/scm/adapters/subversion_adapter.rb
@@ -17,6 +17,7 @@
require 'redmine/scm/adapters/abstract_adapter'
require 'rexml/document'
+require 'uri'
module Redmine
module Scm
@@ -81,24 +82,27 @@ module Redmine
path ||= ''
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
entries = Entries.new
- cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
+ cmd = "#{SVN_BIN} list --xml #{target(URI.escape(path))}@#{identifier}"
cmd << credentials_string
shellout(cmd) do |io|
output = io.read
begin
doc = REXML::Document.new(output)
doc.elements.each("lists/list/entry") do |entry|
+ commit = entry.elements['commit']
+ commit_date = commit.elements['date']
# 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),
+ next if entry.attributes['kind'] == 'dir' && commit_date.nil?
+ name = entry.elements['name'].text
+ entries << Entry.new({:name => URI.unescape(name),
+ :path => ((path.empty? ? "" : "#{path}/") + name),
:kind => entry.attributes['kind'],
- :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
+ :size => ((s = entry.elements['size']) ? s.text.to_i : nil),
:lastrev => Revision.new({
- :identifier => entry.elements['commit'].attributes['revision'],
- :time => Time.parse(entry.elements['commit'].elements['date'].text).localtime,
- :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "")
+ :identifier => commit.attributes['revision'],
+ :time => Time.parse(commit_date.text).localtime,
+ :author => ((a = commit.elements['author']) ? a.text : nil)
})
})
end
@@ -117,7 +121,7 @@ module Redmine
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 = "#{SVN_BIN} proplist --verbose --xml #{target(URI.escape(path))}@#{identifier}"
cmd << credentials_string
properties = {}
shellout(cmd) do |io|
@@ -142,7 +146,8 @@ module Redmine
cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
cmd << credentials_string
cmd << " --verbose " if options[:with_paths]
- cmd << ' ' + target(path)
+ cmd << " --limit #{options[:limit].to_i}" if options[:limit]
+ cmd << ' ' + target(URI.escape(path))
shellout(cmd) do |io|
begin
doc = REXML::Document.new(io)
@@ -179,7 +184,7 @@ module Redmine
cmd = "#{SVN_BIN} diff -r "
cmd << "#{identifier_to}:"
cmd << "#{identifier_from}"
- cmd << " #{target(path)}@#{identifier_from}"
+ cmd << " #{target(URI.escape(path))}@#{identifier_from}"
cmd << credentials_string
diff = []
shellout(cmd) do |io|
@@ -193,7 +198,7 @@ module Redmine
def cat(path, identifier=nil)
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
- cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
+ cmd = "#{SVN_BIN} cat #{target(URI.escape(path))}@#{identifier}"
cmd << credentials_string
cat = nil
shellout(cmd) do |io|
@@ -206,7 +211,7 @@ module Redmine
def annotate(path, identifier=nil)
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
- cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
+ cmd = "#{SVN_BIN} blame #{target(URI.escape(path))}@#{identifier}"
cmd << credentials_string
blame = Annotate.new
shellout(cmd) do |io|
diff --git a/lib/redmine/unified_diff.rb b/lib/redmine/unified_diff.rb
index 36a36cba5..bf4dec335 100644
--- a/lib/redmine/unified_diff.rb
+++ b/lib/redmine/unified_diff.rb
@@ -18,18 +18,30 @@
module Redmine
# Class used to parse unified diffs
class UnifiedDiff < Array
- def initialize(diff, type="inline")
- diff_table = DiffTable.new type
+ def initialize(diff, options={})
+ options.assert_valid_keys(:type, :max_lines)
+ diff_type = options[:type] || 'inline'
+
+ lines = 0
+ @truncated = false
+ diff_table = DiffTable.new(diff_type)
diff.each do |line|
if line =~ /^(---|\+\+\+) (.*)$/
self << diff_table if diff_table.length > 1
- diff_table = DiffTable.new type
+ diff_table = DiffTable.new(diff_type)
+ end
+ diff_table.add_line line
+ lines += 1
+ if options[:max_lines] && lines > options[:max_lines]
+ @truncated = true
+ break
end
- a = diff_table.add_line line
end
self << diff_table unless diff_table.empty?
self
end
+
+ def truncated?; @truncated; end
end
# Class that represents a file diff
diff --git a/lib/redmine/utils.rb b/lib/redmine/utils.rb
new file mode 100644
index 000000000..02f9d3e63
--- /dev/null
+++ b/lib/redmine/utils.rb
@@ -0,0 +1,38 @@
+# Redmine - project management software
+# Copyright (C) 2006-2009 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module Redmine
+ module Utils
+ class << self
+ # Returns the relative root url of the application
+ def relative_url_root
+ ActionController::Base.respond_to?('relative_url_root') ?
+ ActionController::Base.relative_url_root.to_s :
+ ActionController::AbstractRequest.relative_url_root.to_s
+ end
+
+ # Sets the relative root url of the application
+ def relative_url_root=(arg)
+ if ActionController::Base.respond_to?('relative_url_root=')
+ ActionController::Base.relative_url_root=arg
+ else
+ ActionController::AbstractRequest.relative_url_root=arg
+ end
+ end
+ end
+ end
+end
diff --git a/lib/redmine/version.rb b/lib/redmine/version.rb
index 81006fe2d..f51d1741e 100644
--- a/lib/redmine/version.rb
+++ b/lib/redmine/version.rb
@@ -3,8 +3,14 @@ require 'rexml/document'
module Redmine
module VERSION #:nodoc:
MAJOR = 0
- MINOR = 7
- TINY = 'devel'
+ MINOR = 8
+ TINY = 0
+
+ # Branch values:
+ # * official release: nil
+ # * stable branch: stable
+ # * trunk: devel
+ BRANCH = 'devel'
def self.revision
revision = nil
@@ -28,8 +34,10 @@ module Redmine
end
REVISION = self.revision
- STRING = [MAJOR, MINOR, TINY, REVISION].compact.join('.')
+ ARRAY = [MAJOR, MINOR, TINY, BRANCH, REVISION].compact
+ STRING = ARRAY.join('.')
+ def self.to_a; ARRAY end
def self.to_s; STRING end
end
end
diff --git a/lib/redmine/views/other_formats_builder.rb b/lib/redmine/views/other_formats_builder.rb
new file mode 100644
index 000000000..c3c89b24d
--- /dev/null
+++ b/lib/redmine/views/other_formats_builder.rb
@@ -0,0 +1,33 @@
+# Redmine - project management software
+# Copyright (C) 2006-2009 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module Redmine
+ module Views
+ class OtherFormatsBuilder
+ def initialize(view)
+ @view = view
+ end
+
+ def link_to(name, options={})
+ url = { :format => name.to_s.downcase }.merge(options.delete(:url) || {})
+ caption = options.delete(:caption) || name
+ html_options = { :class => name.to_s.downcase, :rel => 'nofollow' }.merge(options)
+ @view.content_tag('span', @view.link_to(caption, url, html_options))
+ end
+ end
+ end
+end
diff --git a/lib/redmine/wiki_formatting.rb b/lib/redmine/wiki_formatting.rb
index 7dffff492..1525dbc19 100644
--- a/lib/redmine/wiki_formatting.rb
+++ b/lib/redmine/wiki_formatting.rb
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# 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
@@ -15,176 +15,65 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-require 'redcloth3'
-require 'coderay'
-
module Redmine
module WikiFormatting
-
- private
-
- class TextileFormatter < RedCloth3
-
- # auto_link rule after textile rules so that it doesn't break !image_url! tags
- RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros]
+ @@formatters = {}
+
+ class << self
+ def map
+ yield self
+ end
- def initialize(*args)
- super
- self.hard_breaks=true
- self.no_span_caps=true
+ def register(name, formatter, helper)
+ raise ArgumentError, "format name '#{name}' is already taken" if @@formatters[name]
+ @@formatters[name.to_sym] = {:formatter => formatter, :helper => helper}
end
- def to_html(*rules, &block)
- @toc = []
- @macros_runner = block
- super(*RULES).to_s
+ def formatter_for(name)
+ entry = @@formatters[name.to_sym]
+ (entry && entry[:formatter]) || Redmine::WikiFormatting::NullFormatter::Formatter
end
-
- private
-
- # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
- # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
- def hard_break( text )
- text.gsub!( /(.)\n(?!\n|\Z|>| *(>? *[#*=]+(\s|$)|[{|]))/, "\\1<br />\n" ) if hard_breaks
+
+ def helper_for(name)
+ entry = @@formatters[name.to_sym]
+ (entry && entry[:helper]) || Redmine::WikiFormatting::NullFormatter::Helper
end
- # Patch to add code highlighting support to RedCloth
- def smooth_offtags( text )
- unless @pre_list.empty?
- ## replace <pre> content
- text.gsub!(/<redpre#(\d+)>/) do
- content = @pre_list[$1.to_i]
- if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
- content = "<code class=\"#{$1} CodeRay\">" +
- CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline)
- end
- content
- end
- end
+ def format_names
+ @@formatters.keys.map
end
- # Patch to add 'table of content' support to RedCloth
- def textile_p_withtoc(tag, atts, cite, content)
- # removes wiki links from the item
- toc_item = content.gsub(/(\[\[|\]\])/, '')
- # removes styles
- # eg. %{color:red}Triggers% => Triggers
- toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1'
+ def to_html(format, text, options = {}, &block)
+ formatter_for(format).new(text).to_html(&block)
+ end
+ end
+
+ # Default formatter module
+ module NullFormatter
+ class Formatter
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::TextHelper
- # 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 + "<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a>"
+ def initialize(text)
+ @text = text
end
- textile_p(tag, atts, cite, content)
- end
-
- alias :textile_h1 :textile_p_withtoc
- alias :textile_h2 :textile_p_withtoc
- alias :textile_h3 :textile_p_withtoc
-
- def inline_toc(text)
- text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
- div_class = 'toc'
- div_class << ' right' if $1 == '>'
- div_class << ' left' if $1 == '<'
- out = "<ul class=\"#{div_class}\">"
- @toc.each do |heading|
- level, anchor, toc_item = heading
- out << "<li class=\"heading#{level}\"><a href=\"##{anchor}\">#{toc_item}</a></li>\n"
- end
- out << '</ul>'
- out
+
+ def to_html(*args)
+ simple_format(auto_link(CGI::escapeHTML(@text)))
end
end
- MACROS_RE = /
- (!)? # escaping
- (
- \{\{ # opening tag
- ([\w]+) # macro name
- (\(([^\}]*)\))? # optional arguments
- \}\} # closing tag
- )
- /x unless const_defined?(:MACROS_RE)
-
- def inline_macros(text)
- text.gsub!(MACROS_RE) do
- esc, all, macro = $1, $2, $3.downcase
- args = ($5 || '').split(',').each(&:strip)
- if esc.nil?
- begin
- @macros_runner.call(macro, args)
- rescue => e
- "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
- end || all
- else
- all
- end
+ module Helper
+ def wikitoolbar_for(field_id)
end
- end
- AUTO_LINK_RE = %r{
- ( # leading text
- <\w+.*?>| # leading HTML tag, or
- [^=<>!:'"/]| # leading punctuation, or
- ^ # beginning of line
- )
- (
- (?:https?://)| # protocol spec, or
- (?:ftp://)|
- (?:www\.) # www.*
- )
- (
- (\S+?) # url
- (\/)? # slash
- )
- ([^\w\=\/;\(\)]*?) # post
- (?=<|\s|$)
- }x unless const_defined?(:AUTO_LINK_RE)
-
- # Turns all urls into clickable links (code from Rails).
- def inline_auto_link(text)
- text.gsub!(AUTO_LINK_RE) do
- all, leading, proto, url, post = $&, $1, $2, $3, $6
- if leading =~ /<a\s/i || leading =~ /![<>=]?/
- # don't replace URL's that are already linked
- # and URL's prefixed with ! !> !< != (textile images)
- all
- else
- # Idea below : an URL with unbalanced parethesis and
- # ending by ')' is put into external parenthesis
- if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
- url=url[0..-2] # discard closing parenth from url
- post = ")"+post # add closing parenth to post
- end
- %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post})
- end
+ def heads_for_wiki_formatter
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
- mail = $1
- if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
- mail
- else
- %{<a href="mailto:#{mail}" class="email">#{mail}</a>}
- end
+
+ def initial_page_content(page)
+ page.pretty_title.to_s
end
end
end
-
- public
-
- def self.to_html(text, options = {}, &block)
- TextileFormatter.new(text).to_html(&block)
- end
end
end
diff --git a/lib/redmine/wiki_formatting/macros.rb b/lib/redmine/wiki_formatting/macros.rb
index adfc590e4..abc07b947 100644
--- a/lib/redmine/wiki_formatting/macros.rb
+++ b/lib/redmine/wiki_formatting/macros.rb
@@ -23,6 +23,15 @@ module Redmine
method_name = "macro_#{name}"
send(method_name, obj, args) if respond_to?(method_name)
end
+
+ def extract_macro_options(args, *keys)
+ options = {}
+ while args.last.to_s.strip =~ %r{^(.+)\=(.+)$} && keys.include?($1.downcase.to_sym)
+ options[$1.downcase.to_sym] = $2
+ args.pop
+ end
+ return [args, options]
+ end
end
@@available_macros = {}
@@ -77,24 +86,29 @@ module Redmine
content_tag('dl', out)
end
- desc "Displays a list of child pages."
+ desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" +
+ " !{{child_pages}} -- can be used from a wiki page only\n" +
+ " !{{child_pages(Foo)}} -- lists all children of page Foo\n" +
+ " !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo"
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)
+ args, options = extract_macro_options(args, :parent)
+ page = nil
+ if args.size > 0
+ page = Wiki.find_page(args.first.to_s, :project => @project)
+ elsif obj.is_a?(WikiContent)
+ page = obj.page
+ else
+ raise 'With no argument, this macro can be called from wiki pages only.'
+ end
+ raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
+ pages = ([page] + page.descendants).group_by(&:parent_id)
+ render_page_hierarchy(pages, options[:parent] ? page.parent_id : 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
- title = args.first.to_s
- if title =~ %r{^([^\:]+)\:(.*)$}
- project_identifier, title = $1, $2
- project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier)
- end
- raise 'Unknow project' unless project && User.current.allowed_to?(:view_wiki_pages, project)
- raise 'No wiki for this project' unless !project.wiki.nil?
- page = project.wiki.find_page(title)
- raise "Page #{args.first} doesn't exist" unless page && page.content
+ page = Wiki.find_page(args.first.to_s, :project => @project)
+ raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
@included_wiki_pages ||= []
raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title)
@included_wiki_pages << page.title
diff --git a/lib/redmine/wiki_formatting/textile/formatter.rb b/lib/redmine/wiki_formatting/textile/formatter.rb
new file mode 100644
index 000000000..67e3579df
--- /dev/null
+++ b/lib/redmine/wiki_formatting/textile/formatter.rb
@@ -0,0 +1,184 @@
+# 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 'redcloth3'
+require 'coderay'
+
+module Redmine
+ module WikiFormatting
+ module Textile
+ class Formatter < RedCloth3
+
+ # auto_link rule after textile rules so that it doesn't break !image_url! tags
+ RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros]
+
+ def initialize(*args)
+ super
+ self.hard_breaks=true
+ self.no_span_caps=true
+ self.filter_styles=true
+ end
+
+ def to_html(*rules, &block)
+ @toc = []
+ @macros_runner = block
+ super(*RULES).to_s
+ end
+
+ private
+
+ # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
+ # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
+ def hard_break( text )
+ text.gsub!( /(.)\n(?!\n|\Z|>| *(>? *[#*=]+(\s|$)|[{|]))/, "\\1<br />\n" ) if hard_breaks
+ end
+
+ # Patch to add code highlighting support to RedCloth
+ def smooth_offtags( text )
+ unless @pre_list.empty?
+ ## replace <pre> content
+ text.gsub!(/<redpre#(\d+)>/) do
+ content = @pre_list[$1.to_i]
+ if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
+ content = "<code class=\"#{$1} CodeRay\">" +
+ CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline)
+ end
+ content
+ end
+ end
+ end
+
+ # Patch to add 'table of content' support to RedCloth
+ def textile_p_withtoc(tag, atts, cite, content)
+ # removes wiki links from the item
+ toc_item = content.gsub(/(\[\[([^\]\|]*)(\|([^\]]*))?\]\])/) { $4 || $2 }
+ # 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 + "<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a>"
+ end
+ textile_p(tag, atts, cite, content)
+ end
+
+ alias :textile_h1 :textile_p_withtoc
+ alias :textile_h2 :textile_p_withtoc
+ alias :textile_h3 :textile_p_withtoc
+
+ def inline_toc(text)
+ text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
+ div_class = 'toc'
+ div_class << ' right' if $1 == '>'
+ div_class << ' left' if $1 == '<'
+ out = "<ul class=\"#{div_class}\">"
+ @toc.each do |heading|
+ level, anchor, toc_item = heading
+ out << "<li class=\"heading#{level}\"><a href=\"##{anchor}\">#{toc_item}</a></li>\n"
+ end
+ out << '</ul>'
+ out
+ end
+ end
+
+ MACROS_RE = /
+ (!)? # escaping
+ (
+ \{\{ # opening tag
+ ([\w]+) # macro name
+ (\(([^\}]*)\))? # optional arguments
+ \}\} # closing tag
+ )
+ /x unless const_defined?(:MACROS_RE)
+
+ def inline_macros(text)
+ text.gsub!(MACROS_RE) do
+ esc, all, macro = $1, $2, $3.downcase
+ args = ($5 || '').split(',').each(&:strip)
+ if esc.nil?
+ begin
+ @macros_runner.call(macro, args)
+ rescue => e
+ "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
+ end || all
+ else
+ all
+ end
+ end
+ end
+
+ AUTO_LINK_RE = %r{
+ ( # leading text
+ <\w+.*?>| # leading HTML tag, or
+ [^=<>!:'"/]| # leading punctuation, or
+ ^ # beginning of line
+ )
+ (
+ (?:https?://)| # protocol spec, or
+ (?:s?ftps?://)|
+ (?:www\.) # www.*
+ )
+ (
+ (\S+?) # url
+ (\/)? # slash
+ )
+ ([^\w\=\/;\(\)]*?) # post
+ (?=<|\s|$)
+ }x unless const_defined?(:AUTO_LINK_RE)
+
+ # Turns all urls into clickable links (code from Rails).
+ def inline_auto_link(text)
+ text.gsub!(AUTO_LINK_RE) do
+ all, leading, proto, url, post = $&, $1, $2, $3, $6
+ if leading =~ /<a\s/i || leading =~ /![<>=]?/
+ # don't replace URL's that are already linked
+ # and URL's prefixed with ! !> !< != (textile images)
+ all
+ else
+ # Idea below : an URL with unbalanced parethesis and
+ # ending by ')' is put into external parenthesis
+ if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
+ url=url[0..-2] # discard closing parenth from url
+ post = ")"+post # add closing parenth to post
+ end
+ %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post})
+ 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
+ mail = $1
+ if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
+ mail
+ else
+ %{<a href="mailto:#{mail}" class="email">#{mail}</a>}
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/redmine/wiki_formatting/textile/helper.rb b/lib/redmine/wiki_formatting/textile/helper.rb
new file mode 100644
index 000000000..c4bde572b
--- /dev/null
+++ b/lib/redmine/wiki_formatting/textile/helper.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 WikiFormatting
+ module Textile
+ module Helper
+ def wikitoolbar_for(field_id)
+ # Is there a simple way to link to a public resource?
+ url = "#{Redmine::Utils.relative_url_root}/help/wiki_syntax.html"
+
+ help_link = l(:setting_text_formatting) + ': ' +
+ link_to(l(:label_help), url,
+ :onclick => "window.open(\"#{ url }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
+
+ javascript_include_tag('jstoolbar/jstoolbar') +
+ javascript_include_tag('jstoolbar/textile') +
+ javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
+ javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
+ end
+
+ def initial_page_content(page)
+ "h1. #{@page.pretty_title}"
+ end
+
+ def heads_for_wiki_formatter
+ stylesheet_link_tag 'jstoolbar'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/tabular_form_builder.rb b/lib/tabular_form_builder.rb
index 88e35a6d2..3ca2d7aab 100644
--- a/lib/tabular_form_builder.rb
+++ b/lib/tabular_form_builder.rb
@@ -28,24 +28,24 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
(field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
src = <<-END_SRC
def #{selector}(field, options = {})
- return super if options.delete :no_label
- label_text = l(options[:label]) if options[:label]
- label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
- label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
- label = @template.content_tag("label", label_text,
- :class => (@object && @object.errors[field] ? "error" : nil),
- :for => (@object_name.to_s + "_" + field.to_s))
- label + super
+ label_for_field(field, options) + super
end
END_SRC
class_eval src, __FILE__, __LINE__
end
def select(field, choices, options = {}, html_options = {})
- label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
- label = @template.content_tag("label", label_text,
- :class => (@object && @object.errors[field] ? "error" : nil),
- :for => (@object_name.to_s + "_" + field.to_s))
- label + super
+ label_for_field(field, options) + super
+ end
+
+ # Returns a label tag for the given field
+ def label_for_field(field, options = {})
+ return '' if options.delete(:no_label)
+ text = l(options[:label]) if options[:label]
+ text ||= l(("field_" + field.to_s.gsub(/\_id$/, "")).to_sym)
+ text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
+ @template.content_tag("label", text,
+ :class => (@object && @object.errors[field] ? "error" : nil),
+ :for => (@object_name.to_s + "_" + field.to_s))
end
end
diff --git a/lib/tasks/email.rake b/lib/tasks/email.rake
index a37b3e197..0f74d6bd3 100644
--- a/lib/tasks/email.rake
+++ b/lib/tasks/email.rake
@@ -1,4 +1,4 @@
-# redMine - project management software
+# Redmine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
@@ -23,6 +23,7 @@ Read an email from standard input.
Issue attributes control options:
project=PROJECT identifier of the target project
+ status=STATUS name of the target status
tracker=TRACKER name of the target tracker
category=CATEGORY name of the target category
priority=PRIORITY name of the target priority
@@ -44,7 +45,7 @@ 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] }
+ %w(project status 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)
@@ -63,12 +64,18 @@ Available IMAP options:
Issue attributes control options:
project=PROJECT identifier of the target project
+ status=STATUS name of the target status
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
+
+Processed emails control options:
+ move_on_success=MAILBOX move emails that were successfully received
+ to MAILBOX instead of deleting them
+ move_on_failure=MAILBOX move emails that were ignored to MAILBOX
Examples:
# No project specified. Emails MUST contain the 'Project' keyword:
@@ -93,10 +100,12 @@ END_DESC
:ssl => ENV['ssl'],
:username => ENV['username'],
:password => ENV['password'],
- :folder => ENV['folder']}
+ :folder => ENV['folder'],
+ :move_on_success => ENV['move_on_success'],
+ :move_on_failure => ENV['move_on_failure']}
options = { :issue => {} }
- %w(project tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
+ %w(project status 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)
diff --git a/lib/tasks/migrate_from_trac.rake b/lib/tasks/migrate_from_trac.rake
index 880964ff8..02d921300 100644
--- a/lib/tasks/migrate_from_trac.rake
+++ b/lib/tasks/migrate_from_trac.rake
@@ -5,12 +5,12 @@
# 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.
@@ -22,10 +22,10 @@ require 'pp'
namespace :redmine do
desc 'Trac migration script'
task :migrate_from_trac => :environment do
-
+
module TracMigrate
TICKET_MAP = []
-
+
DEFAULT_STATUS = IssueStatus.default
assigned_status = IssueStatus.find_by_position(2)
resolved_status = IssueStatus.find_by_position(3)
@@ -36,7 +36,7 @@ namespace :redmine do
'assigned' => assigned_status,
'closed' => closed_status
}
-
+
priorities = Enumeration.get_values('IPRI')
DEFAULT_PRIORITY = priorities[0]
PRIORITY_MAPPING = {'lowest' => priorities[0],
@@ -51,7 +51,7 @@ namespace :redmine do
'critical' => priorities[3],
'blocker' => priorities[4]
}
-
+
TRACKER_BUG = Tracker.find_by_position(1)
TRACKER_FEATURE = Tracker.find_by_position(2)
DEFAULT_TRACKER = TRACKER_BUG
@@ -60,7 +60,7 @@ namespace :redmine do
'task' => TRACKER_FEATURE,
'patch' =>TRACKER_FEATURE
}
-
+
roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
manager_role = roles[0]
developer_role = roles[1]
@@ -68,7 +68,7 @@ namespace :redmine do
ROLE_MAPPING = {'admin' => manager_role,
'developer' => developer_role
}
-
+
class ::Time
class << self
alias :real_now :now
@@ -87,10 +87,10 @@ namespace :redmine do
class TracComponent < ActiveRecord::Base
set_table_name :component
end
-
+
class TracMilestone < ActiveRecord::Base
set_table_name :milestone
-
+ # If this attribute is set a milestone has a defined target timepoint
def due
if read_attribute(:due) && read_attribute(:due) > 0
Time.at(read_attribute(:due)).to_date
@@ -98,43 +98,51 @@ namespace :redmine do
nil
end
end
+ # This is the real timepoint at which the milestone has finished.
+ def completed
+ if read_attribute(:completed) && read_attribute(:completed) > 0
+ Time.at(read_attribute(:completed)).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
set_table_name :ticket_custom
end
-
+
class TracAttachment < ActiveRecord::Base
set_table_name :attachment
set_inheritance_column :none
-
+
def time; Time.at(read_attribute(:time)) end
-
+
def original_filename
filename
end
-
+
def content_type
Redmine::MimeType.of(filename) || ''
end
-
+
def exist?
File.file? trac_fullpath
end
-
+
def read
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)
@@ -142,11 +150,11 @@ namespace :redmine do
"#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}"
end
end
-
+
class TracTicket < ActiveRecord::Base
set_table_name :ticket
set_inheritance_column :none
-
+
# ticket changes: only migrate status changes and comments
has_many :changes, :class_name => "TracTicketChange", :foreign_key => :ticket
has_many :attachments, :class_name => "TracAttachment",
@@ -154,29 +162,29 @@ namespace :redmine do
" 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
read_attribute(:type)
end
-
+
def summary
read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
end
-
+
def description
read_attribute(:description).blank? ? summary : read_attribute(:description)
end
-
+
def time; Time.at(read_attribute(:time)) end
def changetime; Time.at(read_attribute(:changetime)) end
end
-
+
class TracTicketChange < ActiveRecord::Base
set_table_name :ticket_change
-
+
def time; Time.at(read_attribute(:time)) end
end
-
+
TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \
TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \
@@ -184,35 +192,35 @@ namespace :redmine do
TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \
WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \
CamelCase TitleIndex)
-
+
class TracWikiPage < ActiveRecord::Base
set_table_name :wiki
set_primary_key :name
-
+
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)
super.select {|column| column.name.to_s != 'readonly'}
end
-
+
def time; Time.at(read_attribute(:time)) end
end
-
+
class TracPermission < ActiveRecord::Base
- set_table_name :permission
+ 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?
-
+
u = User.find_by_login(username)
if !u
# Create a new user if not found
@@ -221,7 +229,7 @@ namespace :redmine do
mail = mail_attr.value
end
mail = "#{mail}@foo.bar" unless mail.include?("@")
-
+
name = username
if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name')
name = name_attr.value
@@ -229,7 +237,7 @@ namespace :redmine do
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, '-')
@@ -253,13 +261,30 @@ namespace :redmine do
end
u
end
-
+
# Basic wiki syntax conversion
def self.convert_wiki_text(text)
# Titles
text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"}
# External Links
text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
+ # Ticket links:
+ # [ticket:234 Text],[ticket:234 This is a test]
+ text = text.gsub(/\[ticket\:([^\ ]+)\ (.+?)\]/, '"\2":/issues/show/\1')
+ # ticket:1234
+ # #1 is working cause Redmine uses the same syntax.
+ text = text.gsub(/ticket\:([^\ ]+)/, '#\1')
+ # Milestone links:
+ # [milestone:"0.1.0 Mercury" Milestone 0.1.0 (Mercury)]
+ # The text "Milestone 0.1.0 (Mercury)" is not converted,
+ # cause Redmine's wiki does not support this.
+ text = text.gsub(/\[milestone\:\"([^\"]+)\"\ (.+?)\]/, 'version:"\1"')
+ # [milestone:"0.1.0 Mercury"]
+ text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"')
+ text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"')
+ # milestone:0.1.0
+ text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1')
+ text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1')
# Internal Links
text = text.gsub(/\[\[BR\]\]/, "\n") # This has to go before the rules below
text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
@@ -268,11 +293,11 @@ namespace :redmine do
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]]')
- # Normalize things that were supposed to not be links
- # like !NotALink
- text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2')
+ # Links to pages UsingJustWikiCaps
+ text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]')
+ # Normalize things that were supposed to not be links
+ # like !NotALink
+ text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2')
# Revisions links
text = text.gsub(/\[(\d+)\]/, 'r\1')
# Ticket number re-writing
@@ -284,9 +309,38 @@ namespace :redmine do
s
end
end
- # Preformatted blocks
- text = text.gsub(/\{\{\{/, '<pre>')
- text = text.gsub(/\}\}\}/, '</pre>')
+ # We would like to convert the Code highlighting too
+ # This will go into the next line.
+ shebang_line = false
+ # Reguar expression for start of code
+ pre_re = /\{\{\{/
+ # Code hightlighing...
+ shebang_re = /^\#\!([a-z]+)/
+ # Regular expression for end of code
+ pre_end_re = /\}\}\}/
+
+ # Go through the whole text..extract it line by line
+ text = text.gsub(/^(.*)$/) do |line|
+ m_pre = pre_re.match(line)
+ if m_pre
+ line = '<pre>'
+ else
+ m_sl = shebang_re.match(line)
+ if m_sl
+ shebang_line = true
+ line = '<code class="' + m_sl[1] + '">'
+ end
+ m_pre_end = pre_end_re.match(line)
+ if m_pre_end
+ line = '</pre>'
+ if shebang_line
+ line = '</code>' + line
+ end
+ end
+ end
+ line
+ end
+
# Highlighting
text = text.gsub(/'''''([^\s])/, '_*\1')
text = text.gsub(/([^\s])'''''/, '\1*_')
@@ -295,57 +349,73 @@ namespace :redmine do
text = text.gsub(/__/, '+')
text = text.gsub(/~~/, '-')
text = text.gsub(/`/, '@')
- text = text.gsub(/,,/, '~')
+ text = text.gsub(/,,/, '~')
# Lists
text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
text
end
-
+
def self.migrate
establish_connection
# Quick database test
TracComponent.count
-
+
migrated_components = 0
migrated_milestones = 0
migrated_tickets = 0
migrated_custom_values = 0
migrated_ticket_attachments = 0
- migrated_wiki_edits = 0
+ migrated_wiki_edits = 0
migrated_wiki_attachments = 0
-
+
+ #Wiki system initializing...
+ @target_project.wiki.destroy if @target_project.wiki
+ @target_project.reload
+ wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart')
+ wiki_edit_count = 0
+
# Components
print "Migrating components"
issues_category_map = {}
TracComponent.find(:all).each do |component|
- print '.'
- STDOUT.flush
+ print '.'
+ STDOUT.flush
c = IssueCategory.new :project => @target_project,
:name => encode(component.name[0, limit_for(IssueCategory, 'name')])
- next unless c.save
- issues_category_map[component.name] = c
- migrated_components += 1
+ next unless c.save
+ issues_category_map[component.name] = c
+ migrated_components += 1
end
puts
-
+
# Milestones
print "Migrating milestones"
version_map = {}
TracMilestone.find(:all).each do |milestone|
print '.'
STDOUT.flush
+ # First we try to find the wiki page...
+ p = wiki.find_or_new_page(milestone.name.to_s)
+ p.content = WikiContent.new(:page => p) if p.new_record?
+ p.content.text = milestone.description.to_s
+ p.content.author = find_or_create_user('trac')
+ p.content.comments = 'Milestone'
+ p.save
+
v = Version.new :project => @target_project,
:name => encode(milestone.name[0, limit_for(Version, 'name')]),
- :description => encode(milestone.description.to_s[0, limit_for(Version, 'description')]),
- :effective_date => milestone.due
+ :description => nil,
+ :wiki_page_title => milestone.name.to_s,
+ :effective_date => milestone.completed
+
next unless v.save
version_map[milestone.name] = v
migrated_milestones += 1
end
puts
-
+
# Custom fields
# TODO: read trac.ini instead
print "Migrating custom fields"
@@ -360,14 +430,14 @@ namespace :redmine do
# Or create a new one
f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
:field_format => 'string')
-
+
next if f.new_record?
f.trackers = Tracker.find(:all)
f.projects << @target_project
custom_field_map[field.name] = f
end
puts
-
+
# Trac 'resolution' field as a Redmine custom field
r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
r = IssueCustomField.new(:name => 'Resolution',
@@ -378,45 +448,44 @@ namespace :redmine do
r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
r.save!
custom_field_map['resolution'] = r
-
+
# Tickets
print "Migrating tickets"
TracTicket.find(:all, :order => 'id ASC').each do |ticket|
- print '.'
- STDOUT.flush
- i = Issue.new :project => @target_project,
+ print '.'
+ STDOUT.flush
+ i = Issue.new :project => @target_project,
:subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
:description => convert_wiki_text(encode(ticket.description)),
:priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY,
:created_on => ticket.time
- i.author = find_or_create_user(ticket.reporter)
- i.category = issues_category_map[ticket.component] unless ticket.component.blank?
- i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
- i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
- i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
- i.custom_values << CustomValue.new(:custom_field => custom_field_map['resolution'], :value => ticket.resolution) unless ticket.resolution.blank?
- i.id = ticket.id unless Issue.exists?(ticket.id)
- next unless Time.fake(ticket.changetime) { i.save }
- TICKET_MAP[ticket.id] = i.id
- migrated_tickets += 1
-
- # Owner
+ i.author = find_or_create_user(ticket.reporter)
+ i.category = issues_category_map[ticket.component] unless ticket.component.blank?
+ i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
+ i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
+ i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
+ i.id = ticket.id unless Issue.exists?(ticket.id)
+ next unless Time.fake(ticket.changetime) { i.save }
+ TICKET_MAP[ticket.id] = i.id
+ migrated_tickets += 1
+
+ # Owner
unless ticket.owner.blank?
i.assigned_to = find_or_create_user(ticket.owner, true)
Time.fake(ticket.changetime) { i.save }
end
-
- # Comments and status/resolution changes
- ticket.changes.group_by(&:time).each do |time, changeset|
+
+ # Comments and status/resolution changes
+ ticket.changes.group_by(&:time).each do |time, changeset|
status_change = changeset.select {|change| change.field == 'status'}.first
resolution_change = changeset.select {|change| change.field == 'resolution'}.first
comment_change = changeset.select {|change| change.field == 'comment'}.first
-
+
n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''),
:created_on => time
n.user = find_or_create_user(changeset.first.author)
n.journalized = i
- if status_change &&
+ if status_change &&
STATUS_MAPPING[status_change.oldvalue] &&
STATUS_MAPPING[status_change.newvalue] &&
(STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue])
@@ -432,40 +501,40 @@ namespace :redmine do
:value => resolution_change.newvalue)
end
n.save unless n.details.empty? && n.notes.blank?
- end
-
- # Attachments
- ticket.attachments.each do |attachment|
- next unless attachment.exist?
+ end
+
+ # Attachments
+ ticket.attachments.each do |attachment|
+ next unless attachment.exist?
a = Attachment.new :created_on => attachment.time
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
-
- # Custom fields
- ticket.customs.each do |custom|
- next if custom_field_map[custom.name].nil?
- v = CustomValue.new :custom_field => custom_field_map[custom.name],
- :value => custom.value
- v.customized = i
- next unless v.save
+ end
+
+ # Custom fields
+ custom_values = ticket.customs.inject({}) do |h, custom|
+ if custom_field = custom_field_map[custom.name]
+ h[custom_field.id] = custom.value
migrated_custom_values += 1
- end
+ end
+ h
+ end
+ if custom_field_map['resolution'] && !ticket.resolution.blank?
+ custom_values[custom_field_map['resolution'].id] = ticket.resolution
+ end
+ i.custom_field_values = custom_values
+ i.save_custom_field_values
end
-
+
# update issue id sequence if needed (postgresql)
Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
puts
-
- # Wiki
+
+ # Wiki
print "Migrating wiki"
- @target_project.wiki.destroy if @target_project.wiki
- @target_project.reload
- wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart')
- wiki_edit_count = 0
if wiki.save
TracWikiPage.find(:all, :order => 'name, version').each do |page|
# Do not migrate Trac manual wiki pages
@@ -479,10 +548,10 @@ namespace :redmine do
p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac'
p.content.comments = page.comment
Time.fake(page.time) { p.new_record? ? p.save : p.content.save }
-
+
next if p.content.new_record?
- migrated_wiki_edits += 1
-
+ migrated_wiki_edits += 1
+
# Attachments
page.attachments.each do |attachment|
next unless attachment.exist?
@@ -495,7 +564,7 @@ namespace :redmine do
migrated_wiki_attachments += 1 if a.save
end
end
-
+
wiki.reload
wiki.pages.each do |page|
page.content.text = convert_wiki_text(page.content.text)
@@ -503,7 +572,7 @@ namespace :redmine do
end
end
puts
-
+
puts
puts "Components: #{migrated_components}/#{TracComponent.count}"
puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}"
@@ -513,18 +582,18 @@ namespace :redmine do
puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}"
puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
end
-
+
def self.limit_for(klass, attribute)
klass.columns_hash[attribute.to_s].limit
end
-
+
def self.encoding(charset)
@ic = Iconv.new('UTF-8', charset)
rescue Iconv::InvalidEncoding
puts "Invalid encoding!"
return false
end
-
+
def self.set_trac_directory(path)
@@trac_directory = path
raise "This directory doesn't exist!" unless File.directory?(path)
@@ -549,7 +618,7 @@ namespace :redmine do
puts e
return false
end
-
+
def self.set_trac_db_host(host)
return nil if host.blank?
@@trac_db_host = host
@@ -559,7 +628,7 @@ namespace :redmine do
return nil if port.to_i == 0
@@trac_db_port = port.to_i
end
-
+
def self.set_trac_db_name(name)
return nil if name.blank?
@@trac_db_name = name
@@ -568,22 +637,22 @@ namespace :redmine do
def self.set_trac_db_username(username)
@@trac_db_username = username
end
-
+
def self.set_trac_db_password(password)
@@trac_db_password = password
end
-
+
def self.set_trac_db_schema(schema)
@@trac_db_schema = schema
end
mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password
-
+
def self.trac_db_path; "#{trac_directory}/db/trac.db" end
def self.trac_attachments_directory; "#{trac_directory}/attachments" end
-
+
def self.target_project_identifier(identifier)
- project = Project.find_by_identifier(identifier)
+ project = Project.find_by_identifier(identifier)
if !project
# create the target project
project = Project.new :name => identifier.humanize,
@@ -596,16 +665,16 @@ namespace :redmine do
puts
puts "This project already exists in your Redmine database."
print "Are you sure you want to append data to this project ? [Y/n] "
- exit if STDIN.gets.match(/^n$/i)
+ exit if STDIN.gets.match(/^n$/i)
end
project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
@target_project = project.new_record? ? nil : project
end
-
+
def self.connection_params
if %w(sqlite sqlite3).include?(trac_adapter)
- {:adapter => trac_adapter,
+ {:adapter => trac_adapter,
:database => trac_db_path}
else
{:adapter => trac_adapter,
@@ -618,7 +687,7 @@ namespace :redmine do
}
end
end
-
+
def self.establish_connection
constants.each do |const|
klass = const_get(const)
@@ -626,7 +695,7 @@ namespace :redmine do
klass.establish_connection connection_params
end
end
-
+
private
def self.encode(text)
@ic.iconv text
@@ -634,7 +703,7 @@ namespace :redmine do
text
end
end
-
+
puts
if Redmine::DefaultData::Loader.no_data?
puts "Redmine configuration need to be loaded before importing data."
@@ -643,10 +712,10 @@ namespace :redmine do
puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
exit
end
-
+
puts "WARNING: a new project will be added to Redmine during this process."
print "Are you sure you want to continue ? [y/N] "
- break unless STDIN.gets.match(/^y$/i)
+ break unless STDIN.gets.match(/^y$/i)
puts
def prompt(text, options = {}, &block)
@@ -658,9 +727,9 @@ namespace :redmine do
break if yield value
end
end
-
+
DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
-
+
prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter}
unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter)
@@ -674,7 +743,8 @@ namespace :redmine do
prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding}
prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier}
puts
-
+
TracMigrate.migrate
end
end
+
diff --git a/lib/tasks/testing.rake b/lib/tasks/testing.rake
index 42f756f68..da832b3e7 100644
--- a/lib/tasks/testing.rake
+++ b/lib/tasks/testing.rake
@@ -1,5 +1,35 @@
### From http://svn.geekdaily.org/public/rails/plugins/generally_useful/tasks/coverage_via_rcov.rake
+namespace :test do
+ namespace :scm do
+ namespace :setup do
+ desc "Creates directory for test repositories"
+ task :create_dir do
+ FileUtils.mkdir_p Rails.root + '/tmp/test'
+ end
+
+ supported_scms = [:subversion, :cvs, :bazaar, :mercurial, :git, :darcs, :filesystem]
+
+ desc "Creates a test subversion repository"
+ task :subversion => :create_dir do
+ repo_path = "tmp/test/subversion_repository"
+ system "svnadmin create #{repo_path}"
+ system "gunzip < test/fixtures/repositories/subversion_repository.dump.gz | svnadmin load #{repo_path}"
+ end
+
+ (supported_scms - [:subversion]).each do |scm|
+ desc "Creates a test #{scm} repository"
+ task scm => :create_dir do
+ system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test"
+ end
+ end
+
+ desc "Creates all test repositories"
+ task :all => supported_scms
+ end
+ end
+end
+
### Inspired by http://blog.labratz.net/articles/2006/12/2/a-rake-task-for-rcov
begin
require 'rcov/rcovtask'
diff --git a/public/dispatch.cgi.example b/public/dispatch.cgi.example
index 9730473f2..ce705d36e 100755
--- a/public/dispatch.cgi.example
+++ b/public/dispatch.cgi.example
@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#!/usr/bin/env ruby
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
diff --git a/public/dispatch.fcgi.example b/public/dispatch.fcgi.example
index f934b3002..664dbbbee 100755
--- a/public/dispatch.fcgi.example
+++ b/public/dispatch.fcgi.example
@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#!/usr/bin/env ruby
#
# You may specify the path to the FastCGI crash log (a log of unhandled
# exceptions which forced the FastCGI instance to exit, great for debugging)
diff --git a/public/dispatch.rb.example b/public/dispatch.rb.example
index 9730473f2..ce705d36e 100755
--- a/public/dispatch.rb.example
+++ b/public/dispatch.rb.example
@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#!/usr/bin/env ruby
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
diff --git a/public/help/wiki_syntax_detailed.html b/public/help/wiki_syntax_detailed.html
index ce416b573..b3db673b9 100644
--- a/public/help/wiki_syntax_detailed.html
+++ b/public/help/wiki_syntax_detailed.html
@@ -1,352 +1,247 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
-<head>
-<title>RedmineWikiFormatting</title>
-<meta http-equiv="content-type" content="text/html; charset=utf-8" />
-<style type="text/css">
-body { font:80% Verdana,Tahoma,Arial,sans-serif; }
-h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; }
-pre, code { font-size:120%; }
-pre code { font-size:100%; }
-pre {
- margin: 1em 1em 1em 1.6em;
- padding: 2px;
- background-color: #fafafa;
- border: 1px solid #dadada;
- width:95%;
- overflow-x: auto;
-}
-a.new { color: #b73535; }
-
-.CodeRay .af { color:#00C }
-.CodeRay .an { color:#007 }
-.CodeRay .av { color:#700 }
-.CodeRay .aw { color:#C00 }
-.CodeRay .bi { color:#509; font-weight:bold }
-.CodeRay .c { color:#666; }
-
-.CodeRay .ch { color:#04D }
-.CodeRay .ch .k { color:#04D }
-.CodeRay .ch .dl { color:#039 }
-
-.CodeRay .cl { color:#B06; font-weight:bold }
-.CodeRay .co { color:#036; font-weight:bold }
-.CodeRay .cr { color:#0A0 }
-.CodeRay .cv { color:#369 }
-.CodeRay .df { color:#099; font-weight:bold }
-.CodeRay .di { color:#088; font-weight:bold }
-.CodeRay .dl { color:black }
-.CodeRay .do { color:#970 }
-.CodeRay .ds { color:#D42; font-weight:bold }
-.CodeRay .e { color:#666; font-weight:bold }
-.CodeRay .en { color:#800; font-weight:bold }
-.CodeRay .er { color:#F00; background-color:#FAA }
-.CodeRay .ex { color:#F00; font-weight:bold }
-.CodeRay .fl { color:#60E; font-weight:bold }
-.CodeRay .fu { color:#06B; font-weight:bold }
-.CodeRay .gv { color:#d70; font-weight:bold }
-.CodeRay .hx { color:#058; font-weight:bold }
-.CodeRay .i { color:#00D; font-weight:bold }
-.CodeRay .ic { color:#B44; font-weight:bold }
-
-.CodeRay .il { background: #eee }
-.CodeRay .il .il { background: #ddd }
-.CodeRay .il .il .il { background: #ccc }
-.CodeRay .il .idl { font-weight: bold; color: #888 }
-
-.CodeRay .in { color:#B2B; font-weight:bold }
-.CodeRay .iv { color:#33B }
-.CodeRay .la { color:#970; font-weight:bold }
-.CodeRay .lv { color:#963 }
-.CodeRay .oc { color:#40E; font-weight:bold }
-.CodeRay .of { color:#000; font-weight:bold }
-.CodeRay .op { }
-.CodeRay .pc { color:#038; font-weight:bold }
-.CodeRay .pd { color:#369; font-weight:bold }
-.CodeRay .pp { color:#579 }
-.CodeRay .pt { color:#339; font-weight:bold }
-.CodeRay .r { color:#080; font-weight:bold }
-
-.CodeRay .rx { background-color:#fff0ff }
-.CodeRay .rx .k { color:#808 }
-.CodeRay .rx .dl { color:#404 }
-.CodeRay .rx .mod { color:#C2C }
-.CodeRay .rx .fu { color:#404; font-weight: bold }
-
-.CodeRay .s { background-color:#fff0f0 }
-.CodeRay .s .s { background-color:#ffe0e0 }
-.CodeRay .s .s .s { background-color:#ffd0d0 }
-.CodeRay .s .k { color:#D20 }
-.CodeRay .s .dl { color:#710 }
-
-.CodeRay .sh { background-color:#f0fff0 }
-.CodeRay .sh .k { color:#2B2 }
-.CodeRay .sh .dl { color:#161 }
-
-.CodeRay .sy { color:#A60 }
-.CodeRay .sy .k { color:#A60 }
-.CodeRay .sy .dl { color:#630 }
-
-.CodeRay .ta { color:#070 }
-.CodeRay .tf { color:#070; font-weight:bold }
-.CodeRay .ts { color:#D70; font-weight:bold }
-.CodeRay .ty { color:#339; font-weight:bold }
-.CodeRay .v { color:#036 }
-.CodeRay .xt { color:#444 }
-</style>
-</head>
-
-<body>
-<h1><a name="1" class="wiki-page"></a>Wiki formatting</h1>
-
-
-
- <h2><a name="2" class="wiki-page"></a>Links</h2>
-
-
- <h3><a name="3" class="wiki-page"></a>Redmine links</h3>
-
-
- <p>Redmine allows hyperlinking between issues, changesets and wiki pages from anywhere wiki formatting is used.</p>
-
-
- <ul>
- <li>Link to an issue: <strong>#124</strong> (displays <del><a href="/issues/show/124" class="issue" title="bulk edit doesn't change the category or fixed version properties (Closed)">#124</a></del>, link is striked-through if the issue is closed)</li>
- <li>Link to a changeset: <strong>r758</strong> (displays <a href="/repositories/revision/1?rev=758" class="changeset" title="Search engine now only searches objects the user is allowed to view.">r758</a>)</li>
- <li>Link to a changeset with a non-numeric hash: <strong>commit:c6f4d0fd</strong> (displays c6f4d0fd). Added in <a href="/repositories/revision/1?rev=1236" class="changeset" title="Merged Git support branch (r1200 to r1226).">r1236</a>.</li>
- </ul>
-
-
- <p>Wiki links:</p>
-
-
- <ul>
- <li><strong>&#91;&#91;Guide&#93;&#93;</strong> displays a link to the page named 'Guide': <a href="Guide.html" class="wiki-page">Guide</a></li>
- <li><strong>&#91;&#91;Guide|User manual&#93;&#93;</strong> displays a link to the same page but with a different text: <a href="Guide.html" class="wiki-page">User manual</a></li>
- </ul>
-
-
- <p>You can also link to pages of an other project wiki:</p>
-
-
- <ul>
- <li><strong>&#91;&#91;sandbox:some page&#93;&#93;</strong> displays a link to the page named 'Some page' of the Sandbox wiki</li>
- <li><strong>&#91;&#91;sandbox:&#93;&#93;</strong> displays a link to the Sandbox wiki main page</li>
- </ul>
-
-
- <p>Wiki links are displayed in red if the page doesn't exist yet, eg: <a href="Nonexistent_page.html" class="wiki-page new">Nonexistent page</a>.</p>
-
-
- <p>Links to others resources (0.7):</p>
-
-
- <ul>
- <li>Documents:
-
- <ul>
- <li><strong>document#17</strong> (link to document with id 17)</li>
- <li><strong>document:Greetings</strong> (link to the document with title "Greetings")</li>
- <li><strong>document:"Some document"</strong> (double quotes can be used when document title contains spaces)</li>
- </ul></li>
- </ul>
-
-
- <ul>
- <li>Versions:
-
- <ul>
- <li><strong>version#3</strong> (link to version with id 3)</li>
- <li><strong>version:1.0.0</strong> (link to version named "1.0.0")</li>
- <li><strong>version:"1.0 beta 2"</strong></li>
- </ul></li>
- </ul>
-
-
- <ul>
- <li>Attachments:
-
- <ul>
- <li><strong>attachment:file.zip</strong> (link to the attachment of the current object named file.zip)</li>
- <li>For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)</li>
- </ul></li>
- </ul>
-
-
- <ul>
- <li>Repository files
-
- <ul>
- <li><strong>source:some/file</strong> -- Link to the file located at /some/file in the project's repository</li>
- <li><strong>source:some/file@52</strong> -- Link to the file's revision 52</li>
-
- <li><strong>source:some/file#L120</strong> -- Link to line 120 of the file</li>
- <li><strong>source:some/file@52#L120</strong> -- Link to line 120 of the file's revision 52</li>
- <li><strong>export:some/file</strong> -- Force the download of the file</li>
- </ul></li>
-
- </ul>
-
-
-
- <p>Escaping (0.7):</p>
-
-
- <ul>
- <li>You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !</li>
- </ul>
-
-
- <h3><a name="4" class="wiki-page"></a>External links</h3>
-
-
- <p>HTTP URLs and email addresses are automatically turned into clickable links:</p>
-
-
-<pre>
-http://www.redmine.org, someone@foo.bar
-</pre>
-
- <p>displays: <a class="external" href="http://www.redmine.org">http://www.redmine.org</a>, <a href="mailto:someone@foo.bar" class="email">someone@foo.bar</a></p>
-
-
- <p>If you want to display a specific text instead of the URL, you can use the standard textile syntax:</p>
-
-
-<pre>
-"Redmine web site":http://www.redmine.org
-</pre>
-
- <p>displays: <a href="http://www.redmine.org" class="external">Redmine web site</a></p>
-
-
- <h2><a name="5" class="wiki-page"></a>Text formatting</h2>
-
-
- <p>For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See <a class="external" href="http://hobix.com/textile/">http://hobix.com/textile/</a> for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.</p>
-
-
- <h3><a name="6" class="wiki-page"></a>Font style</h3>
-
-
-<pre>
-* *bold*
-* _italic_
-* _*bold italic*_
-* +underline+
-* -strike-through-
-</pre>
-
- <p>Display:</p>
-
-
- <ul>
- <li><strong>bold</strong></li>
- <li><em>italic</em></li>
- <li><em>*bold italic*</em></li>
- <li><ins>underline</ins></li>
- <li><del>strike-through</del></li>
- </ul>
-
-
- <h3><a name="7" class="wiki-page"></a>Inline images</h3>
-
-
- <ul>
- <li><strong>&#33;image_url&#33;</strong> displays an image located at image_url (textile syntax)</li>
- <li><strong>&#33;>image_url&#33;</strong> right floating image</li>
- <li>If you have an image attached to your wiki page, it can be displayed inline using its filename: <strong>&#33;attached_image.png&#33;</strong></li>
- </ul>
-
-
- <h3><a name="8" class="wiki-page"></a>Headings</h3>
-
-
-<pre>
-h1. Heading
-h2. Subheading
-h3. Subheading
-</pre>
-
- <h3><a name="9" class="wiki-page"></a>Paragraphs</h3>
-
-
-<pre>
-p&gt;. right aligned
-p=. centered
-</pre>
-
- <p style="text-align:center;">This is centered paragraph.</p>
-
-
- <h3><a name="10" class="wiki-page"></a>Blockquotes</h3>
-
-
- <p>Start the paragraph with <strong>bq.</strong></p>
-
-
-<pre>
-bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
-To go live, all you need to add is a database and a web server.
-</pre>
-
- <p>Display:</p>
-
-
- <blockquote>
- <p>Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.<br />To go live, all you need to add is a database and a web server.</p>
- </blockquote>
-
-
- <h3><a name="11" class="wiki-page"></a>Table of content</h3>
-
-
-<pre>
-{{toc}} =&gt; left aligned toc
-{{&gt;toc}} =&gt; right aligned toc
-</pre>
-
- <h2><a name="12" class="wiki-page"></a>Macros</h2>
-
-
- <p>Redmine has the following builtin macros:</p>
-
-
- <p><dl><dt><code>hello_world</code></dt><dd><p>Sample macro.</p></dd><dt><code>include</code></dt><dd><p>Include a wiki page. Example:</p>
-
-
- <pre><code>{{include(Foo)}}</code></pre></dd><dt><code>macro_list</code></dt><dd><p>Displays a list of all available macros, including description if available.</p></dd></dl></p>
-
-
- <h2><a name="13" class="wiki-page"></a>Code highlighting</h2>
-
-
- <p>Code highlightment relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, html, javascript, rhtml, ruby, scheme, xml languages.</p>
-
-
- <p>You can highlight code in your wiki page using this syntax:</p>
-
-
-<pre>
-&lt;pre&gt;&lt;code class="ruby"&gt;
- Place you code here.
-&lt;/code&gt;&lt;/pre&gt;
-</pre>
-
- <p>Example:</p>
-
-
-<pre><code class="ruby CodeRay"><span class="no"> 1</span> <span class="c"># The Greeter class</span>
-<span class="no"> 2</span> <span class="r">class</span> <span class="cl">Greeter</span>
-<span class="no"> 3</span> <span class="r">def</span> <span class="fu">initialize</span>(name)
-<span class="no"> 4</span> <span class="iv">@name</span> = name.capitalize
-<span class="no"> 5</span> <span class="r">end</span>
-<span class="no"> 6</span>
-<span class="no"> 7</span> <span class="r">def</span> <span class="fu">salute</span>
-<span class="no"> 8</span> puts <span class="s"><span class="dl">"</span><span class="k">Hello </span><span class="il"><span class="idl">#{</span><span class="iv">@name</span><span class="idl">}</span></span><span class="k">!</span><span class="dl">"</span></span>
-<span class="no"> 9</span> <span class="r">end</span>
-<span class="no"><strong>10</strong></span> <span class="r">end</span>
-</code>
-</pre>
-</body>
-</html>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<title>RedmineWikiFormatting</title>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<style type="text/css">
+ body { font:80% Verdana,Tahoma,Arial,sans-serif; }
+ h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; }
+ pre, code { font-size:120%; }
+ pre code { font-size:100%; }
+ pre {
+ margin: 1em 1em 1em 1.6em;
+ padding: 2px;
+ background-color: #fafafa;
+ border: 1px solid #dadada;
+ width:95%;
+ overflow-x: auto;
+ }
+ a.new { color: #b73535; }
+
+ .CodeRay .c { color:#666; }
+
+ .CodeRay .cl { color:#B06; font-weight:bold }
+ .CodeRay .dl { color:black }
+ .CodeRay .fu { color:#06B; font-weight:bold }
+
+ .CodeRay .il { background: #eee }
+ .CodeRay .il .idl { font-weight: bold; color: #888 }
+
+ .CodeRay .iv { color:#33B }
+ .CodeRay .r { color:#080; font-weight:bold }
+
+ .CodeRay .s { background-color:#fff0f0 }
+ .CodeRay .s .dl { color:#710 }
+</style>
+</head>
+
+<body>
+<h1><a name="1" class="wiki-page"></a>Wiki formatting</h1>
+
+ <h2><a name="2" class="wiki-page"></a>Links</h2>
+
+ <h3><a name="3" class="wiki-page"></a>Redmine links</h3>
+
+ <p>Redmine allows hyperlinking between issues, changesets and wiki pages from anywhere wiki formatting is used.</p>
+ <ul>
+ <li>Link to an issue: <strong>#124</strong> (displays <del><a href="/issues/show/124" class="issue" title="bulk edit doesn't change the category or fixed version properties (Closed)">#124</a></del>, link is striked-through if the issue is closed)</li>
+ <li>Link to a changeset: <strong>r758</strong> (displays <a href="/repositories/revision/1?rev=758" class="changeset" title="Search engine now only searches objects the user is allowed to view.">r758</a>)</li>
+ <li>Link to a changeset with a non-numeric hash: <strong>commit:c6f4d0fd</strong> (displays c6f4d0fd). Added in <a href="/repositories/revision/1?rev=1236" class="changeset" title="Merged Git support branch (r1200 to r1226).">r1236</a>.</li>
+ </ul>
+
+ <p>Wiki links:</p>
+
+ <ul>
+ <li><strong>[[Guide]]</strong> displays a link to the page named 'Guide': <a href="Guide.html" class="wiki-page">Guide</a></li>
+ <li><strong>[[Guide#further-reading]]</strong> takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: <a href="Guide.html#further-reading" class="wiki-page">Guide</a></li>
+ <li><strong>[[Guide|User manual]]</strong> displays a link to the same page but with a different text: <a href="Guide.html" class="wiki-page">User manual</a></li>
+ </ul>
+
+ <p>You can also link to pages of an other project wiki:</p>
+
+ <ul>
+ <li><strong>[[sandbox:some page]]</strong> displays a link to the page named 'Some page' of the Sandbox wiki</li>
+ <li><strong>[[sandbox:]]</strong> displays a link to the Sandbox wiki main page</li>
+ </ul>
+
+ <p>Wiki links are displayed in red if the page doesn't exist yet, eg: <a href="Nonexistent_page.html" class="wiki-page new">Nonexistent page</a>.</p>
+
+ <p>Links to others resources (0.7):</p>
+
+ <ul>
+ <li>Documents:
+ <ul>
+ <li><strong>document#17</strong> (link to document with id 17)</li>
+ <li><strong>document:Greetings</strong> (link to the document with title "Greetings")</li>
+ <li><strong>document:"Some document"</strong> (double quotes can be used when document title contains spaces)</li>
+ </ul></li>
+ </ul>
+
+ <ul>
+ <li>Versions:
+ <ul>
+ <li><strong>version#3</strong> (link to version with id 3)</li>
+ <li><strong>version:1.0.0</strong> (link to version named "1.0.0")</li>
+ <li><strong>version:"1.0 beta 2"</strong></li>
+ </ul></li>
+ </ul>
+
+ <ul>
+ <li>Attachments:
+ <ul>
+ <li><strong>attachment:file.zip</strong> (link to the attachment of the current object named file.zip)</li>
+ <li>For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)</li>
+ </ul></li>
+ </ul>
+
+ <ul>
+ <li>Repository files
+ <ul>
+ <li><strong>source:some/file</strong> -- Link to the file located at /some/file in the project's repository</li>
+ <li><strong>source:some/file@52</strong> -- Link to the file's revision 52</li>
+ <li><strong>source:some/file#L120</strong> -- Link to line 120 of the file</li>
+ <li><strong>source:some/file@52#L120</strong> -- Link to line 120 of the file's revision 52</li>
+ <li><strong>export:some/file</strong> -- Force the download of the file</li>
+ </ul></li>
+ </ul>
+
+ <p>Escaping (0.7):</p>
+
+ <ul>
+ <li>You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !</li>
+ </ul>
+
+
+ <h3><a name="4" class="wiki-page"></a>External links</h3>
+
+ <p>HTTP URLs and email addresses are automatically turned into clickable links:</p>
+
+<pre>
+http://www.redmine.org, someone@foo.bar
+</pre>
+
+ <p>displays: <a class="external" href="http://www.redmine.org">http://www.redmine.org</a>, <a href="mailto:someone@foo.bar" class="email">someone@foo.bar</a></p>
+
+ <p>If you want to display a specific text instead of the URL, you can use the standard textile syntax:</p>
+
+<pre>
+"Redmine web site":http://www.redmine.org
+</pre>
+
+ <p>displays: <a href="http://www.redmine.org" class="external">Redmine web site</a></p>
+
+
+ <h2><a name="5" class="wiki-page"></a>Text formatting</h2>
+
+
+ <p>For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See <a class="external" href="http://hobix.com/textile/">http://hobix.com/textile/</a> for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.</p>
+
+ <h3><a name="6" class="wiki-page"></a>Font style</h3>
+
+<pre>
+* *bold*
+* _italic_
+* _*bold italic*_
+* +underline+
+* -strike-through-
+</pre>
+
+ <p>Display:</p>
+
+ <ul>
+ <li><strong>bold</strong></li>
+ <li><em>italic</em></li>
+ <li><em>*bold italic*</em></li>
+ <li><ins>underline</ins></li>
+ <li><del>strike-through</del></li>
+ </ul>
+
+ <h3><a name="7" class="wiki-page"></a>Inline images</h3>
+
+ <ul>
+ <li><strong>!image_url!</strong> displays an image located at image_url (textile syntax)</li>
+ <li><strong>!>image_url!</strong> right floating image</li>
+ <li>If you have an image attached to your wiki page, it can be displayed inline using its filename: <strong>!attached_image.png!</strong></li>
+ </ul>
+
+ <h3><a name="8" class="wiki-page"></a>Headings</h3>
+
+<pre>
+h1. Heading
+h2. Subheading
+h3. Subsubheading
+</pre>
+
+ <p>Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.</p>
+
+
+ <h3><a name="9" class="wiki-page"></a>Paragraphs</h3>
+
+<pre>
+p>. right aligned
+p=. centered
+</pre>
+
+ <p style="text-align:center;">This is centered paragraph.</p>
+
+
+ <h3><a name="10" class="wiki-page"></a>Blockquotes</h3>
+
+ <p>Start the paragraph with <strong>bq.</strong></p>
+
+<pre>
+bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
+To go live, all you need to add is a database and a web server.
+</pre>
+
+ <p>Display:</p>
+
+ <blockquote>
+ <p>Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.<br />To go live, all you need to add is a database and a web server.</p>
+ </blockquote>
+
+
+ <h3><a name="11" class="wiki-page"></a>Table of content</h3>
+
+<pre>
+{{toc}} => left aligned toc
+{{>toc}} => right aligned toc
+</pre>
+
+ <h2><a name="12" class="wiki-page"></a>Macros</h2>
+
+ <p>Redmine has the following builtin macros:</p>
+
+ <p><dl><dt><code>hello_world</code></dt><dd><p>Sample macro.</p></dd><dt><code>include</code></dt><dd><p>Include a wiki page. Example:</p>
+
+ <pre><code>{{include(Foo)}}</code></pre></dd><dt><code>macro_list</code></dt><dd><p>Displays a list of all available macros, including description if available.</p></dd></dl></p>
+
+
+ <h2><a name="13" class="wiki-page"></a>Code highlighting</h2>
+
+ <p>Code highlightment relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, html, javascript, rhtml, ruby, scheme, xml languages.</p>
+
+ <p>You can highlight code in your wiki page using this syntax:</p>
+
+<pre>
+&lt;pre&gt;&lt;code class="ruby"&gt;
+ Place you code here.
+&lt;/code&gt;&lt;/pre&gt;
+</pre>
+
+ <p>Example:</p>
+
+<pre><code class="ruby CodeRay"><span class="no"> 1</span> <span class="c"># The Greeter class</span>
+<span class="no"> 2</span> <span class="r">class</span> <span class="cl">Greeter</span>
+<span class="no"> 3</span> <span class="r">def</span> <span class="fu">initialize</span>(name)
+<span class="no"> 4</span> <span class="iv">@name</span> = name.capitalize
+<span class="no"> 5</span> <span class="r">end</span>
+<span class="no"> 6</span>
+<span class="no"> 7</span> <span class="r">def</span> <span class="fu">salute</span>
+<span class="no"> 8</span> puts <span class="s"><span class="dl">"</span><span class="k">Hello </span><span class="il"><span class="idl">#{</span><span class="iv">@name</span><span class="idl">}</span></span><span class="k">!</span><span class="dl">"</span></span>
+<span class="no"> 9</span> <span class="r">end</span>
+<span class="no"><strong>10</strong></span> <span class="r">end</span>
+</code>
+</pre>
+</body>
+</html>
diff --git a/public/images/openid-bg.gif b/public/images/openid-bg.gif
new file mode 100644
index 000000000..e2d8377db
--- /dev/null
+++ b/public/images/openid-bg.gif
Binary files differ
diff --git a/public/images/time.png b/public/images/time.png
index 81aa780e3..911da3f1d 100644
--- a/public/images/time.png
+++ b/public/images/time.png
Binary files differ
diff --git a/public/images/time_add.png b/public/images/time_add.png
new file mode 100644
index 000000000..dcc45cb22
--- /dev/null
+++ b/public/images/time_add.png
Binary files differ
diff --git a/public/images/warning.png b/public/images/warning.png
index bbef670b6..628cf2dae 100644
--- a/public/images/warning.png
+++ b/public/images/warning.png
Binary files differ
diff --git a/public/javascripts/calendar/lang/calendar-da.js b/public/javascripts/calendar/lang/calendar-da.js
index 2cba5f683..dfad32179 100644
--- a/public/javascripts/calendar/lang/calendar-da.js
+++ b/public/javascripts/calendar/lang/calendar-da.js
@@ -91,21 +91,21 @@ Calendar._TT["ABOUT"] =
"Dato valg:\n" +
"- Benyt \xab, \xbb tasterne til at vælge år\n" +
"- Benyt " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " tasterne til at vælge måned\n" +
-"- Hold muse tasten inde på punkterne for at vælge hurtigere.";
+"- Hold musetasten inde på punkterne for at vælge hurtigere.";
Calendar._TT["ABOUT_TIME"] = "\n\n" +
"Tids valg:\n" +
-"- Klik på en af tidsramerne for at forhøje det\n" +
+"- Klik på en af tidsrammerne for at forhøje det\n" +
"- eller Shift-klik for at mindske det\n" +
"- eller klik og træk for hurtigere valg.";
Calendar._TT["PREV_YEAR"] = "Forrige år (hold for menu)";
Calendar._TT["PREV_MONTH"] = "Forrige måned (hold for menu)";
-Calendar._TT["GO_TODAY"] = "GÃ¥ til idag";
+Calendar._TT["GO_TODAY"] = "GÃ¥ til dags dato";
Calendar._TT["NEXT_MONTH"] = "Næste måned (hold for menu)";
Calendar._TT["NEXT_YEAR"] = "Næste år (hold for menu)";
Calendar._TT["SEL_DATE"] = "Vælg dato";
Calendar._TT["DRAG_TO_MOVE"] = "Træk for at flytte";
-Calendar._TT["PART_TODAY"] = " (idag)";
+Calendar._TT["PART_TODAY"] = " (dags dato)";
// the following is to inform that "%s" is to be the first day of week
// %s will be replaced with the day name.
@@ -117,7 +117,7 @@ Calendar._TT["DAY_FIRST"] = "Vis %s først";
Calendar._TT["WEEKEND"] = "6,7";
Calendar._TT["CLOSE"] = "Luk";
-Calendar._TT["TODAY"] = "Idag";
+Calendar._TT["TODAY"] = "I dag";
Calendar._TT["TIME_PART"] = "(Shift-)Klik eller træk for at ændre værdi";
// date formats
diff --git a/public/javascripts/calendar/lang/calendar-gl.js b/public/javascripts/calendar/lang/calendar-gl.js
new file mode 100644
index 000000000..6141a761e
--- /dev/null
+++ b/public/javascripts/calendar/lang/calendar-gl.js
@@ -0,0 +1,128 @@
+// ** I18N
+
+// Calendar GL (galician) language
+// Author: Martín Vázquez Cabanas, <eu@martinvazquez.net>
+// Updated: 2009-01-23
+// 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
+("Domingo",
+ "Luns",
+ "Martes",
+ "Mércores",
+ "Xoves",
+ "Venres",
+ "Sábado",
+ "Domingo");
+
+// 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
+("Dom",
+ "Lun",
+ "Mar",
+ "Mér",
+ "Xov",
+ "Ven",
+ "Sáb",
+ "Dom");
+
+// 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
+("Xaneiro",
+ "Febreiro",
+ "Marzo",
+ "Abril",
+ "Maio",
+ "Xuño",
+ "Xullo",
+ "Agosto",
+ "Setembro",
+ "Outubro",
+ "Novembro",
+ "Decembro");
+
+// short month names
+Calendar._SMN = new Array
+("Xan",
+ "Feb",
+ "Mar",
+ "Abr",
+ "Mai",
+ "Xun",
+ "Xull",
+ "Ago",
+ "Set",
+ "Out",
+ "Nov",
+ "Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Acerca do calendario";
+
+Calendar._TT["ABOUT"] =
+"Selector DHTML de Data/Hora\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"Para conseguila última versión visite: http://www.dynarch.com/projects/calendar/\n" +
+"Distribuído baixo licenza GNU LGPL. Visite http://gnu.org/licenses/lgpl.html para máis detalles." +
+"\n\n" +
+"Selección de data:\n" +
+"- Use os botóns \xab, \xbb para seleccionalo ano\n" +
+"- Use os botóns " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para seleccionalo mes\n" +
+"- Manteña pulsado o rato en calquera destes botóns para unha selección rápida.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Selección de hora:\n" +
+"- Pulse en calquera das partes da hora para incrementala\n" +
+"- ou pulse maiúsculas mentres fai clic para decrementala\n" +
+"- ou faga clic e arrastre o rato para unha selección máis rápida.";
+
+Calendar._TT["PREV_YEAR"] = "Ano anterior (manter para menú)";
+Calendar._TT["PREV_MONTH"] = "Mes anterior (manter para menú)";
+Calendar._TT["GO_TODAY"] = "Ir a hoxe";
+Calendar._TT["NEXT_MONTH"] = "Mes seguinte (manter para menú)";
+Calendar._TT["NEXT_YEAR"] = "Ano seguinte (manter para menú)";
+Calendar._TT["SEL_DATE"] = "Seleccionar data";
+Calendar._TT["DRAG_TO_MOVE"] = "Arrastrar para mover";
+Calendar._TT["PART_TODAY"] = " (hoxe)";
+
+// 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"] = "Facer %s primeiro día da semana";
+
+// 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"] = "Pechar";
+Calendar._TT["TODAY"] = "Hoxe";
+Calendar._TT["TIME_PART"] = "(Maiúscula-)Clic ou arrastre para cambiar valor";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%A, %e de %B de %Y";
+
+Calendar._TT["WK"] = "sem";
+Calendar._TT["TIME"] = "Hora:";
diff --git a/public/javascripts/calendar/lang/calendar-ko.js b/public/javascripts/calendar/lang/calendar-ko.js
index 016453bfc..6570bb61d 100644
--- a/public/javascripts/calendar/lang/calendar-ko.js
+++ b/public/javascripts/calendar/lang/calendar-ko.js
@@ -79,23 +79,23 @@ Calendar._SMN = new Array
// tooltips
Calendar._TT = {};
-Calendar._TT["INFO"] = "About the calendar";
+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 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.";
+"ë‚ ì§œ ì„ íƒ:\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" +
+"- 시, ë¶„ì„ ë¹¼ë ¤ë©´ 쉬프트 누르고 í´ë¦­í•˜ì„¸ìš”.\n" +
+"- 좀 ë” ë¹ ë¥´ê²Œ ì„ íƒí•˜ë ¤ë©´ í´ë¦­í•˜ê³  드래그하세요.";
Calendar._TT["PREV_YEAR"] = "ì´ì „ í•´";
Calendar._TT["PREV_MONTH"] = "ì´ì „ 달";
@@ -117,11 +117,11 @@ Calendar._TT["WEEKEND"] = "0,6";
Calendar._TT["CLOSE"] = "닫기";
Calendar._TT["TODAY"] = "오늘";
-Calendar._TT["TIME_PART"] = "(Shift-)í´ë¦­ or drag to change value";
+Calendar._TT["TIME_PART"] = "í´ë¦­(+),쉬프트+í´ë¦­(-),드래그";
// date formats
Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
Calendar._TT["WK"] = "주";
-Calendar._TT["TIME"] = "Time:";
+Calendar._TT["TIME"] = "시간:";
diff --git a/public/javascripts/calendar/lang/calendar-mk.js b/public/javascripts/calendar/lang/calendar-mk.js
new file mode 100644
index 000000000..34fcaaee7
--- /dev/null
+++ b/public/javascripts/calendar/lang/calendar-mk.js
@@ -0,0 +1,127 @@
+// ** I18N
+
+// Calendar МК language
+// Author: Илин ТатабитовÑки, <ilin@slobodensoftver.org.mk>
+// 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"] = "Претходна година (hold for menu)";
+Calendar._TT["PREV_MONTH"] = "Претходен меÑец (hold for menu)";
+Calendar._TT["GO_TODAY"] = "Go Today";
+Calendar._TT["NEXT_MONTH"] = "Следен меÑец (hold for menu)";
+Calendar._TT["NEXT_YEAR"] = "Следна година (hold for menu)";
+Calendar._TT["SEL_DATE"] = "Изберете дата";
+Calendar._TT["DRAG_TO_MOVE"] = "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-)Click or drag to change value";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b";
+
+Calendar._TT["WK"] = "нед";
+Calendar._TT["TIME"] = "Време:";
diff --git a/public/javascripts/calendar/lang/calendar-pt.js b/public/javascripts/calendar/lang/calendar-pt.js
index 5d4d014ce..1ab579592 100644
--- a/public/javascripts/calendar/lang/calendar-pt.js
+++ b/public/javascripts/calendar/lang/calendar-pt.js
@@ -1,7 +1,8 @@
// ** I18N
-// Calendar pt_BR language
+// Calendar pt language
// Author: Adalberto Machado, <betosm@terra.com.br>
+// Corrected by: Pedro Araújo <phcrva19@hotmail.com>
// Encoding: any
// Distributed under the same terms as the calendar itself.
@@ -13,11 +14,11 @@
Calendar._DN = new Array
("Domingo",
"Segunda",
- "Terca",
+ "Terça",
"Quarta",
"Quinta",
"Sexta",
- "Sabado",
+ "Sábado",
"Domingo");
// Please note that the following array of short day names (and the same goes
@@ -40,7 +41,7 @@ Calendar._SDN = new Array
"Qua",
"Qui",
"Sex",
- "Sab",
+ "Sáb",
"Dom");
// First day of the week. "0" means display Sunday first, "1" means display
@@ -51,7 +52,7 @@ Calendar._FD = 1;
Calendar._MN = new Array
("Janeiro",
"Fevereiro",
- "Marco",
+ "Março",
"Abril",
"Maio",
"Junho",
@@ -79,30 +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 a licença 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.";
+"Selecção de data:\n" +
+"- Use os botões \xab, \xbb para seleccionar o ano\n" +
+"- Use os botões " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para seleccionar o mês\n" +
+"- Segure o botão do rato em qualquer um desses botões para selecção rápida.";
Calendar._TT["ABOUT_TIME"] = "\n\n" +
-"Selecao de hora:\n" +
+"Selecçã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 selecção rápida.";
-Calendar._TT["PREV_YEAR"] = "Ant. ano (segure para menu)";
-Calendar._TT["PREV_MONTH"] = "Ant. mes (segure para menu)";
+Calendar._TT["PREV_YEAR"] = "Ano ant. (segure para menu)";
+Calendar._TT["PREV_MONTH"] = "Mês ant. (segure para menu)";
Calendar._TT["GO_TODAY"] = "Hoje";
-Calendar._TT["NEXT_MONTH"] = "Prox. mes (segure para menu)";
+Calendar._TT["NEXT_MONTH"] = "Prox. mês (segure para menu)";
Calendar._TT["NEXT_YEAR"] = "Prox. ano (segure para menu)";
-Calendar._TT["SEL_DATE"] = "Selecione a data";
+Calendar._TT["SEL_DATE"] = "Seleccione a data";
Calendar._TT["DRAG_TO_MOVE"] = "Arraste para mover";
Calendar._TT["PART_TODAY"] = " (hoje)";
diff --git a/public/javascripts/calendar/lang/calendar-sk.js b/public/javascripts/calendar/lang/calendar-sk.js
new file mode 100644
index 000000000..c54d9ac93
--- /dev/null
+++ b/public/javascripts/calendar/lang/calendar-sk.js
@@ -0,0 +1,68 @@
+/*
+ calendar-sk.js
+ language: Slovak
+ encoding: UTF-8
+ author: Stanislav Pach (stano.pach@seznam.cz)
+*/
+
+// ** I18N
+Calendar._DN = new Array('Nedeľa','Pondelok','Utorok','Streda','Štvrtok','Piatok','Sobota','Nedeľa');
+Calendar._SDN = new Array('Ne','Po','Ut','St','Å t','Pi','So','Ne');
+Calendar._MN = new Array('Január','Február','Marec','Apríl','Máj','Jún','Júl','August','September','Október','November','December');
+Calendar._SMN = new Array('Jan','Feb','Mar','Apr','Máj','Jún','Júl','Aug','Sep','Okt','Nov','Dec');
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 1;
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "O komponente kalendár";
+Calendar._TT["TOGGLE"] = "Zmena prvého dňa v týždni";
+Calendar._TT["PREV_YEAR"] = "Predchádzajúci rok (pridrž pre menu)";
+Calendar._TT["PREV_MONTH"] = "Predchádzajúci mesiac (pridrž pre menu)";
+Calendar._TT["GO_TODAY"] = "Dnešný dátum";
+Calendar._TT["NEXT_MONTH"] = "Ďalší mesiac (pridrž pre menu)";
+Calendar._TT["NEXT_YEAR"] = "Ďalší rok (pridrž pre menu)";
+Calendar._TT["SEL_DATE"] = "Zvoľ dátum";
+Calendar._TT["DRAG_TO_MOVE"] = "Chyť a ťahaj pre presun";
+Calendar._TT["PART_TODAY"] = " (dnes)";
+Calendar._TT["MON_FIRST"] = "Ukáž ako prvný Pondelok";
+//Calendar._TT["SUN_FIRST"] = "Ukaž jako první Neděli";
+
+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" +
+"Výber dátumu:\n" +
+"- Použijte tlaÄítka \xab, \xbb pre voľbu roku\n" +
+"- Použijte tlaÄítka " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pre výber mesiaca\n" +
+"- Podržte tlaÄítko myÅ¡i na akomkoľvek z týchto tlaÄítok pre rýchlejší výber.";
+
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Výber Äasu:\n" +
+"- Kliknite na akúkoľvek ÄasÅ¥ z výberu Äasu pre zvýšenie.\n" +
+"- alebo Shift-klick pre zníženie\n" +
+"- alebo kliknite a ťahajte pre rýchlejší výber.";
+
+// 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"] = "Zobraz %s ako prvý";
+
+// 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"] = "Zavrieť";
+Calendar._TT["TODAY"] = "Dnes";
+Calendar._TT["TIME_PART"] = "(Shift-)Klikni alebo ťahaj pre zmenu hodnoty";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "d.m.yy";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "týž";
+Calendar._TT["TIME"] = "ÄŒas:";
diff --git a/public/javascripts/calendar/lang/calendar-sl.js b/public/javascripts/calendar/lang/calendar-sl.js
new file mode 100644
index 000000000..771731c5b
--- /dev/null
+++ b/public/javascripts/calendar/lang/calendar-sl.js
@@ -0,0 +1,127 @@
+// ** I18N
+
+// Calendar SL language
+// Author: Jernej Vidmar, <jernej.vidmar@vidmarboehm.com>
+// Encoding: any
+// 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
+("Nedelja",
+ "Ponedeljek",
+ "Torek",
+ "Sreda",
+ "ÄŒetrtek",
+ "Petek",
+ "Sobota",
+ "Nedelja");
+
+// 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
+("Ned",
+ "Pon",
+ "Tor",
+ "Sre",
+ "ÄŒet",
+ "Pet",
+ "Sob",
+ "Ned");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 0;
+
+// full month names
+Calendar._MN = new Array
+("Januar",
+ "Februar",
+ "Marec",
+ "April",
+ "Maj",
+ "Junij",
+ "Julij",
+ "Avgust",
+ "September",
+ "Oktober",
+ "November",
+ "December");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Maj",
+ "Jun",
+ "Jul",
+ "Avg",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "O koledarju";
+
+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" +
+"Izbira datuma:\n" +
+"- Uporabite \xab, \xbb gumbe za izbiro leta\n" +
+"- Uporabite " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " gumbe za izbiro meseca\n" +
+"- Za hitrejšo izbiro držite miškin gumb nad enim od zgornjih gumbov.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Izbira Äasa:\n" +
+"- Kliknite na katerikoli del Äasa da ga poveÄate\n" +
+"- oziroma kliknite s Shiftom za znižanje\n" +
+"- ali kliknite in vlecite za hitrejšo izbiro.";
+
+Calendar._TT["PREV_YEAR"] = "Prejšnje leto (držite za meni)";
+Calendar._TT["PREV_MONTH"] = "Prejšnji mesec (držite za meni)";
+Calendar._TT["GO_TODAY"] = "Pojdi na danes";
+Calendar._TT["NEXT_MONTH"] = "Naslednji mesec (držite za meni)";
+Calendar._TT["NEXT_YEAR"] = "Naslednje leto (držite za meni)";
+Calendar._TT["SEL_DATE"] = "Izberite datum";
+Calendar._TT["DRAG_TO_MOVE"] = "Povlecite za premik";
+Calendar._TT["PART_TODAY"] = " (danes)";
+
+// 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"] = "Najprej prikaži %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"] = "Zapri";
+Calendar._TT["TODAY"] = "Danes";
+Calendar._TT["TIME_PART"] = "(Shift-)klik ali povleÄi, da spremeniÅ¡ vrednost";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Time:";
diff --git a/public/javascripts/calendar/lang/calendar-sv.js b/public/javascripts/calendar/lang/calendar-sv.js
index 7e73d7926..fcc0eaa67 100644
--- a/public/javascripts/calendar/lang/calendar-sv.js
+++ b/public/javascripts/calendar/lang/calendar-sv.js
@@ -17,7 +17,7 @@ Calendar._SMN_len = 3; // short month name length
// First day of the week. "0" means display Sunday first, "1" means display
// Monday first, etc.
-Calendar._FD = 0;
+Calendar._FD = 1;
// full month names
Calendar._MN = new Array
@@ -36,49 +36,49 @@ Calendar._MN = new Array
// tooltips
Calendar._TT = {};
-Calendar._TT["INFO"] = "About the calendar";
+Calendar._TT["INFO"] = "Om kalendern";
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." +
+"DHTML Datum/Tid-väljare\n" +
+"(c) dynarch.com 2002-2005 / Upphovsman: Mihai Bazon\n" + // don't translate this this ;-)
+"För senaste version besök: http://www.dynarch.com/projects/calendar/\n" +
+"Distribueras under GNU LGPL. Se http://gnu.org/licenses/lgpl.html för detaljer." +
"\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.";
+"Välja datum:\n" +
+"- Använd \xab, \xbb knapparna för att välja år\n" +
+"- Använd " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " knapparna för att välja månad\n" +
+"- Håll nere musknappen på någon av ovanstående knappar för att se snabbval.";
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.";
+"Välja tid:\n" +
+"- Klicka på något av tidsfälten för att öka\n" +
+"- eller Skift-klicka för att minska\n" +
+"- eller klicka och dra för att välja snabbare.";
-Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)";
-Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)";
-Calendar._TT["GO_TODAY"] = "Go Today";
-Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)";
-Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)";
-Calendar._TT["SEL_DATE"] = "Select date";
-Calendar._TT["DRAG_TO_MOVE"] = "Drag to move";
-Calendar._TT["PART_TODAY"] = " (today)";
+Calendar._TT["PREV_YEAR"] = "Föreg. år (håll nere för lista)";
+Calendar._TT["PREV_MONTH"] = "Föreg. månad (håll nere för lista)";
+Calendar._TT["GO_TODAY"] = "GÃ¥ till Idag";
+Calendar._TT["NEXT_MONTH"] = "Nästa månad (håll nere för lista)";
+Calendar._TT["NEXT_YEAR"] = "Nästa år (håll nere för lista)";
+Calendar._TT["SEL_DATE"] = "Välj datum";
+Calendar._TT["DRAG_TO_MOVE"] = "Dra för att flytta";
+Calendar._TT["PART_TODAY"] = " (idag)";
// 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"] = "Visa %s först";
// 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"] = "Close";
-Calendar._TT["TODAY"] = "Today";
-Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value";
+Calendar._TT["CLOSE"] = "Stäng";
+Calendar._TT["TODAY"] = "Idag";
+Calendar._TT["TIME_PART"] = "(Skift-)klicka eller dra för att ändra värde";
// date formats
Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
-Calendar._TT["WK"] = "wk";
-Calendar._TT["TIME"] = "Time:";
+Calendar._TT["WK"] = "v.";
+Calendar._TT["TIME"] = "Tid:";
diff --git a/public/javascripts/calendar/lang/calendar-vn.js b/public/javascripts/calendar/lang/calendar-vn.js
new file mode 100644
index 000000000..9172c6638
--- /dev/null
+++ b/public/javascripts/calendar/lang/calendar-vn.js
@@ -0,0 +1,126 @@
+// ** I18N
+
+// Calendar EN language
+// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
+// Encoding: any
+// 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
+("Chủ nhật",
+ "Thứ Hai",
+ "Thứ Ba",
+ "Thứ Tư",
+ "Thứ Năm",
+ "Thứ Sáu",
+ "Thứ Bảy",
+ "Chủ Nhật");
+
+// 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
+("C.Nhật",
+ "Hai",
+ "Ba",
+ "Tư",
+ "Năm",
+ "Sáu",
+ "Bảy",
+ "C.Nhật");
+
+// 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
+("Tháng Giêng",
+ "Tháng Hai",
+ "Tháng Ba",
+ "Tháng Tư",
+ "Tháng Năm",
+ "Tháng Sáu",
+ "Tháng Bảy",
+ "Tháng Tám",
+ "Tháng Chín",
+ "Tháng Mưá»i",
+ "Tháng M.Một",
+ "Tháng Chạp");
+
+// short month names
+Calendar._SMN = new Array
+("Mmá»™t",
+ "Hai",
+ "Ba",
+ "Tư",
+ "Năm",
+ "Sáu",
+ "Bảy",
+ "Tám",
+ "Chín",
+ "Mưá»i",
+ "MMá»™t",
+ "Chạp");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Giới thiệu";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector (c) dynarch.com 2002-2005 / Tác giả: Mihai Bazon. " + // don't translate this this ;-)
+"Phiên bản mới nhất có tại: http://www.dynarch.com/projects/calendar/. " +
+"Sản phẩm được phân phối theo giấy phép GNU LGPL. Xem chi tiết tại http://gnu.org/licenses/lgpl.html." +
+"\n\n" +
+"Chá»n ngày:\n" +
+"- Dùng nút \xab, \xbb để chá»n năm\n" +
+"- Dùng nút " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " để chá»n tháng\n" +
+"- Giữ chuột vào các nút trên để có danh sách năm và tháng.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Chá»n thá»i gian:\n" +
+"- Click chuá»™t trên từng phần cá»§a thá»i gian để chỉnh sá»­a\n" +
+"- hoặc nhấn Shift + click chuột để tăng giá trị\n" +
+"- hoặc click chuá»™t và kéo (drag) để chá»n nhanh.";
+
+Calendar._TT["PREV_YEAR"] = "Năm trước (giữ chuột để có menu)";
+Calendar._TT["PREV_MONTH"] = "Tháng trước (giữ chuột để có menu)";
+Calendar._TT["GO_TODAY"] = "đến Hôm nay";
+Calendar._TT["NEXT_MONTH"] = "Tháng tới (giữ chuột để có menu)";
+Calendar._TT["NEXT_YEAR"] = "Ngày tới (giữ chuột để có menu)";
+Calendar._TT["SEL_DATE"] = "Chá»n ngày";
+Calendar._TT["DRAG_TO_MOVE"] = "Kéo (drag) để di chuyển";
+Calendar._TT["PART_TODAY"] = " (hôm nay)";
+
+// 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"] = "Hiển thị %s trước";
+
+// 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"] = "Äóng";
+Calendar._TT["TODAY"] = "Hôm nay";
+Calendar._TT["TIME_PART"] = "Click, shift-click hoặc kéo (drag) để đổi giá trị";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Time:";
diff --git a/public/javascripts/context_menu.js b/public/javascripts/context_menu.js
index 20f0fc5a7..955650d6a 100644
--- a/public/javascripts/context_menu.js
+++ b/public/javascripts/context_menu.js
@@ -48,7 +48,7 @@ ContextMenu.prototype = {
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')) {
+ if (tr!=null && 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 && box!=undefined) {
diff --git a/public/javascripts/jstoolbar/jstoolbar.js b/public/javascripts/jstoolbar/jstoolbar.js
index 64c460217..66669ceee 100644
--- a/public/javascripts/jstoolbar/jstoolbar.js
+++ b/public/javascripts/jstoolbar/jstoolbar.js
@@ -378,182 +378,3 @@ jsToolBar.prototype.resizeDragStop = function(event) {
document.removeEventListener('mousemove', this.dragMoveHdlr, false);
document.removeEventListener('mouseup', this.dragStopHdlr, false);
};
-
-// Elements definition ------------------------------------
-
-// strong
-jsToolBar.prototype.elements.strong = {
- type: 'button',
- title: 'Strong',
- fn: {
- wiki: function() { this.singleTag('*') }
- }
-}
-
-// em
-jsToolBar.prototype.elements.em = {
- type: 'button',
- title: 'Italic',
- fn: {
- wiki: function() { this.singleTag("_") }
- }
-}
-
-// ins
-jsToolBar.prototype.elements.ins = {
- type: 'button',
- title: 'Underline',
- fn: {
- wiki: function() { this.singleTag('+') }
- }
-}
-
-// del
-jsToolBar.prototype.elements.del = {
- type: 'button',
- title: 'Deleted',
- fn: {
- wiki: function() { this.singleTag('-') }
- }
-}
-
-// code
-jsToolBar.prototype.elements.code = {
- type: 'button',
- title: 'Code',
- fn: {
- wiki: function() { this.singleTag('@') }
- }
-}
-
-// spacer
-jsToolBar.prototype.elements.space1 = {type: 'space'}
-
-// headings
-jsToolBar.prototype.elements.h1 = {
- type: 'button',
- title: 'Heading 1',
- fn: {
- wiki: function() {
- this.encloseLineSelection('h1. ', '',function(str) {
- str = str.replace(/^h\d+\.\s+/, '')
- return str;
- });
- }
- }
-}
-jsToolBar.prototype.elements.h2 = {
- type: 'button',
- title: 'Heading 2',
- fn: {
- wiki: function() {
- this.encloseLineSelection('h2. ', '',function(str) {
- str = str.replace(/^h\d+\.\s+/, '')
- return str;
- });
- }
- }
-}
-jsToolBar.prototype.elements.h3 = {
- type: 'button',
- title: 'Heading 3',
- fn: {
- wiki: function() {
- this.encloseLineSelection('h3. ', '',function(str) {
- str = str.replace(/^h\d+\.\s+/, '')
- return str;
- });
- }
- }
-}
-
-// spacer
-jsToolBar.prototype.elements.space2 = {type: 'space'}
-
-// ul
-jsToolBar.prototype.elements.ul = {
- type: 'button',
- title: 'Unordered list',
- fn: {
- wiki: function() {
- this.encloseLineSelection('','',function(str) {
- str = str.replace(/\r/g,'');
- return str.replace(/(\n|^)[#-]?\s*/g,"$1* ");
- });
- }
- }
-}
-
-// ol
-jsToolBar.prototype.elements.ol = {
- type: 'button',
- title: 'Ordered list',
- fn: {
- wiki: function() {
- this.encloseLineSelection('','',function(str) {
- str = str.replace(/\r/g,'');
- return str.replace(/(\n|^)[*-]?\s*/g,"$1# ");
- });
- }
- }
-}
-
-// spacer
-jsToolBar.prototype.elements.space3 = {type: 'space'}
-
-// bq
-jsToolBar.prototype.elements.bq = {
- type: 'button',
- title: 'Quote',
- fn: {
- wiki: function() {
- this.encloseLineSelection('','',function(str) {
- str = str.replace(/\r/g,'');
- return str.replace(/(\n|^) *([^\n]*)/g,"$1> $2");
- });
- }
- }
-}
-
-// unbq
-jsToolBar.prototype.elements.unbq = {
- type: 'button',
- title: 'Unquote',
- fn: {
- wiki: function() {
- this.encloseLineSelection('','',function(str) {
- str = str.replace(/\r/g,'');
- return str.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2");
- });
- }
- }
-}
-
-// pre
-jsToolBar.prototype.elements.pre = {
- type: 'button',
- title: 'Preformatted text',
- fn: {
- wiki: function() { this.encloseLineSelection('<pre>\n', '\n</pre>') }
- }
-}
-
-// spacer
-jsToolBar.prototype.elements.space4 = {type: 'space'}
-
-// wiki page
-jsToolBar.prototype.elements.link = {
- type: 'button',
- title: 'Wiki link',
- fn: {
- wiki: function() { this.encloseSelection("[[", "]]") }
- }
-}
-// image
-jsToolBar.prototype.elements.img = {
- type: 'button',
- title: 'Image',
- fn: {
- wiki: function() { this.encloseSelection("!", "!") }
- }
-}
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-da.js b/public/javascripts/jstoolbar/lang/jstoolbar-da.js
index 6ccc8ead2..4b6b431ed 100644
--- a/public/javascripts/jstoolbar/lang/jstoolbar-da.js
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-da.js
@@ -1,16 +1,16 @@
jsToolBar.strings = {};
jsToolBar.strings['Strong'] = 'Fed';
jsToolBar.strings['Italic'] = 'Kursiv';
-jsToolBar.strings['Underline'] = 'Underskrevet';
+jsToolBar.strings['Underline'] = 'Understreget';
jsToolBar.strings['Deleted'] = 'Slettet';
-jsToolBar.strings['Code'] = 'Inline Kode';
+jsToolBar.strings['Code'] = 'Inline-kode';
jsToolBar.strings['Heading 1'] = 'Overskrift 1';
jsToolBar.strings['Heading 2'] = 'Overskrift 2';
jsToolBar.strings['Heading 3'] = 'Overskrift 3';
-jsToolBar.strings['Unordered list'] = 'Unummereret list';
-jsToolBar.strings['Ordered list'] = 'Nummereret list';
+jsToolBar.strings['Unordered list'] = 'Unummereret liste';
+jsToolBar.strings['Ordered list'] = 'Nummereret liste';
jsToolBar.strings['Quote'] = 'Quote';
jsToolBar.strings['Unquote'] = 'Remove Quote';
-jsToolBar.strings['Preformatted text'] = 'Preformatteret tekst';
-jsToolBar.strings['Wiki link'] = 'Link til en Wiki side';
+jsToolBar.strings['Preformatted text'] = 'Præformateret tekst';
+jsToolBar.strings['Wiki link'] = 'Link til en wiki-side';
jsToolBar.strings['Image'] = 'Billede';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-es.js b/public/javascripts/jstoolbar/lang/jstoolbar-es.js
index 2d68498f9..878489fbf 100644
--- a/public/javascripts/jstoolbar/lang/jstoolbar-es.js
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-es.js
@@ -1,16 +1,16 @@
jsToolBar.strings = {};
-jsToolBar.strings['Strong'] = 'Strong';
-jsToolBar.strings['Italic'] = 'Italic';
-jsToolBar.strings['Underline'] = 'Underline';
-jsToolBar.strings['Deleted'] = 'Deleted';
-jsToolBar.strings['Code'] = 'Inline Code';
-jsToolBar.strings['Heading 1'] = 'Heading 1';
-jsToolBar.strings['Heading 2'] = 'Heading 2';
-jsToolBar.strings['Heading 3'] = 'Heading 3';
-jsToolBar.strings['Unordered list'] = 'Unordered list';
-jsToolBar.strings['Ordered list'] = 'Ordered list';
-jsToolBar.strings['Quote'] = 'Quote';
-jsToolBar.strings['Unquote'] = 'Remove Quote';
-jsToolBar.strings['Preformatted text'] = 'Preformatted text';
-jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
-jsToolBar.strings['Image'] = 'Image';
+jsToolBar.strings['Strong'] = 'Negrita';
+jsToolBar.strings['Italic'] = 'Itálica';
+jsToolBar.strings['Underline'] = 'Subrayado';
+jsToolBar.strings['Deleted'] = 'Tachado';
+jsToolBar.strings['Code'] = 'Código fuente';
+jsToolBar.strings['Heading 1'] = 'Encabezado 1';
+jsToolBar.strings['Heading 2'] = 'Encabezado 2';
+jsToolBar.strings['Heading 3'] = 'Encabezado 3';
+jsToolBar.strings['Unordered list'] = 'Lista sin ordenar';
+jsToolBar.strings['Ordered list'] = 'Lista ordenada';
+jsToolBar.strings['Quote'] = 'Citar';
+jsToolBar.strings['Unquote'] = 'Quitar cita';
+jsToolBar.strings['Preformatted text'] = 'Texto con formato';
+jsToolBar.strings['Wiki link'] = 'Enlace a página Wiki';
+jsToolBar.strings['Image'] = 'Imagen';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-gl.js b/public/javascripts/jstoolbar/lang/jstoolbar-gl.js
new file mode 100644
index 000000000..bd1462aeb
--- /dev/null
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-gl.js
@@ -0,0 +1,16 @@
+jsToolBar.strings = {};
+jsToolBar.strings['Strong'] = 'Negriña';
+jsToolBar.strings['Italic'] = 'Itálica';
+jsToolBar.strings['Underline'] = 'Suliñado';
+jsToolBar.strings['Deleted'] = 'Tachado';
+jsToolBar.strings['Code'] = 'Código fonte';
+jsToolBar.strings['Heading 1'] = 'Encabezado 1';
+jsToolBar.strings['Heading 2'] = 'Encabezado 2';
+jsToolBar.strings['Heading 3'] = 'Encabezado 3';
+jsToolBar.strings['Unordered list'] = 'Lista sen ordenar';
+jsToolBar.strings['Ordered list'] = 'Lista ordenada';
+jsToolBar.strings['Quote'] = 'Citar';
+jsToolBar.strings['Unquote'] = 'Quitar cita';
+jsToolBar.strings['Preformatted text'] = 'Texto con formato';
+jsToolBar.strings['Wiki link'] = 'Enlace a páxina Wiki';
+jsToolBar.strings['Image'] = 'Imaxe';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-it.js b/public/javascripts/jstoolbar/lang/jstoolbar-it.js
index 2d68498f9..bf7fcefb2 100644
--- a/public/javascripts/jstoolbar/lang/jstoolbar-it.js
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-it.js
@@ -1,16 +1,16 @@
jsToolBar.strings = {};
-jsToolBar.strings['Strong'] = 'Strong';
-jsToolBar.strings['Italic'] = 'Italic';
-jsToolBar.strings['Underline'] = 'Underline';
-jsToolBar.strings['Deleted'] = 'Deleted';
-jsToolBar.strings['Code'] = 'Inline Code';
-jsToolBar.strings['Heading 1'] = 'Heading 1';
-jsToolBar.strings['Heading 2'] = 'Heading 2';
-jsToolBar.strings['Heading 3'] = 'Heading 3';
-jsToolBar.strings['Unordered list'] = 'Unordered list';
-jsToolBar.strings['Ordered list'] = 'Ordered list';
-jsToolBar.strings['Quote'] = 'Quote';
-jsToolBar.strings['Unquote'] = 'Remove Quote';
-jsToolBar.strings['Preformatted text'] = 'Preformatted text';
-jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
-jsToolBar.strings['Image'] = 'Image';
+jsToolBar.strings['Strong'] = 'Grassetto';
+jsToolBar.strings['Italic'] = 'Corsivo';
+jsToolBar.strings['Underline'] = 'Sottolineato';
+jsToolBar.strings['Deleted'] = 'Barrato';
+jsToolBar.strings['Code'] = 'Codice sorgente';
+jsToolBar.strings['Heading 1'] = 'Titolo 1';
+jsToolBar.strings['Heading 2'] = 'Titolo 2';
+jsToolBar.strings['Heading 3'] = 'Titolo 3';
+jsToolBar.strings['Unordered list'] = 'Elenco puntato';
+jsToolBar.strings['Ordered list'] = 'Numerazione';
+jsToolBar.strings['Quote'] = 'Aumenta rientro';
+jsToolBar.strings['Unquote'] = 'Riduci rientro';
+jsToolBar.strings['Preformatted text'] = 'Testo preformattato';
+jsToolBar.strings['Wiki link'] = 'Collegamento a pagina Wiki';
+jsToolBar.strings['Image'] = 'Immagine';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-ko.js b/public/javascripts/jstoolbar/lang/jstoolbar-ko.js
index 2d68498f9..1c437ef76 100644
--- a/public/javascripts/jstoolbar/lang/jstoolbar-ko.js
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-ko.js
@@ -1,16 +1,16 @@
jsToolBar.strings = {};
-jsToolBar.strings['Strong'] = 'Strong';
-jsToolBar.strings['Italic'] = 'Italic';
-jsToolBar.strings['Underline'] = 'Underline';
-jsToolBar.strings['Deleted'] = 'Deleted';
-jsToolBar.strings['Code'] = 'Inline Code';
-jsToolBar.strings['Heading 1'] = 'Heading 1';
-jsToolBar.strings['Heading 2'] = 'Heading 2';
-jsToolBar.strings['Heading 3'] = 'Heading 3';
-jsToolBar.strings['Unordered list'] = 'Unordered list';
-jsToolBar.strings['Ordered list'] = 'Ordered list';
-jsToolBar.strings['Quote'] = 'Quote';
-jsToolBar.strings['Unquote'] = 'Remove Quote';
-jsToolBar.strings['Preformatted text'] = 'Preformatted text';
-jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
-jsToolBar.strings['Image'] = 'Image';
+jsToolBar.strings['Strong'] = '굵게';
+jsToolBar.strings['Italic'] = '기울임';
+jsToolBar.strings['Underline'] = '밑줄';
+jsToolBar.strings['Deleted'] = '취소선';
+jsToolBar.strings['Code'] = '코드';
+jsToolBar.strings['Heading 1'] = '제목 1';
+jsToolBar.strings['Heading 2'] = '제목 2';
+jsToolBar.strings['Heading 3'] = '제목 3';
+jsToolBar.strings['Unordered list'] = '글머리 기호';
+jsToolBar.strings['Ordered list'] = '번호 매기기';
+jsToolBar.strings['Quote'] = 'ì¸ìš©';
+jsToolBar.strings['Unquote'] = 'ì¸ìš© 취소';
+jsToolBar.strings['Preformatted text'] = '있는 그대로 표현 (Preformatted text)';
+jsToolBar.strings['Wiki link'] = 'Wiki 페ì´ì§€ì— ì—°ê²°';
+jsToolBar.strings['Image'] = '그림';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-mk.js b/public/javascripts/jstoolbar/lang/jstoolbar-mk.js
new file mode 100644
index 000000000..3af63a1cc
--- /dev/null
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-mk.js
@@ -0,0 +1,16 @@
+jsToolBar.strings = {};
+jsToolBar.strings['Strong'] = 'Strong';
+jsToolBar.strings['Italic'] = 'Italic';
+jsToolBar.strings['Underline'] = 'Underline';
+jsToolBar.strings['Deleted'] = 'Deleted';
+jsToolBar.strings['Code'] = 'Inline Code';
+jsToolBar.strings['Heading 1'] = 'Heading 1';
+jsToolBar.strings['Heading 2'] = 'Heading 2';
+jsToolBar.strings['Heading 3'] = 'Heading 3';
+jsToolBar.strings['Unordered list'] = 'Unordered list';
+jsToolBar.strings['Ordered list'] = 'Подредена лиÑта';
+jsToolBar.strings['Quote'] = 'Цитат';
+jsToolBar.strings['Unquote'] = 'ОтÑтрани цитат';
+jsToolBar.strings['Preformatted text'] = 'Preformatted text';
+jsToolBar.strings['Wiki link'] = 'Линк до вики Ñтрана';
+jsToolBar.strings['Image'] = 'Слика';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-nl.js b/public/javascripts/jstoolbar/lang/jstoolbar-nl.js
index 2d68498f9..0c54163f0 100644
--- a/public/javascripts/jstoolbar/lang/jstoolbar-nl.js
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-nl.js
@@ -1,16 +1,16 @@
jsToolBar.strings = {};
-jsToolBar.strings['Strong'] = 'Strong';
-jsToolBar.strings['Italic'] = 'Italic';
-jsToolBar.strings['Underline'] = 'Underline';
-jsToolBar.strings['Deleted'] = 'Deleted';
-jsToolBar.strings['Code'] = 'Inline Code';
-jsToolBar.strings['Heading 1'] = 'Heading 1';
-jsToolBar.strings['Heading 2'] = 'Heading 2';
-jsToolBar.strings['Heading 3'] = 'Heading 3';
-jsToolBar.strings['Unordered list'] = 'Unordered list';
-jsToolBar.strings['Ordered list'] = 'Ordered list';
-jsToolBar.strings['Quote'] = 'Quote';
-jsToolBar.strings['Unquote'] = 'Remove Quote';
-jsToolBar.strings['Preformatted text'] = 'Preformatted text';
-jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
-jsToolBar.strings['Image'] = 'Image';
+jsToolBar.strings['Strong'] = 'Extra nadruk';
+jsToolBar.strings['Italic'] = 'Cursief';
+jsToolBar.strings['Underline'] = 'Onderstreept';
+jsToolBar.strings['Deleted'] = 'Verwijderd';
+jsToolBar.strings['Code'] = 'Computercode';
+jsToolBar.strings['Heading 1'] = 'Kop 1';
+jsToolBar.strings['Heading 2'] = 'Kop 2';
+jsToolBar.strings['Heading 3'] = 'Kop 3';
+jsToolBar.strings['Unordered list'] = 'Ongeordende lijst';
+jsToolBar.strings['Ordered list'] = 'Geordende lijst';
+jsToolBar.strings['Quote'] = 'Citaat';
+jsToolBar.strings['Unquote'] = 'Verwijder citaat';
+jsToolBar.strings['Preformatted text'] = 'Voor-geformateerde tekst';
+jsToolBar.strings['Wiki link'] = 'Link naar een Wiki pagina';
+jsToolBar.strings['Image'] = 'Afbeelding';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-pl.js b/public/javascripts/jstoolbar/lang/jstoolbar-pl.js
index 2d68498f9..0e7a38c90 100644
--- a/public/javascripts/jstoolbar/lang/jstoolbar-pl.js
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-pl.js
@@ -1,16 +1,17 @@
+// Keep this line in order to avoid problems with Windows Notepad UTF-8 EF-BB-BF idea...
jsToolBar.strings = {};
-jsToolBar.strings['Strong'] = 'Strong';
-jsToolBar.strings['Italic'] = 'Italic';
-jsToolBar.strings['Underline'] = 'Underline';
-jsToolBar.strings['Deleted'] = 'Deleted';
-jsToolBar.strings['Code'] = 'Inline Code';
-jsToolBar.strings['Heading 1'] = 'Heading 1';
-jsToolBar.strings['Heading 2'] = 'Heading 2';
-jsToolBar.strings['Heading 3'] = 'Heading 3';
-jsToolBar.strings['Unordered list'] = 'Unordered list';
-jsToolBar.strings['Ordered list'] = 'Ordered list';
-jsToolBar.strings['Quote'] = 'Quote';
-jsToolBar.strings['Unquote'] = 'Remove Quote';
-jsToolBar.strings['Preformatted text'] = 'Preformatted text';
-jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
-jsToolBar.strings['Image'] = 'Image';
+jsToolBar.strings['Strong'] = 'Pogrubienie';
+jsToolBar.strings['Italic'] = 'Kursywa';
+jsToolBar.strings['Underline'] = 'Podkreślenie';
+jsToolBar.strings['Deleted'] = 'Usunięte';
+jsToolBar.strings['Code'] = 'Wstawka kodu';
+jsToolBar.strings['Heading 1'] = 'Nagłowek 1';
+jsToolBar.strings['Heading 2'] = 'Nagłówek 2';
+jsToolBar.strings['Heading 3'] = 'Nagłówek 3';
+jsToolBar.strings['Unordered list'] = 'Nieposortowana lista';
+jsToolBar.strings['Ordered list'] = 'Posortowana lista';
+jsToolBar.strings['Quote'] = 'Cytat';
+jsToolBar.strings['Unquote'] = 'Usuń cytat';
+jsToolBar.strings['Preformatted text'] = 'Sformatowany tekst';
+jsToolBar.strings['Wiki link'] = 'Odnośnik do strony Wiki';
+jsToolBar.strings['Image'] = 'Obraz';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-pt.js b/public/javascripts/jstoolbar/lang/jstoolbar-pt.js
index 2d68498f9..137d7952a 100644
--- a/public/javascripts/jstoolbar/lang/jstoolbar-pt.js
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-pt.js
@@ -1,16 +1,17 @@
+// Translated by: Pedro Araújo <phcrva19@hotmail.com>
jsToolBar.strings = {};
-jsToolBar.strings['Strong'] = 'Strong';
-jsToolBar.strings['Italic'] = 'Italic';
-jsToolBar.strings['Underline'] = 'Underline';
-jsToolBar.strings['Deleted'] = 'Deleted';
-jsToolBar.strings['Code'] = 'Inline Code';
-jsToolBar.strings['Heading 1'] = 'Heading 1';
-jsToolBar.strings['Heading 2'] = 'Heading 2';
-jsToolBar.strings['Heading 3'] = 'Heading 3';
-jsToolBar.strings['Unordered list'] = 'Unordered list';
-jsToolBar.strings['Ordered list'] = 'Ordered list';
-jsToolBar.strings['Quote'] = 'Quote';
-jsToolBar.strings['Unquote'] = 'Remove Quote';
-jsToolBar.strings['Preformatted text'] = 'Preformatted text';
-jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
-jsToolBar.strings['Image'] = 'Image';
+jsToolBar.strings['Strong'] = 'Negrito';
+jsToolBar.strings['Italic'] = 'Itálico';
+jsToolBar.strings['Underline'] = 'Sublinhado';
+jsToolBar.strings['Deleted'] = 'Apagado';
+jsToolBar.strings['Code'] = 'Código Inline';
+jsToolBar.strings['Heading 1'] = 'Cabeçalho 1';
+jsToolBar.strings['Heading 2'] = 'Cabeçalho 2';
+jsToolBar.strings['Heading 3'] = 'Cabeçalho 3';
+jsToolBar.strings['Unordered list'] = 'Lista não ordenada';
+jsToolBar.strings['Ordered list'] = 'Lista ordenada';
+jsToolBar.strings['Quote'] = 'Citação';
+jsToolBar.strings['Unquote'] = 'Remover citação';
+jsToolBar.strings['Preformatted text'] = 'Texto pré-formatado';
+jsToolBar.strings['Wiki link'] = 'Link para uma página da Wiki';
+jsToolBar.strings['Image'] = 'Imagem';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-sk.js b/public/javascripts/jstoolbar/lang/jstoolbar-sk.js
new file mode 100644
index 000000000..0d47cd516
--- /dev/null
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-sk.js
@@ -0,0 +1,16 @@
+jsToolBar.strings = {};
+jsToolBar.strings['Strong'] = 'TuÄné';
+jsToolBar.strings['Italic'] = 'Kurzíva';
+jsToolBar.strings['Underline'] = 'PodÄiarknuté';
+jsToolBar.strings['Deleted'] = 'Preškrtnuté';
+jsToolBar.strings['Code'] = 'Zobrazenie kódu';
+jsToolBar.strings['Heading 1'] = 'Záhlavie 1';
+jsToolBar.strings['Heading 2'] = 'Záhlavie 2';
+jsToolBar.strings['Heading 3'] = 'Záhlavie 3';
+jsToolBar.strings['Unordered list'] = 'Zoznam';
+jsToolBar.strings['Ordered list'] = 'Zoradený zoznam';
+jsToolBar.strings['Quote'] = 'Citácia';
+jsToolBar.strings['Unquote'] = 'Odstránenie citácie';
+jsToolBar.strings['Preformatted text'] = 'Predformátovaný text';
+jsToolBar.strings['Wiki link'] = 'Link na Wiki stránku';
+jsToolBar.strings['Image'] = 'Obrázok';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-sl.js b/public/javascripts/jstoolbar/lang/jstoolbar-sl.js
new file mode 100644
index 000000000..70949957e
--- /dev/null
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-sl.js
@@ -0,0 +1,16 @@
+jsToolBar.strings = {};
+jsToolBar.strings['Strong'] = 'Krepko';
+jsToolBar.strings['Italic'] = 'Poševno';
+jsToolBar.strings['Underline'] = 'PodÄrtano';
+jsToolBar.strings['Deleted'] = 'Izbrisano';
+jsToolBar.strings['Code'] = 'Koda med vrsticami';
+jsToolBar.strings['Heading 1'] = 'Naslov 1';
+jsToolBar.strings['Heading 2'] = 'Naslov 2';
+jsToolBar.strings['Heading 3'] = 'Naslov 3';
+jsToolBar.strings['Unordered list'] = 'Neurejen seznam';
+jsToolBar.strings['Ordered list'] = 'Urejen seznam';
+jsToolBar.strings['Quote'] = 'Citat';
+jsToolBar.strings['Unquote'] = 'Odstrani citat';
+jsToolBar.strings['Preformatted text'] = 'Predoblikovano besedilo';
+jsToolBar.strings['Wiki link'] = 'Povezava na Wiki stran';
+jsToolBar.strings['Image'] = 'Slika';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-sv.js b/public/javascripts/jstoolbar/lang/jstoolbar-sv.js
index 2d68498f9..08c0b69a3 100644
--- a/public/javascripts/jstoolbar/lang/jstoolbar-sv.js
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-sv.js
@@ -1,16 +1,16 @@
jsToolBar.strings = {};
-jsToolBar.strings['Strong'] = 'Strong';
-jsToolBar.strings['Italic'] = 'Italic';
-jsToolBar.strings['Underline'] = 'Underline';
-jsToolBar.strings['Deleted'] = 'Deleted';
-jsToolBar.strings['Code'] = 'Inline Code';
-jsToolBar.strings['Heading 1'] = 'Heading 1';
-jsToolBar.strings['Heading 2'] = 'Heading 2';
-jsToolBar.strings['Heading 3'] = 'Heading 3';
-jsToolBar.strings['Unordered list'] = 'Unordered list';
-jsToolBar.strings['Ordered list'] = 'Ordered list';
-jsToolBar.strings['Quote'] = 'Quote';
-jsToolBar.strings['Unquote'] = 'Remove Quote';
-jsToolBar.strings['Preformatted text'] = 'Preformatted text';
-jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
-jsToolBar.strings['Image'] = 'Image';
+jsToolBar.strings['Strong'] = 'Fet';
+jsToolBar.strings['Italic'] = 'Kursiv';
+jsToolBar.strings['Underline'] = 'Understruken';
+jsToolBar.strings['Deleted'] = 'Genomstruken';
+jsToolBar.strings['Code'] = 'Kod';
+jsToolBar.strings['Heading 1'] = 'Rubrik 1';
+jsToolBar.strings['Heading 2'] = 'Rubrik 2';
+jsToolBar.strings['Heading 3'] = 'Rubrik 3';
+jsToolBar.strings['Unordered list'] = 'Osorterad lista';
+jsToolBar.strings['Ordered list'] = 'Sorterad lista';
+jsToolBar.strings['Quote'] = 'Citat';
+jsToolBar.strings['Unquote'] = 'Ta bort citat';
+jsToolBar.strings['Preformatted text'] = 'Förformaterad text';
+jsToolBar.strings['Wiki link'] = 'Länk till en wikisida';
+jsToolBar.strings['Image'] = 'Bild';
diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-vn.js b/public/javascripts/jstoolbar/lang/jstoolbar-vn.js
new file mode 100644
index 000000000..f598bfe0a
--- /dev/null
+++ b/public/javascripts/jstoolbar/lang/jstoolbar-vn.js
@@ -0,0 +1,16 @@
+jsToolBar.strings = {};
+jsToolBar.strings['Strong'] = 'Äậm';
+jsToolBar.strings['Italic'] = 'Nghiêng';
+jsToolBar.strings['Underline'] = 'Gạch chân';
+jsToolBar.strings['Deleted'] = 'Xóa';
+jsToolBar.strings['Code'] = 'Mã chung dòng';
+jsToolBar.strings['Heading 1'] = 'Tiêu đỠ1';
+jsToolBar.strings['Heading 2'] = 'Tiêu đỠ2';
+jsToolBar.strings['Heading 3'] = 'Tiêu đỠ3';
+jsToolBar.strings['Unordered list'] = 'Danh sách không thứ tự';
+jsToolBar.strings['Ordered list'] = 'Danh sách có thứ tự';
+jsToolBar.strings['Quote'] = 'Trích dẫn';
+jsToolBar.strings['Unquote'] = 'BỠtrích dẫn';
+jsToolBar.strings['Preformatted text'] = 'Mã nguồn';
+jsToolBar.strings['Wiki link'] = 'Liên kết đến trang wiki';
+jsToolBar.strings['Image'] = 'Ảnh';
diff --git a/public/javascripts/jstoolbar/textile.js b/public/javascripts/jstoolbar/textile.js
new file mode 100644
index 000000000..c461b9d2e
--- /dev/null
+++ b/public/javascripts/jstoolbar/textile.js
@@ -0,0 +1,200 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * This file is part of DotClear.
+ * Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All
+ * rights reserved.
+ *
+ * DotClear 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.
+ *
+ * DotClear 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 DotClear; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ***** END LICENSE BLOCK *****
+*/
+
+/* Modified by JP LANG for textile formatting */
+
+// strong
+jsToolBar.prototype.elements.strong = {
+ type: 'button',
+ title: 'Strong',
+ fn: {
+ wiki: function() { this.singleTag('*') }
+ }
+}
+
+// em
+jsToolBar.prototype.elements.em = {
+ type: 'button',
+ title: 'Italic',
+ fn: {
+ wiki: function() { this.singleTag("_") }
+ }
+}
+
+// ins
+jsToolBar.prototype.elements.ins = {
+ type: 'button',
+ title: 'Underline',
+ fn: {
+ wiki: function() { this.singleTag('+') }
+ }
+}
+
+// del
+jsToolBar.prototype.elements.del = {
+ type: 'button',
+ title: 'Deleted',
+ fn: {
+ wiki: function() { this.singleTag('-') }
+ }
+}
+
+// code
+jsToolBar.prototype.elements.code = {
+ type: 'button',
+ title: 'Code',
+ fn: {
+ wiki: function() { this.singleTag('@') }
+ }
+}
+
+// spacer
+jsToolBar.prototype.elements.space1 = {type: 'space'}
+
+// headings
+jsToolBar.prototype.elements.h1 = {
+ type: 'button',
+ title: 'Heading 1',
+ fn: {
+ wiki: function() {
+ this.encloseLineSelection('h1. ', '',function(str) {
+ str = str.replace(/^h\d+\.\s+/, '')
+ return str;
+ });
+ }
+ }
+}
+jsToolBar.prototype.elements.h2 = {
+ type: 'button',
+ title: 'Heading 2',
+ fn: {
+ wiki: function() {
+ this.encloseLineSelection('h2. ', '',function(str) {
+ str = str.replace(/^h\d+\.\s+/, '')
+ return str;
+ });
+ }
+ }
+}
+jsToolBar.prototype.elements.h3 = {
+ type: 'button',
+ title: 'Heading 3',
+ fn: {
+ wiki: function() {
+ this.encloseLineSelection('h3. ', '',function(str) {
+ str = str.replace(/^h\d+\.\s+/, '')
+ return str;
+ });
+ }
+ }
+}
+
+// spacer
+jsToolBar.prototype.elements.space2 = {type: 'space'}
+
+// ul
+jsToolBar.prototype.elements.ul = {
+ type: 'button',
+ title: 'Unordered list',
+ fn: {
+ wiki: function() {
+ this.encloseLineSelection('','',function(str) {
+ str = str.replace(/\r/g,'');
+ return str.replace(/(\n|^)[#-]?\s*/g,"$1* ");
+ });
+ }
+ }
+}
+
+// ol
+jsToolBar.prototype.elements.ol = {
+ type: 'button',
+ title: 'Ordered list',
+ fn: {
+ wiki: function() {
+ this.encloseLineSelection('','',function(str) {
+ str = str.replace(/\r/g,'');
+ return str.replace(/(\n|^)[*-]?\s*/g,"$1# ");
+ });
+ }
+ }
+}
+
+// spacer
+jsToolBar.prototype.elements.space3 = {type: 'space'}
+
+// bq
+jsToolBar.prototype.elements.bq = {
+ type: 'button',
+ title: 'Quote',
+ fn: {
+ wiki: function() {
+ this.encloseLineSelection('','',function(str) {
+ str = str.replace(/\r/g,'');
+ return str.replace(/(\n|^) *([^\n]*)/g,"$1> $2");
+ });
+ }
+ }
+}
+
+// unbq
+jsToolBar.prototype.elements.unbq = {
+ type: 'button',
+ title: 'Unquote',
+ fn: {
+ wiki: function() {
+ this.encloseLineSelection('','',function(str) {
+ str = str.replace(/\r/g,'');
+ return str.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2");
+ });
+ }
+ }
+}
+
+// pre
+jsToolBar.prototype.elements.pre = {
+ type: 'button',
+ title: 'Preformatted text',
+ fn: {
+ wiki: function() { this.encloseLineSelection('<pre>\n', '\n</pre>') }
+ }
+}
+
+// spacer
+jsToolBar.prototype.elements.space4 = {type: 'space'}
+
+// wiki page
+jsToolBar.prototype.elements.link = {
+ type: 'button',
+ title: 'Wiki link',
+ fn: {
+ wiki: function() { this.encloseSelection("[[", "]]") }
+ }
+}
+// image
+jsToolBar.prototype.elements.img = {
+ type: 'button',
+ title: 'Image',
+ fn: {
+ wiki: function() { this.encloseSelection("!", "!") }
+ }
+}
diff --git a/public/robots.txt b/public/robots.txt
deleted file mode 100644
index 5fb2d1597..000000000
--- a/public/robots.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-User-agent: *
-Disallow: /projects/gantt
-Disallow: /projects/calendar
-Disallow: /repositories/diff
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 8c4173413..653ae152f 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -18,7 +18,7 @@ h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; bord
padding: 0px 0px 0px 0px;
white-space:nowrap;
}
-#top-menu a {color: #fff; padding-right: 8px; font-weight: bold;}
+#top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
#top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
#account {float:right;}
@@ -55,9 +55,10 @@ h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; bord
#sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
* html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
-#content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
+#content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
* html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
-html>body #content { height: auto; min-height: 600px; overflow: auto; }
+html>body #content { min-height: 600px; }
+* html body #content { height: 600px; } /* IE */
#main.nosidebar #sidebar{ display: none; }
#main.nosidebar #content{ width: auto; border-right: 0; }
@@ -68,6 +69,8 @@ html>body #content { height: auto; min-height: 600px; overflow: auto; }
#login-form table td {padding: 6px;}
#login-form label {font-weight: bold;}
+input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
+
.clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
/***** Links *****/
@@ -84,8 +87,11 @@ table.list td { vertical-align: top; }
table.list td.id { width: 2%; text-align: center;}
table.list td.checkbox { width: 15px; padding: 0px;}
+tr.project td.name a { padding-left: 16px; white-space:nowrap; }
+tr.project.parent td.name a { background: url('../images/bullet_toggle_minus.png') no-repeat; }
+
tr.issue { text-align: center; white-space: nowrap; }
-tr.issue td.subject, tr.issue td.category { white-space: normal; }
+tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; }
tr.issue td.subject { text-align: left; }
tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
@@ -119,6 +125,12 @@ tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-sp
td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
td.hours .hours-dec { font-size: 0.9em; }
+table.plugins td { vertical-align: middle; }
+table.plugins td.configure { text-align: right; padding-right: 1em; }
+table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
+table.plugins span.description { display: block; font-size: 0.9em; }
+table.plugins span.url { display: block; font-size: 0.9em; }
+
table.list tbody tr:hover { background-color:#ffffdd; }
table td {padding:2px;}
table p {margin:0;}
@@ -148,6 +160,7 @@ div.square {
}
.contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
.contextual input {font-size:0.9em;}
+.message .contextual { margin-top: 0; }
.splitcontentleft{float:left; width:49%;}
.splitcontentright{float:right; width:49%;}
@@ -163,6 +176,7 @@ li p {margin-top: 0;}
div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
+p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
fieldset#filters, fieldset#date-range { padding: 0.7em; margin-bottom: 8px; }
fieldset#filters p { margin: 1.2em 0 0.8em 2px; }
@@ -184,9 +198,10 @@ div#activity dt.me .time { border-bottom: 1px solid #999; }
div#activity dt .time { color: #777; font-size: 80%; }
div#activity dd .description, #search-results dd .description { font-style: italic; }
div#activity span.project:after, #search-results span.project:after { content: " -"; }
-div#activity dd span.description, #search-results dd span.description { display:block; }
+div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
#search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
+
div#search-results-counts {float:right;}
div#search-results-counts ul { margin-top: 0.5em; }
div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
@@ -204,6 +219,8 @@ dt.attachment { background-image: url(../images/attachment.png); }
dt.document { background-image: url(../images/document.png); }
dt.project { background-image: url(../images/projects.png); }
+#search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
+
div#roadmap fieldset.related-issues { margin-bottom: 1em; }
div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
div#roadmap .wiki h1:first-child { display: none; }
@@ -220,6 +237,22 @@ table#time-report tbody tr.last-level { font-style: normal; color: #555; }
table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
table#time-report .hours-dec { font-size: 0.9em; }
+form#issue-form .attributes { margin-bottom: 8px; }
+form#issue-form .attributes p { padding-top: 1px; padding-bottom: 2px; }
+form#issue-form .attributes select { min-width: 30%; }
+
+ul.projects { margin: 0; padding-left: 1em; }
+ul.projects.root { margin: 0; padding: 0; }
+ul.projects ul { border-left: 3px solid #e0e0e0; }
+ul.projects li { list-style-type:none; }
+ul.projects li.root { margin-bottom: 1em; }
+ul.projects li.child { margin-top: 1em;}
+ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
+.my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
+
+#tracker_project_ids ul { margin: 0; padding-left: 1em; }
+#tracker_project_ids li { list-style-type:none; }
+
ul.properties {padding:0; font-size: 0.9em; color: #777;}
ul.properties li {list-style-type:none;}
ul.properties li span {font-style:italic;}
@@ -257,7 +290,7 @@ margin*/
font-weight: normal;
margin-left: 0px;
text-align: left;
-width: 200px;
+width: 270px;
}
input#time_entry_comments { width: 90%;}
@@ -272,6 +305,7 @@ input#time_entry_comments { width: 90%;}
#attachments_fields input[type=text] {margin-left: 8px; }
+div.attachments { margin-top: 12px; }
div.attachments p { margin:4px 0 2px 0; }
div.attachments img { vertical-align: middle; }
div.attachments span.author { font-size: 0.9em; color: #888; }
@@ -279,7 +313,7 @@ div.attachments span.author { font-size: 0.9em; color: #888; }
p.other-formats { text-align: right; font-size:0.9em; color: #666; }
.other-formats span + span:before { content: "| "; }
-a.feed { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
+a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
/***** Flash & error messages ****/
#errorExplanation, div.flash, .nodata, .warning {
@@ -305,6 +339,14 @@ div.flash.notice {
color: #005f00;
}
+div.flash.warning {
+ background: url(../images/warning.png) 8px 5px no-repeat;
+ background-color: #FFEBC1;
+ border-color: #FDBF3B;
+ color: #A6750C;
+ text-align: left;
+}
+
.nodata, .warning {
text-align: center;
background-color: #FFEBC1;
@@ -587,6 +629,7 @@ vertical-align: middle;
.icon-index { background-image: url(../images/index.png); }
.icon-history { background-image: url(../images/history.png); }
.icon-time { background-image: url(../images/time.png); }
+.icon-time-add { background-image: url(../images/time_add.png); }
.icon-stats { background-image: url(../images/stats.png); }
.icon-warning { background-image: url(../images/warning.png); }
.icon-fav { background-image: url(../images/fav.png); }
@@ -613,9 +656,53 @@ vertical-align: middle;
.icon22-settings { background-image: url(../images/22x22/settings.png); }
.icon22-plugin { background-image: url(../images/22x22/plugin.png); }
+img.gravatar {
+ padding: 2px;
+ border: solid 1px #d5d5d5;
+ background: #fff;
+}
+
+div.issue img.gravatar {
+ float: right;
+ margin: 0 0 0 1em;
+ padding: 5px;
+}
+
+div.issue table img.gravatar {
+ height: 14px;
+ width: 14px;
+ padding: 2px;
+ float: left;
+ margin: 0 0.5em 0 0;
+}
+
+#history img.gravatar {
+ padding: 3px;
+ margin: 0 1.5em 1em 0;
+ float: left;
+}
+
+td.username img.gravatar {
+ float: left;
+ margin: 0 1em 0 0;
+}
+
+#activity dt img.gravatar {
+ float: left;
+ margin: 0 1em 1em 0;
+}
+
+#activity dt,
+.journal {
+ clear: left;
+}
+
+h2 img { vertical-align:middle; }
+
+
/***** Media print specific styles *****/
@media print {
#top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
#main { background: #fff; }
- #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
+ #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
}
diff --git a/public/themes/classic/stylesheets/application.css b/public/themes/classic/stylesheets/application.css
index 57f911da6..280b86edd 100644
--- a/public/themes/classic/stylesheets/application.css
+++ b/public/themes/classic/stylesheets/application.css
@@ -26,7 +26,7 @@ h2, h3, h4, .wiki h1, .wiki h2, .wiki h3 { border-bottom: 0px; color:#606060; fo
h2, .wiki h1 { letter-spacing:-1px; }
h4 { border-bottom: dotted 1px #c0c0c0; }
-#top-menu a.home, #top-menu a.mypage, #top-menu a.projects, #top-menu a.admin, #top-menu a.help {
+#top-menu a.home, #top-menu a.my-page, #top-menu a.projects, #top-menu a.administration, #top-menu a.help {
background-position: 0% 40%;
background-repeat: no-repeat;
padding-left: 20px;
@@ -35,7 +35,7 @@ h4 { border-bottom: dotted 1px #c0c0c0; }
}
#top-menu a.home { background-image: url(../../../images/home.png); }
-#top-menu a.mypage { background-image: url(../../../images/user_page.png); }
+#top-menu a.my-page { background-image: url(../../../images/user_page.png); }
#top-menu a.projects { background-image: url(../../../images/projects.png); }
-#top-menu a.admin { background-image: url(../../../images/admin.png); }
+#top-menu a.administration { background-image: url(../../../images/admin.png); }
#top-menu a.help { background-image: url(../../../images/help.png); }
diff --git a/test/fixtures/attachments.yml b/test/fixtures/attachments.yml
index ec57aa6dd..2497bd9a3 100644
--- a/test/fixtures/attachments.yml
+++ b/test/fixtures/attachments.yml
@@ -85,4 +85,28 @@ attachments_007:
filename: archive.zip
author_id: 1
content_type: application/octet-stream
+attachments_008:
+ created_on: 2006-07-19 21:07:27 +02:00
+ container_type: Project
+ container_id: 1
+ downloads: 0
+ disk_filename: 060719210727_project_file.zip
+ digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
+ id: 8
+ filesize: 320
+ filename: project_file.zip
+ author_id: 2
+ content_type: application/octet-stream
+attachments_009:
+ created_on: 2006-07-19 21:07:27 +02:00
+ container_type: Version
+ container_id: 1
+ downloads: 0
+ disk_filename: 060719210727_version_file.zip
+ digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
+ id: 9
+ filesize: 452
+ filename: version_file.zip
+ author_id: 2
+ content_type: application/octet-stream
\ No newline at end of file
diff --git a/test/fixtures/changesets.yml b/test/fixtures/changesets.yml
index 3b47eecd8..cbc00eb83 100644
--- a/test/fixtures/changesets.yml
+++ b/test/fixtures/changesets.yml
@@ -7,6 +7,7 @@ changesets_001:
comments: My very first commit
repository_id: 10
committer: dlopper
+ user_id: 3
changesets_002:
commit_date: 2007-04-12
committed_on: 2007-04-12 15:14:44 +02:00
@@ -15,6 +16,7 @@ changesets_002:
comments: 'This commit fixes #1, #2 and references #1 & #3'
repository_id: 10
committer: dlopper
+ user_id: 3
changesets_003:
commit_date: 2007-04-12
committed_on: 2007-04-12 15:14:44 +02:00
@@ -25,6 +27,7 @@ changesets_003:
IssueID 666 3
repository_id: 10
committer: dlopper
+ user_id: 3
changesets_004:
commit_date: 2007-04-12
committed_on: 2007-04-12 15:14:44 +02:00
@@ -35,4 +38,5 @@ changesets_004:
IssueID 4 2
repository_id: 10
committer: dlopper
+ user_id: 3
\ No newline at end of file
diff --git a/test/fixtures/custom_fields.yml b/test/fixtures/custom_fields.yml
index 1005edae4..a17827473 100644
--- a/test/fixtures/custom_fields.yml
+++ b/test/fixtures/custom_fields.yml
@@ -15,6 +15,7 @@ custom_fields_001:
is_required: false
field_format: list
default_value: ""
+ editable: true
custom_fields_002:
name: Searchable field
min_length: 1
@@ -28,6 +29,7 @@ custom_fields_002:
field_format: string
searchable: true
default_value: "Default string"
+ editable: true
custom_fields_003:
name: Development status
min_length: 0
@@ -45,6 +47,7 @@ custom_fields_003:
is_required: true
field_format: list
default_value: ""
+ editable: true
custom_fields_004:
name: Phone number
min_length: 0
@@ -57,6 +60,7 @@ custom_fields_004:
is_required: false
field_format: string
default_value: ""
+ editable: true
custom_fields_005:
name: Money
min_length: 0
@@ -69,4 +73,18 @@ custom_fields_005:
is_required: false
field_format: float
default_value: ""
+ editable: true
+custom_fields_006:
+ name: Float field
+ min_length: 0
+ regexp: ""
+ is_for_all: true
+ type: IssueCustomField
+ max_length: 0
+ possible_values: ""
+ id: 6
+ is_required: false
+ field_format: float
+ default_value: ""
+ editable: true
\ No newline at end of file
diff --git a/test/fixtures/custom_fields_trackers.yml b/test/fixtures/custom_fields_trackers.yml
index cb06d2fcf..bfbe0d24c 100644
--- a/test/fixtures/custom_fields_trackers.yml
+++ b/test/fixtures/custom_fields_trackers.yml
@@ -8,3 +8,12 @@ custom_fields_trackers_002:
custom_fields_trackers_003:
custom_field_id: 2
tracker_id: 3
+custom_fields_trackers_004:
+ custom_field_id: 6
+ tracker_id: 1
+custom_fields_trackers_005:
+ custom_field_id: 6
+ tracker_id: 2
+custom_fields_trackers_006:
+ custom_field_id: 6
+ tracker_id: 3
diff --git a/test/fixtures/custom_values.yml b/test/fixtures/custom_values.yml
index 572142889..0e2b454ab 100644
--- a/test/fixtures/custom_values.yml
+++ b/test/fixtures/custom_values.yml
@@ -3,54 +3,84 @@ custom_values_006:
customized_type: Issue
custom_field_id: 2
customized_id: 3
- id: 9
+ id: 6
value: "125"
custom_values_007:
customized_type: Project
custom_field_id: 3
customized_id: 1
- id: 10
+ id: 7
value: Stable
custom_values_001:
customized_type: User
custom_field_id: 4
customized_id: 3
- id: 2
+ id: 1
value: ""
custom_values_002:
customized_type: User
custom_field_id: 4
customized_id: 4
- id: 3
+ id: 2
value: 01 23 45 67 89
custom_values_003:
customized_type: User
custom_field_id: 4
customized_id: 2
- id: 4
+ id: 3
value: ""
custom_values_004:
customized_type: Issue
custom_field_id: 2
customized_id: 1
- id: 7
+ id: 4
value: "125"
custom_values_005:
customized_type: Issue
custom_field_id: 2
customized_id: 2
- id: 8
+ id: 5
value: ""
custom_values_008:
customized_type: Issue
custom_field_id: 1
customized_id: 3
- id: 11
+ id: 8
value: "MySQL"
custom_values_009:
customized_type: Issue
custom_field_id: 2
customized_id: 3
- id: 12
+ id: 9
value: "this is a stringforcustomfield search"
+custom_values_010:
+ customized_type: Issue
+ custom_field_id: 6
+ customized_id: 1
+ id: 10
+ value: "2.1"
+custom_values_011:
+ customized_type: Issue
+ custom_field_id: 6
+ customized_id: 2
+ id: 11
+ value: "2.05"
+custom_values_012:
+ customized_type: Issue
+ custom_field_id: 6
+ customized_id: 3
+ id: 12
+ value: "11.65"
+custom_values_013:
+ customized_type: Issue
+ custom_field_id: 6
+ customized_id: 7
+ id: 13
+ value: ""
+custom_values_014:
+ customized_type: Issue
+ custom_field_id: 6
+ customized_id: 5
+ id: 14
+ value: "-7.6"
\ No newline at end of file
diff --git a/test/fixtures/diffs/subversion.diff b/test/fixtures/diffs/subversion.diff
new file mode 100644
index 000000000..9b6c9d086
--- /dev/null
+++ b/test/fixtures/diffs/subversion.diff
@@ -0,0 +1,79 @@
+Index: app/views/settings/_general.rhtml
+===================================================================
+--- app/views/settings/_general.rhtml (revision 2094)
++++ app/views/settings/_general.rhtml (working copy)
+@@ -48,6 +48,9 @@
+ <p><label><%= l(:setting_feeds_limit) %></label>
+ <%= text_field_tag 'settings[feeds_limit]', Setting.feeds_limit, :size => 6 %></p>
+
++<p><label><%= l(:setting_diff_max_lines_displayed) %></label>
++<%= text_field_tag 'settings[diff_max_lines_displayed]', Setting.diff_max_lines_displayed, :size => 6 %></p>
++
+ <p><label><%= l(:setting_gravatar_enabled) %></label>
+ <%= check_box_tag 'settings[gravatar_enabled]', 1, Setting.gravatar_enabled? %><%= hidden_field_tag 'settings[gravatar_enabled]', 0 %></p>
+ </div>
+Index: app/views/common/_diff.rhtml
+===================================================================
+--- app/views/common/_diff.rhtml (revision 2111)
++++ app/views/common/_diff.rhtml (working copy)
+@@ -1,4 +1,5 @@
+-<% Redmine::UnifiedDiff.new(diff, :type => diff_type).each do |table_file| -%>
++<% diff = Redmine::UnifiedDiff.new(diff, :type => diff_type, :max_lines => Setting.diff_max_lines_displayed.to_i) -%>
++<% diff.each do |table_file| -%>
+ <div class="autoscroll">
+ <% if diff_type == 'sbs' -%>
+ <table class="filecontent CodeRay">
+@@ -62,3 +63,5 @@
+
+ </div>
+ <% end -%>
++
++<%= l(:text_diff_truncated) if diff.truncated? %>
+Index: lang/lt.yml
+===================================================================
+--- config/settings.yml (revision 2094)
++++ config/settings.yml (working copy)
+@@ -61,6 +61,9 @@
+ feeds_limit:
+ format: int
+ default: 15
++diff_max_lines_displayed:
++ format: int
++ default: 1500
+ enabled_scm:
+ serialized: true
+ default:
+Index: lib/redmine/unified_diff.rb
+===================================================================
+--- lib/redmine/unified_diff.rb (revision 2110)
++++ lib/redmine/unified_diff.rb (working copy)
+@@ -19,8 +19,11 @@
+ # Class used to parse unified diffs
+ class UnifiedDiff < Array
+ def initialize(diff, options={})
++ options.assert_valid_keys(:type, :max_lines)
+ diff_type = options[:type] || 'inline'
+
++ lines = 0
++ @truncated = false
+ diff_table = DiffTable.new(diff_type)
+ diff.each do |line|
+ if line =~ /^(---|\+\+\+) (.*)$/
+@@ -28,10 +31,17 @@
+ diff_table = DiffTable.new(diff_type)
+ end
+ diff_table.add_line line
++ lines += 1
++ if options[:max_lines] && lines > options[:max_lines]
++ @truncated = true
++ break
++ end
+ end
+ self << diff_table unless diff_table.empty?
+ self
+ end
++
++ def truncated?; @truncated; end
+ end
+
+ # Class that represents a file diff
diff --git a/test/fixtures/enabled_modules.yml b/test/fixtures/enabled_modules.yml
index da63bad5d..6639dfa1a 100644
--- a/test/fixtures/enabled_modules.yml
+++ b/test/fixtures/enabled_modules.yml
@@ -43,4 +43,16 @@ enabled_modules_011:
name: issue_tracking
project_id: 2
id: 11
+enabled_modules_012:
+ name: time_tracking
+ project_id: 3
+ id: 12
+enabled_modules_013:
+ name: issue_tracking
+ project_id: 3
+ id: 13
+enabled_modules_014:
+ name: issue_tracking
+ project_id: 5
+ id: 14
\ No newline at end of file
diff --git a/test/fixtures/files/testfile.txt b/test/fixtures/files/testfile.txt
index 4b2a49c69..84f601ee6 100644
--- a/test/fixtures/files/testfile.txt
+++ b/test/fixtures/files/testfile.txt
@@ -1 +1,2 @@
-this is a text file for upload tests \ No newline at end of file
+this is a text file for upload tests
+with multiple lines
diff --git a/test/fixtures/issues.yml b/test/fixtures/issues.yml
index 9d3287c6f..921ba40c4 100644
--- a/test/fixtures/issues.yml
+++ b/test/fixtures/issues.yml
@@ -91,4 +91,38 @@ issues_006:
status_id: 1
start_date: <%= Date.today.to_s(:db) %>
due_date: <%= 1.days.from_now.to_date.to_s(:db) %>
+issues_007:
+ created_on: <%= 10.days.ago.to_date.to_s(:db) %>
+ project_id: 1
+ updated_on: <%= 10.days.ago.to_date.to_s(:db) %>
+ priority_id: 3
+ subject: Issue due today
+ id: 7
+ fixed_version_id:
+ category_id:
+ description: This is an issue that is due today
+ tracker_id: 1
+ assigned_to_id:
+ author_id: 2
+ status_id: 1
+ start_date: <%= 10.days.ago.to_s(:db) %>
+ due_date: <%= Date.today.to_s(:db) %>
+ lock_version: 0
+issues_008:
+ created_on: <%= 10.days.ago.to_date.to_s(:db) %>
+ project_id: 1
+ updated_on: <%= 10.days.ago.to_date.to_s(:db) %>
+ priority_id: 3
+ subject: Closed issue
+ id: 8
+ fixed_version_id:
+ category_id:
+ description: This is a closed issue.
+ tracker_id: 1
+ assigned_to_id:
+ author_id: 2
+ status_id: 5
+ start_date:
+ due_date:
+ lock_version: 0
\ No newline at end of file
diff --git a/test/fixtures/mail_handler/message_reply.eml b/test/fixtures/mail_handler/message_reply.eml
new file mode 100644
index 000000000..a2ef8ee14
--- /dev/null
+++ b/test/fixtures/mail_handler/message_reply.eml
@@ -0,0 +1,15 @@
+Message-ID: <4974C93E.3070005@somenet.foo>
+Date: Mon, 19 Jan 2009 19:41:02 +0100
+From: "John Smith" <jsmith@somenet.foo>
+User-Agent: Thunderbird 2.0.0.19 (Windows/20081209)
+MIME-Version: 1.0
+To: redmine@somenet.foo
+Subject: Reply via email
+References: <redmine.message-2.20070512171800@somenet.foo>
+In-Reply-To: <redmine.message-2.20070512171800@somenet.foo>
+Content-Type: text/plain; charset=UTF-8; format=flowed
+Content-Transfer-Encoding: 7bit
+
+This is a reply to a forum message.
+
+
diff --git a/test/fixtures/mail_handler/message_reply_by_subject.eml b/test/fixtures/mail_handler/message_reply_by_subject.eml
new file mode 100644
index 000000000..985aaa0e4
--- /dev/null
+++ b/test/fixtures/mail_handler/message_reply_by_subject.eml
@@ -0,0 +1,13 @@
+Message-ID: <4974C93E.3070005@somenet.foo>
+Date: Mon, 19 Jan 2009 19:41:02 +0100
+From: "John Smith" <jsmith@somenet.foo>
+User-Agent: Thunderbird 2.0.0.19 (Windows/20081209)
+MIME-Version: 1.0
+To: redmine@somenet.foo
+Subject: Re: [eCookbook - Help board - msg2] Reply to the first post
+Content-Type: text/plain; charset=UTF-8; format=flowed
+Content-Transfer-Encoding: 7bit
+
+This is a reply to a forum message.
+
+
diff --git a/test/fixtures/mail_handler/ticket_html_only.eml b/test/fixtures/mail_handler/ticket_html_only.eml
new file mode 100644
index 000000000..511e5f107
--- /dev/null
+++ b/test/fixtures/mail_handler/ticket_html_only.eml
@@ -0,0 +1,22 @@
+x-sender: <jsmith@somenet.foo>
+x-receiver: <redmine@somenet.foo>
+Received: from [127.0.0.1] ([127.0.0.1]) by somenet.foo with Quick 'n Easy Mail Server SMTP (1.0.0.0);
+ Sun, 14 Dec 2008 16:18:06 GMT
+Message-ID: <494531B9.1070709@somenet.foo>
+Date: Sun, 14 Dec 2008 17:18:01 +0100
+From: "John Smith" <jsmith@somenet.foo>
+User-Agent: Thunderbird 2.0.0.18 (Windows/20081105)
+MIME-Version: 1.0
+To: redmine@somenet.foo
+Subject: HTML email
+Content-Type: text/html; charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+</head>
+<body bgcolor="#ffffff" text="#000000">
+This is a <b>html-only</b> email.<br>
+</body>
+</html>
diff --git a/test/fixtures/mail_handler/ticket_on_given_project.eml b/test/fixtures/mail_handler/ticket_on_given_project.eml
index 927dbc63e..5dbd0dc2e 100644
--- a/test/fixtures/mail_handler/ticket_on_given_project.eml
+++ b/test/fixtures/mail_handler/ticket_on_given_project.eml
@@ -1,9 +1,9 @@
-Return-Path: <jsmith@somenet.foo>
+Return-Path: <JSmith@somenet.foo>
Received: from osiris ([127.0.0.1])
by OSIRIS
with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
-From: "John Smith" <jsmith@somenet.foo>
+From: "John Smith" <JSmith@somenet.foo>
To: <redmine@somenet.foo>
Subject: New ticket on a given project
Date: Sun, 22 Jun 2008 12:28:07 +0200
diff --git a/test/fixtures/mail_handler/ticket_reply.eml b/test/fixtures/mail_handler/ticket_reply.eml
index 99fcfa0d1..74724ccf4 100644
--- a/test/fixtures/mail_handler/ticket_reply.eml
+++ b/test/fixtures/mail_handler/ticket_reply.eml
@@ -3,10 +3,11 @@ Received: from osiris ([127.0.0.1])
by OSIRIS
with hMailServer ; Sat, 21 Jun 2008 18:41:39 +0200
Message-ID: <006a01c8d3bd$ad9baec0$0a00a8c0@osiris>
+In-Reply-To: <redmine.issue-2.20060719210421@osiris>
From: "John Smith" <jsmith@somenet.foo>
To: <redmine@somenet.foo>
References: <485d0ad366c88_d7014663a025f@osiris.tmail>
-Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories
+Subject: Re: Add ingredients categories
Date: Sat, 21 Jun 2008 18:41:39 +0200
MIME-Version: 1.0
Content-Type: multipart/alternative;
diff --git a/test/fixtures/mail_handler/ticket_with_cc.eml b/test/fixtures/mail_handler/ticket_with_cc.eml
new file mode 100644
index 000000000..f809fed77
--- /dev/null
+++ b/test/fixtures/mail_handler/ticket_with_cc.eml
@@ -0,0 +1,40 @@
+Return-Path: <JSmith@somenet.foo>
+Received: from osiris ([127.0.0.1])
+ by OSIRIS
+ with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
+Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
+From: "John Smith" <JSmith@somenet.foo>
+To: <redmine@somenet.foo>
+Cc: <DLopper@somenet.foo>
+Subject: New ticket on a given project
+Date: Sun, 22 Jun 2008 12:28:07 +0200
+MIME-Version: 1.0
+Content-Type: text/plain;
+ format=flowed;
+ charset="iso-8859-1";
+ reply-type=original
+Content-Transfer-Encoding: 7bit
+X-Priority: 3
+X-MSMail-Priority: Normal
+X-Mailer: Microsoft Outlook Express 6.00.2900.2869
+X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
+
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
+turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
+blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
+sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
+in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
+sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
+id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
+eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
+sed, mauris. Pellentesque habitant morbi tristique senectus et netus et
+malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
+platea dictumst.
+
+Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
+sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
+Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
+dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
+massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
+pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
+
diff --git a/test/fixtures/mail_handler/ticket_with_custom_fields.eml b/test/fixtures/mail_handler/ticket_with_custom_fields.eml
new file mode 100644
index 000000000..f3798a4b6
--- /dev/null
+++ b/test/fixtures/mail_handler/ticket_with_custom_fields.eml
@@ -0,0 +1,41 @@
+Return-Path: <jsmith@somenet.foo>
+Received: from osiris ([127.0.0.1])
+ by OSIRIS
+ with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
+Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
+From: "John Smith" <jsmith@somenet.foo>
+To: <redmine@somenet.foo>
+Subject: New ticket with custom field values
+Date: Sun, 22 Jun 2008 12:28:07 +0200
+MIME-Version: 1.0
+Content-Type: text/plain;
+ format=flowed;
+ charset="iso-8859-1";
+ reply-type=original
+Content-Transfer-Encoding: 7bit
+X-Priority: 3
+X-MSMail-Priority: Normal
+X-Mailer: Microsoft Outlook Express 6.00.2900.2869
+X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
+
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
+turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
+blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
+sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
+in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
+sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
+id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
+eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
+sed, mauris. Pellentesque habitant morbi tristique senectus et netus et
+malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
+platea dictumst.
+
+Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
+sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
+Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
+dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
+massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
+pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
+
+category: Stock management
+searchable field: Value for a custom field
diff --git a/test/fixtures/members.yml b/test/fixtures/members.yml
index 32c65c673..4156bb867 100644
--- a/test/fixtures/members.yml
+++ b/test/fixtures/members.yml
@@ -5,18 +5,21 @@ members_001:
role_id: 1
id: 1
user_id: 2
+ mail_notification: true
members_002:
created_on: 2006-07-19 19:35:36 +02:00
project_id: 1
role_id: 2
id: 2
user_id: 3
+ mail_notification: true
members_003:
created_on: 2006-07-19 19:35:36 +02:00
project_id: 2
role_id: 2
id: 3
user_id: 2
+ mail_notification: true
members_004:
id: 4
created_on: 2006-07-19 19:35:36 +02:00
@@ -24,10 +27,12 @@ members_004:
role_id: 2
# Locked user
user_id: 5
+ mail_notification: true
members_005:
id: 5
created_on: 2006-07-19 19:35:33 +02:00
project_id: 5
role_id: 1
user_id: 2
+ mail_notification: true
\ No newline at end of file
diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml
index f82f376c1..8ed376eb2 100644
--- a/test/fixtures/messages.yml
+++ b/test/fixtures/messages.yml
@@ -30,7 +30,7 @@ messages_003:
replies_count: 0
last_reply_id:
content: "An other reply"
- author_id:
+ author_id: 2
parent_id: 1
board_id: 1
messages_004:
@@ -38,8 +38,8 @@ messages_004:
updated_on: 2007-08-12 17:15:32 +02:00
subject: Post 2
id: 4
- replies_count: 1
- last_reply_id: 5
+ replies_count: 2
+ last_reply_id: 6
content: "This is an other post"
author_id:
parent_id:
@@ -55,3 +55,14 @@ messages_005:
author_id: 1
parent_id: 4
board_id: 1
+messages_006:
+ created_on: <%= 2.days.ago.to_date.to_s(:db) %>
+ updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
+ subject: 'RE: post 2'
+ id: 6
+ replies_count: 0
+ last_reply_id:
+ content: "Another reply to the second post"
+ author_id: 3
+ parent_id: 4
+ board_id: 1
diff --git a/test/fixtures/projects.yml b/test/fixtures/projects.yml
index 8e1b3fe1d..de692af0d 100644
--- a/test/fixtures/projects.yml
+++ b/test/fixtures/projects.yml
@@ -3,55 +3,72 @@ projects_001:
created_on: 2006-07-19 19:13:59 +02:00
name: eCookbook
updated_on: 2006-07-19 22:53:01 +02:00
- projects_count: 3
id: 1
description: Recipes management application
homepage: http://ecookbook.somenet.foo/
is_public: true
identifier: ecookbook
parent_id:
+ lft: 1
+ rgt: 10
projects_002:
created_on: 2006-07-19 19:14:19 +02:00
name: OnlineStore
updated_on: 2006-07-19 19:14:19 +02:00
- projects_count: 0
id: 2
description: E-commerce web site
homepage: ""
is_public: false
identifier: onlinestore
parent_id:
+ lft: 11
+ rgt: 12
projects_003:
created_on: 2006-07-19 19:15:21 +02:00
name: eCookbook Subproject 1
updated_on: 2006-07-19 19:18:12 +02:00
- projects_count: 0
id: 3
description: eCookBook Subproject 1
homepage: ""
is_public: true
identifier: subproject1
parent_id: 1
+ lft: 6
+ rgt: 7
projects_004:
created_on: 2006-07-19 19:15:51 +02:00
name: eCookbook Subproject 2
updated_on: 2006-07-19 19:17:07 +02:00
- projects_count: 0
id: 4
description: eCookbook Subproject 2
homepage: ""
is_public: true
identifier: subproject2
parent_id: 1
+ lft: 8
+ rgt: 9
projects_005:
created_on: 2006-07-19 19:15:51 +02:00
name: Private child of eCookbook
updated_on: 2006-07-19 19:17:07 +02:00
- projects_count: 0
id: 5
description: This is a private subproject of a public project
homepage: ""
is_public: false
- identifier: private_child
+ identifier: private-child
parent_id: 1
+ lft: 2
+ rgt: 5
+projects_006:
+ created_on: 2006-07-19 19:15:51 +02:00
+ name: Child of private child
+ updated_on: 2006-07-19 19:17:07 +02:00
+ id: 6
+ description: This is a public subproject of a private project
+ homepage: ""
+ is_public: true
+ identifier: project6
+ parent_id: 5
+ lft: 3
+ rgt: 4
\ No newline at end of file
diff --git a/test/fixtures/projects_trackers.yml b/test/fixtures/projects_trackers.yml
index 31f7f943a..4766a9f82 100644
--- a/test/fixtures/projects_trackers.yml
+++ b/test/fixtures/projects_trackers.yml
@@ -1,44 +1,44 @@
---
-projects_trackers_012:
+projects_trackers_001:
project_id: 4
tracker_id: 3
-projects_trackers_001:
+projects_trackers_002:
project_id: 1
tracker_id: 1
-projects_trackers_013:
+projects_trackers_003:
project_id: 5
tracker_id: 1
-projects_trackers_002:
+projects_trackers_004:
project_id: 1
tracker_id: 2
-projects_trackers_014:
+projects_trackers_005:
project_id: 5
tracker_id: 2
-projects_trackers_015:
+projects_trackers_006:
project_id: 5
tracker_id: 3
-projects_trackers_004:
+projects_trackers_007:
project_id: 2
tracker_id: 1
-projects_trackers_005:
+projects_trackers_008:
project_id: 2
tracker_id: 2
-projects_trackers_006:
+projects_trackers_009:
project_id: 2
tracker_id: 3
-projects_trackers_008:
+projects_trackers_010:
project_id: 3
tracker_id: 2
-projects_trackers_009:
+projects_trackers_011:
project_id: 3
tracker_id: 3
-projects_trackers_010:
+projects_trackers_012:
project_id: 4
tracker_id: 1
-projects_trackers_011:
+projects_trackers_013:
project_id: 4
tracker_id: 2
-projects_trackers_012:
+projects_trackers_014:
project_id: 1
tracker_id: 3
\ No newline at end of file
diff --git a/test/fixtures/roles.yml b/test/fixtures/roles.yml
index 74cba2706..d8ae2c819 100644
--- a/test/fixtures/roles.yml
+++ b/test/fixtures/roles.yml
@@ -32,6 +32,7 @@ roles_001:
- :view_wiki_pages
- :view_wiki_edits
- :edit_wiki_pages
+ - :delete_wiki_pages_attachments
- :protect_wiki_pages
- :delete_wiki_pages
- :rename_wiki_pages
@@ -42,6 +43,7 @@ roles_001:
- :view_files
- :manage_files
- :browse_repository
+ - :manage_repository
- :view_changesets
position: 1
@@ -78,6 +80,8 @@ roles_002:
- :protect_wiki_pages
- :delete_wiki_pages
- :add_messages
+ - :edit_own_messages
+ - :delete_own_messages
- :manage_boards
- :view_files
- :manage_files
diff --git a/test/fixtures/watchers.yml b/test/fixtures/watchers.yml
index 6c8cdfb5e..803b03e5e 100644
--- a/test/fixtures/watchers.yml
+++ b/test/fixtures/watchers.yml
@@ -7,4 +7,8 @@ watchers_002:
watchable_type: Message
watchable_id: 1
user_id: 1
+watchers_003:
+ watchable_type: Issue
+ watchable_id: 2
+ user_id: 1
\ No newline at end of file
diff --git a/test/fixtures/wiki_contents.yml b/test/fixtures/wiki_contents.yml
index 8c53d4d97..8798ff229 100644
--- a/test/fixtures/wiki_contents.yml
+++ b/test/fixtures/wiki_contents.yml
@@ -47,4 +47,26 @@ wiki_contents_004:
version: 1
author_id: 1
comments:
+wiki_contents_005:
+ text: |-
+ h1. Child page 1
+
+ This is a child page
+ updated_on: 2007-03-08 00:18:07 +01:00
+ page_id: 5
+ id: 5
+ version: 1
+ author_id: 1
+ comments:
+wiki_contents_006:
+ text: |-
+ h1. Child page 2
+
+ This is a child page
+ updated_on: 2007-03-08 00:18:07 +01:00
+ page_id: 6
+ id: 6
+ version: 1
+ author_id: 1
+ comments:
\ No newline at end of file
diff --git a/test/fixtures/wiki_pages.yml b/test/fixtures/wiki_pages.yml
index e285441ff..8d29c2f72 100644
--- a/test/fixtures/wiki_pages.yml
+++ b/test/fixtures/wiki_pages.yml
@@ -27,4 +27,18 @@ wiki_pages_004:
wiki_id: 1
protected: false
parent_id: 1
+wiki_pages_005:
+ created_on: 2007-03-08 00:18:07 +01:00
+ title: Child_1
+ id: 5
+ wiki_id: 1
+ protected: false
+ parent_id: 2
+wiki_pages_006:
+ created_on: 2007-03-08 00:18:07 +01:00
+ title: Child_2
+ id: 6
+ wiki_id: 1
+ protected: false
+ parent_id: 2
\ No newline at end of file
diff --git a/test/functional/account_controller_test.rb b/test/functional/account_controller_test.rb
index a6e379991..c5a78dad2 100644
--- a/test/functional/account_controller_test.rb
+++ b/test/functional/account_controller_test.rb
@@ -22,7 +22,7 @@ require 'account_controller'
class AccountController; def rescue_action(e) raise e end; end
class AccountControllerTest < Test::Unit::TestCase
- fixtures :users
+ fixtures :users, :roles
def setup
@controller = AccountController.new
@@ -64,6 +64,83 @@ class AccountControllerTest < Test::Unit::TestCase
:content => /Invalid user or password/
end
+ if Object.const_defined?(:OpenID)
+
+ def test_login_with_openid_for_existing_user
+ Setting.self_registration = '3'
+ Setting.openid = '1'
+ existing_user = User.new(:firstname => 'Cool',
+ :lastname => 'User',
+ :mail => 'user@somedomain.com',
+ :identity_url => 'http://openid.example.com/good_user')
+ existing_user.login = 'cool_user'
+ assert existing_user.save!
+
+ post :login, :openid_url => existing_user.identity_url
+ assert_redirected_to 'my/page'
+ end
+
+ def test_login_with_openid_with_new_user_created
+ Setting.self_registration = '3'
+ Setting.openid = '1'
+ post :login, :openid_url => 'http://openid.example.com/good_user'
+ assert_redirected_to 'my/account'
+ user = User.find_by_login('cool_user')
+ assert user
+ assert_equal 'Cool', user.firstname
+ assert_equal 'User', user.lastname
+ end
+
+ def test_login_with_openid_with_new_user_and_self_registration_off
+ Setting.self_registration = '0'
+ Setting.openid = '1'
+ post :login, :openid_url => 'http://openid.example.com/good_user'
+ assert_redirected_to home_url
+ user = User.find_by_login('cool_user')
+ assert ! user
+ end
+
+ def test_login_with_openid_with_new_user_created_with_email_activation_should_have_a_token
+ Setting.self_registration = '1'
+ Setting.openid = '1'
+ post :login, :openid_url => 'http://openid.example.com/good_user'
+ assert_redirected_to 'login'
+ user = User.find_by_login('cool_user')
+ assert user
+
+ token = Token.find_by_user_id_and_action(user.id, 'register')
+ assert token
+ end
+
+ def test_login_with_openid_with_new_user_created_with_manual_activation
+ Setting.self_registration = '2'
+ Setting.openid = '1'
+ post :login, :openid_url => 'http://openid.example.com/good_user'
+ assert_redirected_to 'login'
+ user = User.find_by_login('cool_user')
+ assert user
+ assert_equal User::STATUS_REGISTERED, user.status
+ end
+
+ def test_login_with_openid_with_new_user_with_conflict_should_register
+ Setting.self_registration = '3'
+ Setting.openid = '1'
+ existing_user = User.new(:firstname => 'Cool', :lastname => 'User', :mail => 'user@somedomain.com')
+ existing_user.login = 'cool_user'
+ assert existing_user.save!
+
+ post :login, :openid_url => 'http://openid.example.com/good_user'
+ assert_response :success
+ assert_template 'register'
+ assert assigns(:user)
+ assert_equal 'http://openid.example.com/good_user', assigns(:user)[:identity_url]
+ end
+
+ else
+ puts "Skipping openid tests."
+ end
+
+
def test_autologin
Setting.autologin = "7"
Token.delete_all
diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb
index 05205b399..32965de4c 100644
--- a/test/functional/admin_controller_test.rb
+++ b/test/functional/admin_controller_test.rb
@@ -38,6 +38,13 @@ class AdminControllerTest < Test::Unit::TestCase
:attributes => { :class => /nodata/ }
end
+ def test_projects_routing
+ assert_routing(
+ {:method => :get, :path => '/admin/projects'},
+ :controller => 'admin', :action => 'projects'
+ )
+ end
+
def test_index_with_no_configuration_data
delete_configuration_data
get :index
@@ -45,6 +52,25 @@ class AdminControllerTest < Test::Unit::TestCase
:attributes => { :class => /nodata/ }
end
+ def test_projects
+ get :projects
+ assert_response :success
+ assert_template 'projects'
+ assert_not_nil assigns(:projects)
+ # active projects only
+ assert_nil assigns(:projects).detect {|u| !u.active?}
+ end
+
+ def test_projects_with_name_filter
+ get :projects, :name => 'store', :status => ''
+ assert_response :success
+ assert_template 'projects'
+ projects = assigns(:projects)
+ assert_not_nil projects
+ assert_equal 1, projects.size
+ assert_equal 'OnlineStore', projects.first.name
+ end
+
def test_load_default_configuration_data
delete_configuration_data
post :default_configuration, :lang => 'fr'
@@ -53,12 +79,40 @@ class AdminControllerTest < Test::Unit::TestCase
def test_test_email
get :test_email
- assert_redirected_to 'settings/edit'
+ assert_redirected_to '/settings/edit?tab=notifications'
mail = ActionMailer::Base.deliveries.last
assert_kind_of TMail::Mail, mail
user = User.find(1)
assert_equal [user.mail], mail.bcc
end
+
+ def test_no_plugins
+ Redmine::Plugin.clear
+
+ get :plugins
+ assert_response :success
+ assert_template 'plugins'
+ end
+
+ def test_plugins
+ # Register a few plugins
+ Redmine::Plugin.register :foo do
+ name 'Foo plugin'
+ author 'John Smith'
+ description 'This is a test plugin'
+ version '0.0.1'
+ settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings'
+ end
+ Redmine::Plugin.register :bar do
+ end
+
+ get :plugins
+ assert_response :success
+ assert_template 'plugins'
+
+ assert_tag :td, :child => { :tag => 'span', :content => 'Foo plugin' }
+ assert_tag :td, :child => { :tag => 'span', :content => 'Bar' }
+ end
def test_info
get :info
@@ -66,6 +120,8 @@ class AdminControllerTest < Test::Unit::TestCase
assert_template 'info'
end
+ private
+
def delete_configuration_data
Role.delete_all('builtin = 0')
Tracker.delete_all
diff --git a/test/functional/application_controller_test.rb b/test/functional/application_controller_test.rb
index 6fcf8fe9a..7d1cc8391 100644
--- a/test/functional/application_controller_test.rb
+++ b/test/functional/application_controller_test.rb
@@ -37,4 +37,8 @@ class ApplicationControllerTest < Test::Unit::TestCase
end
set_language_if_valid('en')
end
+
+ def test_call_hook_mixed_in
+ assert @controller.respond_to?(:call_hook)
+ end
end
diff --git a/test/functional/attachments_controller_test.rb b/test/functional/attachments_controller_test.rb
index 139896ce6..634d4279b 100644
--- a/test/functional/attachments_controller_test.rb
+++ b/test/functional/attachments_controller_test.rb
@@ -23,7 +23,8 @@ class AttachmentsController; def rescue_action(e) raise e end; end
class AttachmentsControllerTest < Test::Unit::TestCase
- fixtures :users, :projects, :roles, :members, :enabled_modules, :issues, :attachments
+ fixtures :users, :projects, :roles, :members, :enabled_modules, :issues, :attachments,
+ :versions, :wiki_pages, :wikis
def setup
@controller = AttachmentsController.new
@@ -74,6 +75,52 @@ class AttachmentsControllerTest < Test::Unit::TestCase
def test_anonymous_on_private_private
get :download, :id => 7
- assert_redirected_to 'account/login'
+ assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fdownload%2F7'
+ end
+
+ def test_destroy_issue_attachment
+ issue = Issue.find(3)
+ @request.session[:user_id] = 2
+
+ assert_difference 'issue.attachments.count', -1 do
+ post :destroy, :id => 1
+ end
+ # no referrer
+ assert_redirected_to 'projects/show/ecookbook'
+ assert_nil Attachment.find_by_id(1)
+ j = issue.journals.find(:first, :order => 'created_on DESC')
+ assert_equal 'attachment', j.details.first.property
+ assert_equal '1', j.details.first.prop_key
+ assert_equal 'error281.txt', j.details.first.old_value
+ end
+
+ def test_destroy_wiki_page_attachment
+ @request.session[:user_id] = 2
+ assert_difference 'Attachment.count', -1 do
+ post :destroy, :id => 3
+ assert_response 302
+ end
+ end
+
+ def test_destroy_project_attachment
+ @request.session[:user_id] = 2
+ assert_difference 'Attachment.count', -1 do
+ post :destroy, :id => 8
+ assert_response 302
+ end
+ end
+
+ def test_destroy_version_attachment
+ @request.session[:user_id] = 2
+ assert_difference 'Attachment.count', -1 do
+ post :destroy, :id => 9
+ assert_response 302
+ end
+ end
+
+ def test_destroy_without_permission
+ post :destroy, :id => 3
+ assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fdestroy%2F3'
+ assert Attachment.find_by_id(3)
end
end
diff --git a/test/functional/boards_controller_test.rb b/test/functional/boards_controller_test.rb
index 3ff71bc4e..190eae237 100644
--- a/test/functional/boards_controller_test.rb
+++ b/test/functional/boards_controller_test.rb
@@ -31,6 +31,13 @@ class BoardsControllerTest < Test::Unit::TestCase
User.current = nil
end
+ def test_index_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/world_domination/boards'},
+ :controller => 'boards', :action => 'index', :project_id => 'world_domination'
+ )
+ end
+
def test_index
get :index, :project_id => 1
assert_response :success
@@ -39,6 +46,24 @@ class BoardsControllerTest < Test::Unit::TestCase
assert_not_nil assigns(:project)
end
+ def test_new_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/world_domination/boards/new'},
+ :controller => 'boards', :action => 'new', :project_id => 'world_domination'
+ )
+ assert_recognizes(
+ {:controller => 'boards', :action => 'new', :project_id => 'world_domination'},
+ {:method => :post, :path => '/projects/world_domination/boards'}
+ )
+ end
+
+ def test_show_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/world_domination/boards/44'},
+ :controller => 'boards', :action => 'show', :id => '44', :project_id => 'world_domination'
+ )
+ end
+
def test_show
get :show, :project_id => 1, :id => 1
assert_response :success
@@ -47,4 +72,22 @@ class BoardsControllerTest < Test::Unit::TestCase
assert_not_nil assigns(:project)
assert_not_nil assigns(:topics)
end
+
+ def test_edit_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/world_domination/boards/44/edit'},
+ :controller => 'boards', :action => 'edit', :id => '44', :project_id => 'world_domination'
+ )
+ assert_recognizes(#TODO: use PUT method to board_path, modify form accordingly
+ {:controller => 'boards', :action => 'edit', :id => '44', :project_id => 'world_domination'},
+ {:method => :post, :path => '/projects/world_domination/boards/44/edit'}
+ )
+ end
+
+ def test_destroy_routing
+ assert_routing(#TODO: use DELETE method to board_path, modify form accoringly
+ {:method => :post, :path => '/projects/world_domination/boards/44/destroy'},
+ :controller => 'boards', :action => 'destroy', :id => '44', :project_id => 'world_domination'
+ )
+ end
end
diff --git a/test/functional/custom_fields_controller_test.rb b/test/functional/custom_fields_controller_test.rb
new file mode 100644
index 000000000..42dedb3c1
--- /dev/null
+++ b/test/functional/custom_fields_controller_test.rb
@@ -0,0 +1,61 @@
+# Redmine - project management software
+# Copyright (C) 2006-2009 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require File.dirname(__FILE__) + '/../test_helper'
+require 'custom_fields_controller'
+
+# Re-raise errors caught by the controller.
+class CustomFieldsController; def rescue_action(e) raise e end; end
+
+class CustomFieldsControllerTest < Test::Unit::TestCase
+ fixtures :custom_fields, :trackers, :users
+
+ def setup
+ @controller = CustomFieldsController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @request.session[:user_id] = 1
+ end
+
+ def test_post_new_list_custom_field
+ assert_difference 'CustomField.count' do
+ post :new, :type => "IssueCustomField",
+ :custom_field => {:name => "test_post_new_list",
+ :default_value => "",
+ :min_length => "0",
+ :searchable => "0",
+ :regexp => "",
+ :is_for_all => "1",
+ :possible_values => "0.1\n0.2\n",
+ :max_length => "0",
+ :is_filter => "0",
+ :is_required =>"0",
+ :field_format => "list",
+ :tracker_ids => ["1", ""]}
+ end
+ assert_redirected_to '/custom_fields'
+ field = IssueCustomField.find_by_name('test_post_new_list')
+ assert_not_nil field
+ assert_equal ["0.1", "0.2"], field.possible_values
+ assert_equal 1, field.trackers.size
+ end
+
+ def test_invalid_custom_field_class_should_redirect_to_list
+ get :new, :type => 'UnknownCustomField'
+ assert_redirected_to '/custom_fields'
+ end
+end
diff --git a/test/functional/documents_controller_test.rb b/test/functional/documents_controller_test.rb
index 7c1f0213a..2ad94aacf 100644
--- a/test/functional/documents_controller_test.rb
+++ b/test/functional/documents_controller_test.rb
@@ -30,12 +30,39 @@ class DocumentsControllerTest < Test::Unit::TestCase
@response = ActionController::TestResponse.new
User.current = nil
end
-
+
+ def test_index_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/documents'},
+ :controller => 'documents', :action => 'index', :project_id => '567'
+ )
+ end
+
def test_index
+ # Sets a default category
+ e = Enumeration.find_by_name('Technical documentation')
+ e.update_attributes(:is_default => true)
+
get :index, :project_id => 'ecookbook'
assert_response :success
assert_template 'index'
assert_not_nil assigns(:grouped)
+
+ # Default category selected in the new document form
+ assert_tag :select, :attributes => {:name => 'document[category_id]'},
+ :child => {:tag => 'option', :attributes => {:selected => 'selected'},
+ :content => 'Technical documentation'}
+ end
+
+ def test_new_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/documents/new'},
+ :controller => 'documents', :action => 'new', :project_id => '567'
+ )
+ assert_recognizes(
+ {:controller => 'documents', :action => 'new', :project_id => '567'},
+ {:method => :post, :path => '/projects/567/documents'}
+ )
end
def test_new_with_one_attachment
@@ -57,6 +84,31 @@ class DocumentsControllerTest < Test::Unit::TestCase
assert_equal 'testfile.txt', document.attachments.first.filename
end
+ def test_edit_routing
+ assert_routing(
+ {:method => :get, :path => '/documents/22/edit'},
+ :controller => 'documents', :action => 'edit', :id => '22'
+ )
+ assert_recognizes(#TODO: should be using PUT on document URI
+ {:controller => 'documents', :action => 'edit', :id => '567'},
+ {:method => :post, :path => '/documents/567/edit'}
+ )
+ end
+
+ def test_show_routing
+ assert_routing(
+ {:method => :get, :path => '/documents/22'},
+ :controller => 'documents', :action => 'show', :id => '22'
+ )
+ end
+
+ def test_destroy_routing
+ assert_recognizes(#TODO: should be using DELETE on document URI
+ {:controller => 'documents', :action => 'destroy', :id => '567'},
+ {:method => :post, :path => '/documents/567/destroy'}
+ )
+ end
+
def test_destroy
@request.session[:user_id] = 2
post :destroy, :id => 1
diff --git a/test/functional/issue_relations_controller_test.rb b/test/functional/issue_relations_controller_test.rb
new file mode 100644
index 000000000..dc64a004e
--- /dev/null
+++ b/test/functional/issue_relations_controller_test.rb
@@ -0,0 +1,58 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'issue_relations_controller'
+
+# Re-raise errors caught by the controller.
+class IssueRelationsController; def rescue_action(e) raise e end; end
+
+
+class IssueRelationsControllerTest < Test::Unit::TestCase
+ fixtures :projects,
+ :users,
+ :roles,
+ :members,
+ :issues,
+ :issue_statuses,
+ :enabled_modules,
+ :enumerations,
+ :trackers
+
+ def setup
+ @controller = IssueRelationsController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ User.current = nil
+ end
+
+ def test_new_routing
+ assert_routing(
+ {:method => :post, :path => '/issues/1/relations'},
+ {:controller => 'issue_relations', :action => 'new', :issue_id => '1'}
+ )
+ end
+
+ def test_destroy_routing
+ assert_recognizes( #TODO: use DELETE on issue URI
+ {:controller => 'issue_relations', :action => 'destroy', :issue_id => '1', :id => '23'},
+ {:method => :post, :path => '/issues/1/relations/23/destroy'}
+ )
+ end
+
+ def test_new
+ assert_difference 'IssueRelation.count' do
+ @request.session[:user_id] = 3
+ post :new, :issue_id => 1,
+ :relation => {:issue_to_id => '2', :relation_type => 'relates', :delay => ''}
+ end
+ end
+
+ def test_should_create_relations_with_visible_issues_only
+ Setting.cross_project_issue_relations = '1'
+ assert_nil Issue.visible(User.find(3)).find_by_id(4)
+
+ assert_no_difference 'IssueRelation.count' do
+ @request.session[:user_id] = 3
+ post :new, :issue_id => 1,
+ :relation => {:issue_to_id => '4', :relation_type => 'relates', :delay => ''}
+ end
+ end
+end
diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb
index 9e2a9ffd5..cc1c77408 100644
--- a/test/functional/issues_controller_test.rb
+++ b/test/functional/issues_controller_test.rb
@@ -49,6 +49,13 @@ class IssuesControllerTest < Test::Unit::TestCase
@response = ActionController::TestResponse.new
User.current = nil
end
+
+ def test_index_routing
+ assert_routing(
+ {:method => :get, :path => '/issues'},
+ :controller => 'issues', :action => 'index'
+ )
+ end
def test_index
get :index
@@ -62,7 +69,43 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
assert_no_tag :tag => 'a', :content => /Issue on project 2/
end
+
+ def test_index_should_not_list_issues_when_module_disabled
+ EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
+ get :index
+ assert_response :success
+ assert_template 'index.rhtml'
+ assert_not_nil assigns(:issues)
+ assert_nil assigns(:project)
+ assert_no_tag :tag => 'a', :content => /Can't print recipes/
+ assert_tag :tag => 'a', :content => /Subproject issue/
+ end
+ def test_index_with_project_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/23/issues'},
+ :controller => 'issues', :action => 'index', :project_id => '23'
+ )
+ end
+
+ def test_index_should_not_list_issues_when_module_disabled
+ EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
+ get :index
+ assert_response :success
+ assert_template 'index.rhtml'
+ assert_not_nil assigns(:issues)
+ assert_nil assigns(:project)
+ assert_no_tag :tag => 'a', :content => /Can't print recipes/
+ assert_tag :tag => 'a', :content => /Subproject issue/
+ end
+
+ def test_index_with_project_routing
+ assert_routing(
+ {:method => :get, :path => 'projects/23/issues'},
+ :controller => 'issues', :action => 'index', :project_id => '23'
+ )
+ end
+
def test_index_with_project
Setting.display_subprojects_issues = 0
get :index, :project_id => 1
@@ -96,6 +139,17 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_tag :tag => 'a', :content => /Issue of a private subproject/
end
+ def test_index_with_project_routing_formatted
+ assert_routing(
+ {:method => :get, :path => 'projects/23/issues.pdf'},
+ :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
+ )
+ assert_routing(
+ {:method => :get, :path => 'projects/23/issues.atom'},
+ :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
+ )
+ end
+
def test_index_with_project_and_filter
get :index, :project_id => 1, :set_filter => 1
assert_response :success
@@ -115,6 +169,17 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_equal 'text/csv', @response.content_type
end
+ def test_index_formatted
+ assert_routing(
+ {:method => :get, :path => 'issues.pdf'},
+ :controller => 'issues', :action => 'index', :format => 'pdf'
+ )
+ assert_routing(
+ {:method => :get, :path => 'issues.atom'},
+ :controller => 'issues', :action => 'index', :format => 'atom'
+ )
+ end
+
def test_index_pdf
get :index, :format => 'pdf'
assert_response :success
@@ -126,6 +191,16 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_not_nil assigns(:issues)
assert_equal 'application/pdf', @response.content_type
end
+
+ def test_index_sort
+ get :index, :sort_key => 'tracker'
+ assert_response :success
+
+ sort_params = @request.session['issuesindex_sort']
+ assert sort_params.is_a?(Hash)
+ assert_equal 'tracker', sort_params[:key]
+ assert_equal 'ASC', sort_params[:order]
+ end
def test_gantt
get :gantt, :project_id => 1
@@ -144,11 +219,28 @@ class IssuesControllerTest < Test::Unit::TestCase
assert events.include?(i)
end
+ def test_cross_project_gantt
+ get :gantt
+ assert_response :success
+ assert_template 'gantt.rhtml'
+ assert_not_nil assigns(:gantt)
+ events = assigns(:gantt).events
+ assert_not_nil events
+ end
+
def test_gantt_export_to_pdf
get :gantt, :project_id => 1, :format => 'pdf'
assert_response :success
- assert_template 'gantt.rfpdf'
assert_equal 'application/pdf', @response.content_type
+ assert @response.body.starts_with?('%PDF')
+ assert_not_nil assigns(:gantt)
+ end
+
+ def test_cross_project_gantt_export_to_pdf
+ get :gantt, :format => 'pdf'
+ assert_response :success
+ assert_equal 'application/pdf', @response.content_type
+ assert @response.body.starts_with?('%PDF')
assert_not_nil assigns(:gantt)
end
@@ -169,6 +261,13 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_not_nil assigns(:calendar)
end
+ def test_cross_project_calendar
+ get :calendar
+ assert_response :success
+ assert_template 'calendar'
+ assert_not_nil assigns(:calendar)
+ end
+
def test_changes
get :changes, :project_id => 1
assert_response :success
@@ -176,6 +275,24 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_equal 'application/atom+xml', @response.content_type
end
+ def test_show_routing
+ assert_routing(
+ {:method => :get, :path => '/issues/64'},
+ :controller => 'issues', :action => 'show', :id => '64'
+ )
+ end
+
+ def test_show_routing_formatted
+ assert_routing(
+ {:method => :get, :path => '/issues/2332.pdf'},
+ :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
+ )
+ assert_routing(
+ {:method => :get, :path => '/issues/23123.atom'},
+ :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
+ )
+ end
+
def test_show_by_anonymous
get :show, :id => 1
assert_response :success
@@ -206,6 +323,40 @@ class IssuesControllerTest < Test::Unit::TestCase
:child => { :tag => 'legend',
:content => /Notes/ } }
end
+
+ def test_show_should_not_disclose_relations_to_invisible_issues
+ Setting.cross_project_issue_relations = '1'
+ IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
+ # Relation to a private project issue
+ IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
+
+ get :show, :id => 1
+ assert_response :success
+
+ assert_tag :div, :attributes => { :id => 'relations' },
+ :descendant => { :tag => 'a', :content => /#2$/ }
+ assert_no_tag :div, :attributes => { :id => 'relations' },
+ :descendant => { :tag => 'a', :content => /#4$/ }
+ end
+
+ def test_new_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/1/issues/new'},
+ :controller => 'issues', :action => 'new', :project_id => '1'
+ )
+ assert_recognizes(
+ {:controller => 'issues', :action => 'new', :project_id => '1'},
+ {:method => :post, :path => '/projects/1/issues'}
+ )
+ end
+
+ def test_show_export_to_pdf
+ get :show, :id => 3, :format => 'pdf'
+ assert_response :success
+ assert_equal 'application/pdf', @response.content_type
+ assert @response.body.starts_with?('%PDF')
+ assert_not_nil assigns(:issue)
+ end
def test_get_new
@request.session[:user_id] = 2
@@ -237,7 +388,7 @@ class IssuesControllerTest < Test::Unit::TestCase
:priority_id => 5}
assert_response :success
assert_template 'new'
- end
+ end
def test_post_new
@request.session[:user_id] = 2
@@ -248,7 +399,7 @@ class IssuesControllerTest < Test::Unit::TestCase
:priority_id => 5,
:estimated_hours => '',
:custom_field_values => {'2' => 'Value for field 2'}}
- assert_redirected_to 'issues/show'
+ assert_redirected_to :action => 'show'
issue = Issue.find_by_subject('This is the test_new issue')
assert_not_nil issue
@@ -260,6 +411,16 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_equal 'Value for field 2', v.value
end
+ def test_post_new_and_continue
+ @request.session[:user_id] = 2
+ post :new, :project_id => 1,
+ :issue => {:tracker_id => 3,
+ :subject => 'This is first issue',
+ :priority_id => 5},
+ :continue => ''
+ assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
+ end
+
def test_post_new_without_custom_fields_param
@request.session[:user_id] = 2
post :new, :project_id => 1,
@@ -267,9 +428,9 @@ class IssuesControllerTest < Test::Unit::TestCase
:subject => 'This is the test_new issue',
:description => 'This is the description',
:priority_id => 5}
- assert_redirected_to 'issues/show'
+ assert_redirected_to :action => 'show'
end
-
+
def test_post_new_with_required_custom_field_and_without_custom_fields_param
field = IssueCustomField.find_by_name('Database')
field.update_attribute(:is_required, true)
@@ -287,20 +448,45 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
end
+ def test_post_new_with_watchers
+ @request.session[:user_id] = 2
+ ActionMailer::Base.deliveries.clear
+
+ assert_difference 'Watcher.count', 2 do
+ post :new, :project_id => 1,
+ :issue => {:tracker_id => 1,
+ :subject => 'This is a new issue with watchers',
+ :description => 'This is the description',
+ :priority_id => 5,
+ :watcher_user_ids => ['2', '3']}
+ end
+ issue = Issue.find_by_subject('This is a new issue with watchers')
+ assert_not_nil issue
+ assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
+
+ # Watchers added
+ assert_equal [2, 3], issue.watcher_user_ids.sort
+ assert issue.watched_by?(User.find(3))
+ # Watchers notified
+ mail = ActionMailer::Base.deliveries.last
+ assert_kind_of TMail::Mail, mail
+ assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
+ end
+
def test_post_should_preserve_fields_values_on_validation_failure
@request.session[:user_id] = 2
post :new, :project_id => 1,
:issue => {:tracker_id => 1,
- :subject => 'This is the test_new issue',
- # empty description
- :description => '',
+ # empty subject
+ :subject => '',
+ :description => 'This is a description',
:priority_id => 6,
:custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
assert_response :success
assert_template 'new'
- assert_tag :input, :attributes => { :name => 'issue[subject]',
- :value => 'This is the test_new issue' }
+ assert_tag :textarea, :attributes => { :name => 'issue[description]' },
+ :content => 'This is a description'
assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
:child => { :tag => 'option', :attributes => { :selected => 'selected',
:value => '6' },
@@ -314,6 +500,13 @@ class IssuesControllerTest < Test::Unit::TestCase
:value => 'Value for field 2'}
end
+ def test_copy_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
+ :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
+ )
+ end
+
def test_copy_issue
@request.session[:user_id] = 2
get :new, :project_id => 1, :copy_from => 1
@@ -323,6 +516,17 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_equal orig.subject, assigns(:issue).subject
end
+ def test_edit_routing
+ assert_routing(
+ {:method => :get, :path => '/issues/1/edit'},
+ :controller => 'issues', :action => 'edit', :id => '1'
+ )
+ assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
+ {:controller => 'issues', :action => 'edit', :id => '1'},
+ {:method => :post, :path => '/issues/1/edit'}
+ )
+ end
+
def test_get_edit
@request.session[:user_id] = 2
get :edit, :id => 1
@@ -354,6 +558,13 @@ class IssuesControllerTest < Test::Unit::TestCase
:attributes => { :selected => 'selected' } }
end
+ def test_reply_routing
+ assert_routing(
+ {:method => :post, :path => '/issues/1/quoted'},
+ :controller => 'issues', :action => 'reply', :id => '1'
+ )
+ end
+
def test_reply_to_issue
@request.session[:user_id] = 2
get :reply, :id => 1
@@ -385,7 +596,7 @@ class IssuesControllerTest < Test::Unit::TestCase
}
end
end
- assert_redirected_to 'issues/show/1'
+ assert_redirected_to :action => 'show', :id => '1'
issue.reload
assert_equal new_subject, issue.subject
# Make sure custom fields were not cleared
@@ -411,7 +622,7 @@ class IssuesControllerTest < Test::Unit::TestCase
}
end
end
- assert_redirected_to 'issues/show/1'
+ assert_redirected_to :action => 'show', :id => '1'
issue.reload
assert_equal 'New custom value', issue.custom_value_for(2).value
@@ -431,7 +642,7 @@ class IssuesControllerTest < Test::Unit::TestCase
:notes => 'Assigned to dlopper',
:time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
end
- assert_redirected_to 'issues/show/1'
+ assert_redirected_to :action => 'show', :id => '1'
issue.reload
assert_equal 2, issue.status_id
j = issue.journals.find(:first, :order => 'id DESC')
@@ -448,7 +659,7 @@ class IssuesControllerTest < Test::Unit::TestCase
post :edit,
:id => 1,
:notes => notes
- assert_redirected_to 'issues/show/1'
+ assert_redirected_to :action => 'show', :id => '1'
j = Issue.find(1).journals.find(:first, :order => 'id DESC')
assert_equal notes, j.notes
assert_equal 0, j.details.size
@@ -467,7 +678,7 @@ class IssuesControllerTest < Test::Unit::TestCase
:notes => '2.5 hours added',
:time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
end
- assert_redirected_to 'issues/show/1'
+ assert_redirected_to :action => 'show', :id => '1'
issue = Issue.find(1)
@@ -484,12 +695,16 @@ class IssuesControllerTest < Test::Unit::TestCase
def test_post_edit_with_attachment_only
set_tmp_attachments_directory
+ # Delete all fixtured journals, a race condition can occur causing the wrong
+ # journal to get fetched in the next find.
+ Journal.delete_all
+
# anonymous user
post :edit,
:id => 1,
:notes => '',
:attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
- assert_redirected_to 'issues/show/1'
+ assert_redirected_to :action => 'show', :id => '1'
j = Issue.find(1).journals.find(:first, :order => 'id DESC')
assert j.notes.blank?
assert_equal 1, j.details.size
@@ -508,22 +723,65 @@ class IssuesControllerTest < Test::Unit::TestCase
post :edit,
:id => 1,
:notes => ''
- assert_redirected_to 'issues/show/1'
+ assert_redirected_to :action => 'show', :id => '1'
issue.reload
assert issue.journals.empty?
# No email should be sent
assert ActionMailer::Base.deliveries.empty?
end
+
+ def test_post_edit_with_invalid_spent_time
+ @request.session[:user_id] = 2
+ notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
+
+ assert_no_difference('Journal.count') do
+ post :edit,
+ :id => 1,
+ :notes => notes,
+ :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
+ end
+ assert_response :success
+ assert_template 'edit'
+
+ assert_tag :textarea, :attributes => { :name => 'notes' },
+ :content => notes
+ assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
+ end
def test_bulk_edit
@request.session[:user_id] = 2
# update issues priority
- post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
+ post :bulk_edit, :ids => [1, 2], :priority_id => 7,
+ :assigned_to_id => '',
+ :custom_field_values => {'2' => ''},
+ :notes => 'Bulk editing'
assert_response 302
# check that the issues were updated
assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
- assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
+
+ issue = Issue.find(1)
+ journal = issue.journals.find(:first, :order => 'created_on DESC')
+ assert_equal '125', issue.custom_value_for(2).value
+ assert_equal 'Bulk editing', journal.notes
+ assert_equal 1, journal.details.size
+ end
+
+ def test_bulk_edit_custom_field
+ @request.session[:user_id] = 2
+ # update issues priority
+ post :bulk_edit, :ids => [1, 2], :priority_id => '',
+ :assigned_to_id => '',
+ :custom_field_values => {'2' => '777'},
+ :notes => 'Bulk editing custom field'
+ assert_response 302
+
+ issue = Issue.find(1)
+ journal = issue.journals.find(:first, :order => 'created_on DESC')
+ assert_equal '777', issue.custom_value_for(2).value
+ assert_equal 1, journal.details.size
+ assert_equal '125', journal.details.first.old_value
+ assert_equal '777', journal.details.first.value
end
def test_bulk_unassign
@@ -536,17 +794,28 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_nil Issue.find(2).assigned_to
end
+ def test_move_routing
+ assert_routing(
+ {:method => :get, :path => '/issues/1/move'},
+ :controller => 'issues', :action => 'move', :id => '1'
+ )
+ assert_recognizes(
+ {:controller => 'issues', :action => 'move', :id => '1'},
+ {:method => :post, :path => '/issues/1/move'}
+ )
+ end
+
def test_move_one_issue_to_another_project
@request.session[:user_id] = 1
post :move, :id => 1, :new_project_id => 2
- assert_redirected_to 'projects/ecookbook/issues'
+ assert_redirected_to :action => 'index', :project_id => 'ecookbook'
assert_equal 2, Issue.find(1).project_id
end
def test_bulk_move_to_another_project
@request.session[:user_id] = 1
post :move, :ids => [1, 2], :new_project_id => 2
- assert_redirected_to 'projects/ecookbook/issues'
+ assert_redirected_to :action => 'index', :project_id => 'ecookbook'
# Issues moved to project 2
assert_equal 2, Issue.find(1).project_id
assert_equal 2, Issue.find(2).project_id
@@ -558,10 +827,20 @@ class IssuesControllerTest < Test::Unit::TestCase
def test_bulk_move_to_another_tracker
@request.session[:user_id] = 1
post :move, :ids => [1, 2], :new_tracker_id => 2
- assert_redirected_to 'projects/ecookbook/issues'
+ assert_redirected_to :action => 'index', :project_id => 'ecookbook'
assert_equal 2, Issue.find(1).tracker_id
assert_equal 2, Issue.find(2).tracker_id
end
+
+ def test_bulk_copy_to_another_project
+ @request.session[:user_id] = 1
+ assert_difference 'Issue.count', 2 do
+ assert_no_difference 'Project.find(1).issues.count' do
+ post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
+ end
+ end
+ assert_redirected_to 'projects/ecookbook/issues'
+ end
def test_context_menu_one_issue
@request.session[:user_id] = 2
@@ -569,10 +848,10 @@ class IssuesControllerTest < Test::Unit::TestCase
assert_response :success
assert_template 'context_menu'
assert_tag :tag => 'a', :content => 'Edit',
- :attributes => { :href => '/issues/edit/1',
+ :attributes => { :href => '/issues/1/edit',
:class => 'icon-edit' }
assert_tag :tag => 'a', :content => 'Closed',
- :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
+ :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
:class => '' }
assert_tag :tag => 'a', :content => 'Immediate',
:attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
@@ -581,7 +860,7 @@ class IssuesControllerTest < Test::Unit::TestCase
:attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
:class => '' }
assert_tag :tag => 'a', :content => 'Copy',
- :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
+ :attributes => { :href => '/projects/ecookbook/issues/1/copy',
:class => 'icon-copy' }
assert_tag :tag => 'a', :content => 'Move',
:attributes => { :href => '/issues/move?ids%5B%5D=1',
@@ -632,11 +911,18 @@ class IssuesControllerTest < Test::Unit::TestCase
:class => 'icon-del disabled' }
end
+ def test_destroy_routing
+ assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
+ {:controller => 'issues', :action => 'destroy', :id => '1'},
+ {:method => :post, :path => '/issues/1/destroy'}
+ )
+ end
+
def test_destroy_issue_with_no_time_entries
assert_nil TimeEntry.find_by_issue_id(2)
@request.session[:user_id] = 2
post :destroy, :id => 2
- assert_redirected_to 'projects/ecookbook/issues'
+ assert_redirected_to :action => 'index', :project_id => 'ecookbook'
assert_nil Issue.find_by_id(2)
end
@@ -652,7 +938,7 @@ class IssuesControllerTest < Test::Unit::TestCase
def test_destroy_issues_and_destroy_time_entries
@request.session[:user_id] = 2
post :destroy, :ids => [1, 3], :todo => 'destroy'
- assert_redirected_to 'projects/ecookbook/issues'
+ assert_redirected_to :action => 'index', :project_id => 'ecookbook'
assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
assert_nil TimeEntry.find_by_id([1, 2])
end
@@ -660,7 +946,7 @@ class IssuesControllerTest < Test::Unit::TestCase
def test_destroy_issues_and_assign_time_entries_to_project
@request.session[:user_id] = 2
post :destroy, :ids => [1, 3], :todo => 'nullify'
- assert_redirected_to 'projects/ecookbook/issues'
+ assert_redirected_to :action => 'index', :project_id => 'ecookbook'
assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
assert_nil TimeEntry.find(1).issue_id
assert_nil TimeEntry.find(2).issue_id
@@ -669,22 +955,9 @@ class IssuesControllerTest < Test::Unit::TestCase
def test_destroy_issues_and_reassign_time_entries_to_another_issue
@request.session[:user_id] = 2
post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
- assert_redirected_to 'projects/ecookbook/issues'
+ assert_redirected_to :action => 'index', :project_id => 'ecookbook'
assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
assert_equal 2, TimeEntry.find(1).issue_id
assert_equal 2, TimeEntry.find(2).issue_id
end
-
- def test_destroy_attachment
- issue = Issue.find(3)
- a = issue.attachments.size
- @request.session[:user_id] = 2
- post :destroy_attachment, :id => 3, :attachment_id => 1
- assert_redirected_to 'issues/show/3'
- assert_nil Attachment.find_by_id(1)
- issue.reload
- assert_equal((a-1), issue.attachments.size)
- j = issue.journals.find(:first, :order => 'created_on DESC')
- assert_equal 'attachment', j.details.first.property
- end
end
diff --git a/test/functional/members_controller_test.rb b/test/functional/members_controller_test.rb
new file mode 100644
index 000000000..f69bde193
--- /dev/null
+++ b/test/functional/members_controller_test.rb
@@ -0,0 +1,15 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'members_controller'
+
+# Re-raise errors caught by the controller.
+class MembersController; def rescue_action(e) raise e end; end
+
+
+class MembersControllerTest < Test::Unit::TestCase
+ def test_members_routing
+ assert_routing(
+ {:method => :post, :path => 'projects/5234/members/new'},
+ :controller => 'members', :action => 'new', :id => '5234'
+ )
+ end
+end
diff --git a/test/functional/messages_controller_test.rb b/test/functional/messages_controller_test.rb
index b1b3ea942..d19249c06 100644
--- a/test/functional/messages_controller_test.rb
+++ b/test/functional/messages_controller_test.rb
@@ -31,6 +31,13 @@ class MessagesControllerTest < Test::Unit::TestCase
User.current = nil
end
+ def test_show_routing
+ assert_routing(
+ {:method => :get, :path => '/boards/22/topics/2'},
+ :controller => 'messages', :action => 'show', :id => '2', :board_id => '22'
+ )
+ end
+
def test_show
get :show, :board_id => 1, :id => 1
assert_response :success
@@ -54,6 +61,17 @@ class MessagesControllerTest < Test::Unit::TestCase
assert_response 404
end
+ def test_new_routing
+ assert_routing(
+ {:method => :get, :path => '/boards/lala/topics/new'},
+ :controller => 'messages', :action => 'new', :board_id => 'lala'
+ )
+ assert_recognizes(#TODO: POST to collection, need to adjust form accordingly
+ {:controller => 'messages', :action => 'new', :board_id => 'lala'},
+ {:method => :post, :path => '/boards/lala/topics/new'}
+ )
+ end
+
def test_get_new
@request.session[:user_id] = 2
get :new, :board_id => 1
@@ -64,7 +82,7 @@ class MessagesControllerTest < Test::Unit::TestCase
def test_post_new
@request.session[:user_id] = 2
ActionMailer::Base.deliveries.clear
- Setting.notified_events << 'message_posted'
+ Setting.notified_events = ['message_posted']
post :new, :board_id => 1,
:message => { :subject => 'Test created message',
@@ -78,7 +96,7 @@ class MessagesControllerTest < Test::Unit::TestCase
mail = ActionMailer::Base.deliveries.last
assert_kind_of TMail::Mail, mail
- assert_equal "[#{message.board.project.name} - #{message.board.name}] Test created message", mail.subject
+ assert_equal "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] Test created message", mail.subject
assert mail.body.include?('Message body')
# author
assert mail.bcc.include?('jsmith@somenet.foo')
@@ -86,6 +104,17 @@ class MessagesControllerTest < Test::Unit::TestCase
assert mail.bcc.include?('dlopper@somenet.foo')
end
+ def test_edit_routing
+ assert_routing(
+ {:method => :get, :path => '/boards/lala/topics/22/edit'},
+ :controller => 'messages', :action => 'edit', :board_id => 'lala', :id => '22'
+ )
+ assert_recognizes( #TODO: use PUT to topic_path, modify form accordingly
+ {:controller => 'messages', :action => 'edit', :board_id => 'lala', :id => '22'},
+ {:method => :post, :path => '/boards/lala/topics/22/edit'}
+ )
+ end
+
def test_get_edit
@request.session[:user_id] = 2
get :edit, :board_id => 1, :id => 1
@@ -104,6 +133,13 @@ class MessagesControllerTest < Test::Unit::TestCase
assert_equal 'New body', message.content
end
+ def test_reply_routing
+ assert_recognizes(
+ {:controller => 'messages', :action => 'reply', :board_id => '22', :id => '555'},
+ {:method => :post, :path => '/boards/22/topics/555/replies'}
+ )
+ end
+
def test_reply
@request.session[:user_id] = 2
post :reply, :board_id => 1, :id => 1, :reply => { :content => 'This is a test reply', :subject => 'Test reply' }
@@ -111,6 +147,13 @@ class MessagesControllerTest < Test::Unit::TestCase
assert Message.find_by_subject('Test reply')
end
+ def test_destroy_routing
+ assert_recognizes(#TODO: use DELETE to topic_path, adjust form accordingly
+ {:controller => 'messages', :action => 'destroy', :board_id => '22', :id => '555'},
+ {:method => :post, :path => '/boards/22/topics/555/destroy'}
+ )
+ end
+
def test_destroy_topic
@request.session[:user_id] = 2
post :destroy, :board_id => 1, :id => 1
diff --git a/test/functional/my_controller_test.rb b/test/functional/my_controller_test.rb
index c1349ace4..997340096 100644
--- a/test/functional/my_controller_test.rb
+++ b/test/functional/my_controller_test.rb
@@ -22,7 +22,7 @@ require 'my_controller'
class MyController; def rescue_action(e) raise e end; end
class MyControllerTest < Test::Unit::TestCase
- fixtures :users, :issues, :issue_statuses, :trackers, :enumerations
+ fixtures :users, :issues, :issue_statuses, :trackers, :enumerations, :custom_fields
def setup
@controller = MyController.new
@@ -43,20 +43,37 @@ class MyControllerTest < Test::Unit::TestCase
assert_template 'page'
end
- def test_get_account
+ def test_my_account_should_show_editable_custom_fields
get :account
assert_response :success
assert_template 'account'
assert_equal User.find(2), assigns(:user)
+
+ assert_tag :input, :attributes => { :name => 'user[custom_field_values][4]'}
+ end
+
+ def test_my_account_should_not_show_non_editable_custom_fields
+ UserCustomField.find(4).update_attribute :editable, false
+
+ get :account
+ assert_response :success
+ assert_template 'account'
+ assert_equal User.find(2), assigns(:user)
+
+ assert_no_tag :input, :attributes => { :name => 'user[custom_field_values][4]'}
end
def test_update_account
- post :account, :user => {:firstname => "Joe", :login => "root", :admin => 1}
+ post :account, :user => {:firstname => "Joe",
+ :login => "root",
+ :admin => 1,
+ :custom_field_values => {"4" => "0100562500"}}
assert_redirected_to 'my/account'
user = User.find(2)
assert_equal user, assigns(:user)
assert_equal "Joe", user.firstname
assert_equal "jsmith", user.login
+ assert_equal "0100562500", user.custom_value_for(4).value
assert !user.admin?
end
diff --git a/test/functional/news_controller_test.rb b/test/functional/news_controller_test.rb
index 01f8015b9..651471b66 100644
--- a/test/functional/news_controller_test.rb
+++ b/test/functional/news_controller_test.rb
@@ -31,6 +31,20 @@ class NewsControllerTest < Test::Unit::TestCase
User.current = nil
end
+ def test_index_routing
+ assert_routing(
+ {:method => :get, :path => '/news'},
+ :controller => 'news', :action => 'index'
+ )
+ end
+
+ def test_index_routing_formatted
+ assert_routing(
+ {:method => :get, :path => '/news.atom'},
+ :controller => 'news', :action => 'index', :format => 'atom'
+ )
+ end
+
def test_index
get :index
assert_response :success
@@ -38,6 +52,20 @@ class NewsControllerTest < Test::Unit::TestCase
assert_not_nil assigns(:newss)
assert_nil assigns(:project)
end
+
+ def test_index_with_project_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/news'},
+ :controller => 'news', :action => 'index', :project_id => '567'
+ )
+ end
+
+ def test_index_with_project_routing_formatted
+ assert_routing(
+ {:method => :get, :path => '/projects/567/news.atom'},
+ :controller => 'news', :action => 'index', :project_id => '567', :format => 'atom'
+ )
+ end
def test_index_with_project
get :index, :project_id => 1
@@ -46,6 +74,13 @@ class NewsControllerTest < Test::Unit::TestCase
assert_not_nil assigns(:newss)
end
+ def test_show_routing
+ assert_routing(
+ {:method => :get, :path => '/news/2'},
+ :controller => 'news', :action => 'show', :id => '2'
+ )
+ end
+
def test_show
get :show, :id => 1
assert_response :success
@@ -58,6 +93,17 @@ class NewsControllerTest < Test::Unit::TestCase
assert_response 404
end
+ def test_new_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/news/new'},
+ :controller => 'news', :action => 'new', :project_id => '567'
+ )
+ assert_recognizes(
+ {:controller => 'news', :action => 'new', :project_id => '567'},
+ {:method => :post, :path => '/projects/567/news'}
+ )
+ end
+
def test_get_new
@request.session[:user_id] = 2
get :new, :project_id => 1
@@ -79,6 +125,17 @@ class NewsControllerTest < Test::Unit::TestCase
assert_equal Project.find(1), news.project
end
+ def test_edit_routing
+ assert_routing(
+ {:method => :get, :path => '/news/234'},
+ :controller => 'news', :action => 'show', :id => '234'
+ )
+ assert_recognizes(#TODO: PUT to news URI instead, need to modify form
+ {:controller => 'news', :action => 'edit', :id => '567'},
+ {:method => :post, :path => '/news/567/edit'}
+ )
+ end
+
def test_get_edit
@request.session[:user_id] = 2
get :edit, :id => 1
@@ -127,6 +184,13 @@ class NewsControllerTest < Test::Unit::TestCase
assert_equal comments_count - 1, News.find(1).comments.size
end
+ def test_destroy_routing
+ assert_recognizes(#TODO: should use DELETE to news URI, need to change form
+ {:controller => 'news', :action => 'destroy', :id => '567'},
+ {:method => :post, :path => '/news/567/destroy'}
+ )
+ end
+
def test_destroy
@request.session[:user_id] = 2
post :destroy, :id => 1
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
index 45af4e33e..25f9ad78e 100644
--- a/test/functional/projects_controller_test.rb
+++ b/test/functional/projects_controller_test.rb
@@ -23,25 +23,48 @@ class ProjectsController; def rescue_action(e) raise e end; end
class ProjectsControllerTest < Test::Unit::TestCase
fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
- :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages
+ :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
+ :attachments
def setup
@controller = ProjectsController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@request.session[:user_id] = nil
+ Setting.default_language = 'en'
end
-
+
+ def test_index_routing
+ assert_routing(
+ {:method => :get, :path => '/projects'},
+ :controller => 'projects', :action => 'index'
+ )
+ end
+
def test_index
get :index
assert_response :success
assert_template 'index'
- assert_not_nil assigns(:project_tree)
- # Root project as hash key
- assert assigns(:project_tree).keys.include?(Project.find(1))
- # Subproject in corresponding value
- assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3))
- end
+ assert_not_nil assigns(:projects)
+
+ assert_tag :ul, :child => {:tag => 'li',
+ :descendant => {:tag => 'a', :content => 'eCookbook'},
+ :child => { :tag => 'ul',
+ :descendant => { :tag => 'a',
+ :content => 'Child of private child'
+ }
+ }
+ }
+
+ assert_no_tag :a, :content => /Private child of eCookbook/
+ end
+
+ def test_index_atom_routing
+ assert_routing(
+ {:method => :get, :path => '/projects.atom'},
+ :controller => 'projects', :action => 'index', :format => 'atom'
+ )
+ end
def test_index_atom
get :index, :format => 'atom'
@@ -50,12 +73,34 @@ class ProjectsControllerTest < Test::Unit::TestCase
assert_select 'feed>title', :text => 'Redmine: Latest projects'
assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
end
-
- def test_show_by_id
- get :show, :id => 1
+
+ def test_add_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/new'},
+ :controller => 'projects', :action => 'add'
+ )
+ assert_recognizes(
+ {:controller => 'projects', :action => 'add'},
+ {:method => :post, :path => '/projects/new'}
+ )
+ assert_recognizes(
+ {:controller => 'projects', :action => 'add'},
+ {:method => :post, :path => '/projects'}
+ )
+ end
+
+ def test_show_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/test'},
+ :controller => 'projects', :action => 'show', :id => 'test'
+ )
+ end
+
+ def test_show_by_id
+ get :show, :id => 1
assert_response :success
- assert_template 'show'
- assert_not_nil assigns(:project)
+ assert_template 'show'
+ assert_not_nil assigns(:project)
end
def test_show_by_identifier
@@ -81,6 +126,17 @@ class ProjectsControllerTest < Test::Unit::TestCase
assert_tag :tag => 'a', :content => /Private child/
end
+ def test_settings_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/4223/settings'},
+ :controller => 'projects', :action => 'settings', :id => '4223'
+ )
+ assert_routing(
+ {:method => :get, :path => '/projects/4223/settings/members'},
+ :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members'
+ )
+ end
+
def test_settings
@request.session[:user_id] = 2 # manager
get :settings, :id => 1
@@ -97,13 +153,49 @@ class ProjectsControllerTest < Test::Unit::TestCase
assert_equal 'Test changed name', project.name
end
+ def test_add_version_routing
+ assert_routing(
+ {:method => :get, :path => 'projects/64/versions/new'},
+ :controller => 'projects', :action => 'add_version', :id => '64'
+ )
+ assert_routing(
+ #TODO: use PUT
+ {:method => :post, :path => 'projects/64/versions/new'},
+ :controller => 'projects', :action => 'add_version', :id => '64'
+ )
+ end
+
+ def test_add_issue_category_routing
+ assert_routing(
+ {:method => :get, :path => 'projects/test/categories/new'},
+ :controller => 'projects', :action => 'add_issue_category', :id => 'test'
+ )
+ assert_routing(
+ #TODO: use PUT and update form
+ {:method => :post, :path => 'projects/64/categories/new'},
+ :controller => 'projects', :action => 'add_issue_category', :id => '64'
+ )
+ end
+
+ def test_destroy_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/destroy'},
+ :controller => 'projects', :action => 'destroy', :id => '567'
+ )
+ assert_routing(
+ #TODO: use DELETE and update form
+ {:method => :post, :path => 'projects/64/destroy'},
+ :controller => 'projects', :action => 'destroy', :id => '64'
+ )
+ end
+
def test_get_destroy
@request.session[:user_id] = 1 # admin
get :destroy, :id => 1
assert_response :success
assert_template 'destroy'
assert_not_nil Project.find_by_id(1)
- end
+ end
def test_post_destroy
@request.session[:user_id] = 1 # admin
@@ -111,19 +203,95 @@ class ProjectsControllerTest < Test::Unit::TestCase
assert_redirected_to 'admin/projects'
assert_nil Project.find_by_id(1)
end
-
- def test_list_files
- get :list_files, :id => 1
- assert_response :success
- assert_template 'list_files'
- assert_not_nil assigns(:versions)
- end
-
- def test_changelog
- get :changelog, :id => 1
- assert_response :success
- assert_template 'changelog'
- assert_not_nil assigns(:versions)
+
+ def test_add_file
+ set_tmp_attachments_directory
+ @request.session[:user_id] = 2
+ Setting.notified_events = ['file_added']
+ ActionMailer::Base.deliveries.clear
+
+ assert_difference 'Attachment.count' do
+ post :add_file, :id => 1, :version_id => '',
+ :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
+ end
+ assert_redirected_to 'projects/list_files/ecookbook'
+ a = Attachment.find(:first, :order => 'created_on DESC')
+ assert_equal 'testfile.txt', a.filename
+ assert_equal Project.find(1), a.container
+
+ mail = ActionMailer::Base.deliveries.last
+ assert_kind_of TMail::Mail, mail
+ assert_equal "[eCookbook] New file", mail.subject
+ assert mail.body.include?('testfile.txt')
+ end
+
+ def test_add_file_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/33/files/new'},
+ :controller => 'projects', :action => 'add_file', :id => '33'
+ )
+ assert_routing(
+ {:method => :post, :path => '/projects/33/files/new'},
+ :controller => 'projects', :action => 'add_file', :id => '33'
+ )
+ end
+
+ def test_add_version_file
+ set_tmp_attachments_directory
+ @request.session[:user_id] = 2
+ Setting.notified_events = ['file_added']
+
+ assert_difference 'Attachment.count' do
+ post :add_file, :id => 1, :version_id => '2',
+ :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
+ end
+ assert_redirected_to 'projects/list_files/ecookbook'
+ a = Attachment.find(:first, :order => 'created_on DESC')
+ assert_equal 'testfile.txt', a.filename
+ assert_equal Version.find(2), a.container
+ end
+
+ def test_list_files
+ get :list_files, :id => 1
+ assert_response :success
+ assert_template 'list_files'
+ assert_not_nil assigns(:containers)
+
+ # file attached to the project
+ assert_tag :a, :content => 'project_file.zip',
+ :attributes => { :href => '/attachments/download/8/project_file.zip' }
+
+ # file attached to a project's version
+ assert_tag :a, :content => 'version_file.zip',
+ :attributes => { :href => '/attachments/download/9/version_file.zip' }
+ end
+
+ def test_list_files_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/33/files'},
+ :controller => 'projects', :action => 'list_files', :id => '33'
+ )
+ end
+
+ def test_changelog_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/44/changelog'},
+ :controller => 'projects', :action => 'changelog', :id => '44'
+ )
+ end
+
+ def test_changelog
+ get :changelog, :id => 1
+ assert_response :success
+ assert_template 'changelog'
+ assert_not_nil assigns(:versions)
+ end
+
+ def test_roadmap_routing
+ assert_routing(
+ {:method => :get, :path => 'projects/33/roadmap'},
+ :controller => 'projects', :action => 'roadmap', :id => '33'
+ )
end
def test_roadmap
@@ -147,7 +315,21 @@ class ProjectsControllerTest < Test::Unit::TestCase
# Completed version appears
assert assigns(:versions).include?(Version.find(1))
end
-
+
+ def test_project_activity_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/1/activity'},
+ :controller => 'projects', :action => 'activity', :id => '1'
+ )
+ end
+
+ def test_project_activity_atom_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/1/activity.atom'},
+ :controller => 'projects', :action => 'activity', :id => '1', :format => 'atom'
+ )
+ end
+
def test_project_activity
get :activity, :id => 1, :with_subprojects => 0
assert_response :success
@@ -184,6 +366,10 @@ class ProjectsControllerTest < Test::Unit::TestCase
}
end
+ def test_global_activity_routing
+ assert_routing({:method => :get, :path => '/activity'}, :controller => 'projects', :action => 'activity')
+ end
+
def test_global_activity
get :activity
assert_response :success
@@ -202,19 +388,57 @@ class ProjectsControllerTest < Test::Unit::TestCase
}
end
+ def test_user_activity
+ get :activity, :user_id => 2
+ assert_response :success
+ assert_template 'activity'
+ assert_not_nil assigns(:events_by_day)
+
+ assert_tag :tag => "h3",
+ :content => /#{3.day.ago.to_date.day}/,
+ :sibling => { :tag => "dl",
+ :child => { :tag => "dt",
+ :attributes => { :class => /issue/ },
+ :child => { :tag => "a",
+ :content => /#{Issue.find(1).subject}/,
+ }
+ }
+ }
+ end
+
+ def test_global_activity_atom_routing
+ assert_routing({:method => :get, :path => '/activity.atom'}, :controller => 'projects', :action => 'activity', :format => 'atom')
+ end
+
def test_activity_atom_feed
get :activity, :format => 'atom'
assert_response :success
assert_template 'common/feed.atom.rxml'
end
- def test_archive
+ def test_archive_routing
+ assert_routing(
+ #TODO: use PUT to project path and modify form
+ {:method => :post, :path => 'projects/64/archive'},
+ :controller => 'projects', :action => 'archive', :id => '64'
+ )
+ end
+
+ def test_archive
@request.session[:user_id] = 1 # admin
post :archive, :id => 1
assert_redirected_to 'admin/projects'
assert !Project.find(1).active?
end
+ def test_unarchive_routing
+ assert_routing(
+ #TODO: use PUT to project path and modify form
+ {:method => :post, :path => '/projects/567/unarchive'},
+ :controller => 'projects', :action => 'unarchive', :id => '567'
+ )
+ end
+
def test_unarchive
@request.session[:user_id] = 1 # admin
Project.find(1).archive
@@ -223,6 +447,23 @@ class ProjectsControllerTest < Test::Unit::TestCase
assert Project.find(1).active?
end
+ def test_jump_should_redirect_to_active_tab
+ get :show, :id => 1, :jump => 'issues'
+ assert_redirected_to 'projects/ecookbook/issues'
+ end
+
+ def test_jump_should_not_redirect_to_inactive_tab
+ get :show, :id => 3, :jump => 'documents'
+ assert_response :success
+ assert_template 'show'
+ end
+
+ def test_jump_should_not_redirect_to_unknown_tab
+ get :show, :id => 3, :jump => 'foobar'
+ assert_response :success
+ assert_template 'show'
+ end
+
def test_project_menu
assert_no_difference 'Redmine::MenuManager.items(:project_menu).size' do
Redmine::MenuManager.map :project_menu do |menu|
@@ -233,14 +474,17 @@ class ProjectsControllerTest < Test::Unit::TestCase
get :show, :id => 1
assert_tag :div, :attributes => { :id => 'main-menu' },
- :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Foo' } }
+ :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Foo',
+ :attributes => { :class => 'foo' } } }
assert_tag :div, :attributes => { :id => 'main-menu' },
- :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Bar' },
+ :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Bar',
+ :attributes => { :class => 'bar' } },
:before => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' } } }
assert_tag :div, :attributes => { :id => 'main-menu' },
- :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' },
+ :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK',
+ :attributes => { :class => 'hello' } },
:before => { :tag => 'li', :child => { :tag => 'a', :content => 'Activity' } } }
# Remove the menu items
diff --git a/test/functional/reports_controller_test.rb b/test/functional/reports_controller_test.rb
new file mode 100644
index 000000000..b90d904f8
--- /dev/null
+++ b/test/functional/reports_controller_test.rb
@@ -0,0 +1,20 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'reports_controller'
+
+# Re-raise errors caught by the controller.
+class ReportsController; def rescue_action(e) raise e end; end
+
+
+class ReportsControllerTest < Test::Unit::TestCase
+ def test_issue_report_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/issues/report'},
+ :controller => 'reports', :action => 'issue_report', :id => '567'
+ )
+ assert_routing(
+ {:method => :get, :path => '/projects/567/issues/report/assigned_to'},
+ :controller => 'reports', :action => 'issue_report', :id => '567', :detail => 'assigned_to'
+ )
+
+ end
+end
diff --git a/test/functional/repositories_controller_test.rb b/test/functional/repositories_controller_test.rb
index 2892f3bd1..ccf5e77ba 100644
--- a/test/functional/repositories_controller_test.rb
+++ b/test/functional/repositories_controller_test.rb
@@ -31,25 +31,134 @@ class RepositoriesControllerTest < Test::Unit::TestCase
User.current = nil
end
+ def test_show_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/redmine/repository'},
+ :controller => 'repositories', :action => 'show', :id => 'redmine'
+ )
+ end
+
+ def test_edit_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/world_domination/repository/edit'},
+ :controller => 'repositories', :action => 'edit', :id => 'world_domination'
+ )
+ assert_routing(
+ {:method => :post, :path => '/projects/world_domination/repository/edit'},
+ :controller => 'repositories', :action => 'edit', :id => 'world_domination'
+ )
+ end
+
+ def test_revisions_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/redmine/repository/revisions'},
+ :controller => 'repositories', :action => 'revisions', :id => 'redmine'
+ )
+ end
+
+ def test_revisions_atom_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/redmine/repository/revisions.atom'},
+ :controller => 'repositories', :action => 'revisions', :id => 'redmine', :format => 'atom'
+ )
+ end
+
def test_revisions
get :revisions, :id => 1
assert_response :success
assert_template 'revisions'
assert_not_nil assigns(:changesets)
end
+
+ def test_revision_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/restmine/repository/revisions/2457'},
+ :controller => 'repositories', :action => 'revision', :id => 'restmine', :rev => '2457'
+ )
+ end
def test_revision_with_before_nil_and_afer_normal
get :revision, {:id => 1, :rev => 1}
assert_response :success
assert_template 'revision'
assert_no_tag :tag => "div", :attributes => { :class => "contextual" },
- :child => { :tag => "a", :attributes => { :href => '/repositories/revision/ecookbook/0'}
+ :child => { :tag => "a", :attributes => { :href => '/projects/ecookbook/repository/revisions/0'}
}
assert_tag :tag => "div", :attributes => { :class => "contextual" },
- :child => { :tag => "a", :attributes => { :href => '/repositories/revision/ecookbook/2'}
+ :child => { :tag => "a", :attributes => { :href => '/projects/ecookbook/repository/revisions/2'}
}
end
+
+ def test_diff_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/restmine/repository/revisions/2457/diff'},
+ :controller => 'repositories', :action => 'diff', :id => 'restmine', :rev => '2457'
+ )
+ end
+
+ def test_unified_diff_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/restmine/repository/revisions/2457/diff.diff'},
+ :controller => 'repositories', :action => 'diff', :id => 'restmine', :rev => '2457', :format => 'diff'
+ )
+ end
+
+ def test_diff_path_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/restmine/repository/diff/path/to/file.c'},
+ :controller => 'repositories', :action => 'diff', :id => 'restmine', :path => %w[path to file.c]
+ )
+ end
+ def test_diff_path_routing_with_revision
+ assert_routing(
+ {:method => :get, :path => '/projects/restmine/repository/revisions/2/diff/path/to/file.c'},
+ :controller => 'repositories', :action => 'diff', :id => 'restmine', :path => %w[path to file.c], :rev => '2'
+ )
+ end
+
+ def test_browse_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/restmine/repository/browse/path/to/dir'},
+ :controller => 'repositories', :action => 'browse', :id => 'restmine', :path => %w[path to dir]
+ )
+ end
+
+ def test_entry_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/restmine/repository/entry/path/to/file.c'},
+ :controller => 'repositories', :action => 'entry', :id => 'restmine', :path => %w[path to file.c]
+ )
+ end
+
+ def test_entry_routing_with_revision
+ assert_routing(
+ {:method => :get, :path => '/projects/restmine/repository/revisions/2/entry/path/to/file.c'},
+ :controller => 'repositories', :action => 'entry', :id => 'restmine', :path => %w[path to file.c], :rev => '2'
+ )
+ end
+
+ def test_annotate_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/restmine/repository/annotate/path/to/file.c'},
+ :controller => 'repositories', :action => 'annotate', :id => 'restmine', :path => %w[path to file.c]
+ )
+ end
+
+ def test_changesrouting
+ assert_routing(
+ {:method => :get, :path => '/projects/restmine/repository/changes/path/to/file.c'},
+ :controller => 'repositories', :action => 'changes', :id => 'restmine', :path => %w[path to file.c]
+ )
+ end
+
+ def test_statistics_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/restmine/repository/statistics'},
+ :controller => 'repositories', :action => 'stats', :id => 'restmine'
+ )
+ end
+
def test_graph_commits_per_month
get :graph, :id => 1, :graph => 'commits_per_month'
assert_response :success
@@ -61,4 +170,38 @@ class RepositoriesControllerTest < Test::Unit::TestCase
assert_response :success
assert_equal 'image/svg+xml', @response.content_type
end
+
+ def test_committers
+ @request.session[:user_id] = 2
+ # add a commit with an unknown user
+ Changeset.create!(:repository => Project.find(1).repository, :committer => 'foo', :committed_on => Time.now, :revision => 100, :comments => 'Committed by foo.')
+
+ get :committers, :id => 1
+ assert_response :success
+ assert_template 'committers'
+
+ assert_tag :td, :content => 'dlopper',
+ :sibling => { :tag => 'td',
+ :child => { :tag => 'select', :attributes => { :name => %r{^committers\[\d+\]\[\]$} },
+ :child => { :tag => 'option', :content => 'Dave Lopper',
+ :attributes => { :value => '3', :selected => 'selected' }}}}
+ assert_tag :td, :content => 'foo',
+ :sibling => { :tag => 'td',
+ :child => { :tag => 'select', :attributes => { :name => %r{^committers\[\d+\]\[\]$} }}}
+ assert_no_tag :td, :content => 'foo',
+ :sibling => { :tag => 'td',
+ :descendant => { :tag => 'option', :attributes => { :selected => 'selected' }}}
+ end
+
+ def test_map_committers
+ @request.session[:user_id] = 2
+ # add a commit with an unknown user
+ c = Changeset.create!(:repository => Project.find(1).repository, :committer => 'foo', :committed_on => Time.now, :revision => 100, :comments => 'Committed by foo.')
+
+ assert_no_difference "Changeset.count(:conditions => 'user_id = 3')" do
+ post :committers, :id => 1, :committers => { '0' => ['foo', '2'], '1' => ['dlopper', '3']}
+ assert_redirected_to '/repositories/committers/ecookbook'
+ assert_equal User.find(2), c.reload.user
+ end
+ end
end
diff --git a/test/functional/repositories_subversion_controller_test.rb b/test/functional/repositories_subversion_controller_test.rb
index 245a170d1..a3918a922 100644
--- a/test/functional/repositories_subversion_controller_test.rb
+++ b/test/functional/repositories_subversion_controller_test.rb
@@ -78,13 +78,15 @@ class RepositoriesSubversionControllerTest < Test::Unit::TestCase
get :changes, :id => 1, :path => ['subversion_test', 'folder', 'helloworld.rb' ]
assert_response :success
assert_template 'changes'
- # svn properties
- assert_not_nil assigns(:properties)
- assert_equal 'native', assigns(:properties)['svn:eol-style']
- assert_tag :ul,
- :child => { :tag => 'li',
- :child => { :tag => 'b', :content => 'svn:eol-style' },
- :child => { :tag => 'span', :content => 'native' } }
+ # svn properties displayed with svn >= 1.5 only
+ if Redmine::Scm::Adapters::SubversionAdapter.client_version_above?([1, 5, 0])
+ assert_not_nil assigns(:properties)
+ assert_equal 'native', assigns(:properties)['svn:eol-style']
+ assert_tag :ul,
+ :child => { :tag => 'li',
+ :child => { :tag => 'b', :content => 'svn:eol-style' },
+ :child => { :tag => 'span', :content => 'native' } }
+ end
end
def test_entry
@@ -129,11 +131,11 @@ class RepositoriesSubversionControllerTest < Test::Unit::TestCase
:child => { :tag => 'li',
# link to the entry at rev 2
:child => { :tag => 'a',
- :attributes => {:href => '/repositories/entry/ecookbook/test/some/path/in/the/repo?rev=2'},
+ :attributes => {:href => '/projects/ecookbook/repository/revisions/2/entry/test/some/path/in/the/repo'},
:content => 'repo',
# link to partial diff
:sibling => { :tag => 'a',
- :attributes => { :href => '/repositories/diff/ecookbook/test/some/path/in/the/repo?rev=2' }
+ :attributes => { :href => '/projects/ecookbook/repository/revisions/2/diff/test/some/path/in/the/repo' }
}
}
}
@@ -151,11 +153,11 @@ class RepositoriesSubversionControllerTest < Test::Unit::TestCase
:child => { :tag => 'li',
# link to the entry at rev 2
:child => { :tag => 'a',
- :attributes => {:href => '/repositories/entry/ecookbook/path/in/the/repo?rev=2'},
+ :attributes => {:href => '/projects/ecookbook/repository/revisions/2/entry/path/in/the/repo'},
:content => 'repo',
# link to partial diff
:sibling => { :tag => 'a',
- :attributes => { :href => '/repositories/diff/ecookbook/path/in/the/repo?rev=2' }
+ :attributes => { :href => '/projects/ecookbook/repository/revisions/2/diff/path/in/the/repo' }
}
}
}
diff --git a/test/functional/roles_controller_test.rb b/test/functional/roles_controller_test.rb
index d70a4f0c3..188e79e2d 100644
--- a/test/functional/roles_controller_test.rb
+++ b/test/functional/roles_controller_test.rb
@@ -118,46 +118,6 @@ class RolesControllerTest < Test::Unit::TestCase
assert_not_nil Role.find_by_id(1)
end
- def test_get_workflow
- get :workflow
- assert_response :success
- assert_template 'workflow'
- assert_not_nil assigns(:roles)
- assert_not_nil assigns(:trackers)
- end
-
- def test_get_workflow_with_role_and_tracker
- get :workflow, :role_id => 2, :tracker_id => 1
- assert_response :success
- assert_template 'workflow'
- # allowed transitions
- assert_tag :tag => 'input', :attributes => { :type => 'checkbox',
- :name => 'issue_status[2][]',
- :value => '1',
- :checked => 'checked' }
- # not allowed
- assert_tag :tag => 'input', :attributes => { :type => 'checkbox',
- :name => 'issue_status[2][]',
- :value => '3',
- :checked => nil }
- end
-
- def test_post_workflow
- post :workflow, :role_id => 2, :tracker_id => 1, :issue_status => {'4' => ['5'], '3' => ['1', '2']}
- assert_redirected_to 'roles/workflow'
-
- assert_equal 3, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2})
- assert_not_nil Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2})
- assert_nil Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4})
- end
-
- def test_clear_workflow
- assert Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2}) > 0
-
- post :workflow, :role_id => 2, :tracker_id => 1
- assert_equal 0, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2})
- end
-
def test_get_report
get :report
assert_response :success
diff --git a/test/functional/search_controller_test.rb b/test/functional/search_controller_test.rb
index ce06ec298..b804bbd28 100644
--- a/test/functional/search_controller_test.rb
+++ b/test/functional/search_controller_test.rb
@@ -45,6 +45,17 @@ class SearchControllerTest < Test::Unit::TestCase
assert_tag :a, :content => 'Changesets (4)'
end
+ def test_search_issues
+ get :index, :q => 'issue', :issues => 1
+ assert_response :success
+ assert_template 'index'
+
+ assert assigns(:results).include?(Issue.find(8))
+ assert assigns(:results).include?(Issue.find(5))
+ assert_tag :dt, :attributes => { :class => /issue closed/ },
+ :child => { :tag => 'a', :content => /Closed/ }
+ end
+
def test_search_project_and_subprojects
get :index, :id => 1, :q => 'recipe subproject', :scope => 'subprojects', :submit => 'Search'
assert_response :success
diff --git a/test/functional/sys_api_test.rb b/test/functional/sys_api_test.rb
deleted file mode 100644
index 48ed780d0..000000000
--- a/test/functional/sys_api_test.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require File.dirname(__FILE__) + '/../test_helper'
-require 'sys_controller'
-
-# Re-raise errors caught by the controller.
-class SysController; def rescue_action(e) raise e end; end
-
-class SysControllerTest < Test::Unit::TestCase
- fixtures :projects, :enabled_modules, :repositories
-
- def setup
- @controller = SysController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
- # Enable WS
- Setting.sys_api_enabled = 1
- end
-
- def test_projects_with_repository_enabled
- result = invoke :projects_with_repository_enabled
- assert_equal EnabledModule.count(:all, :conditions => {:name => 'repository'}), result.size
-
- project = result.first
- assert project.is_a?(AWSProjectWithRepository)
-
- assert project.respond_to?(:id)
- assert_equal 1, project.id
-
- assert project.respond_to?(:identifier)
- assert_equal 'ecookbook', project.identifier
-
- assert project.respond_to?(:name)
- assert_equal 'eCookbook', project.name
-
- assert project.respond_to?(:is_public)
- assert project.is_public
-
- assert project.respond_to?(:repository)
- assert project.repository.is_a?(Repository)
- end
-
- def test_repository_created
- project = Project.find(3)
- assert_nil project.repository
- assert invoke(:repository_created, project.identifier, 'Subversion', 'http://localhost/svn')
- project.reload
- assert_not_nil project.repository
- assert project.repository.is_a?(Repository::Subversion)
- assert_equal 'http://localhost/svn', project.repository.url
- end
-end
diff --git a/test/functional/sys_controller_test.rb b/test/functional/sys_controller_test.rb
new file mode 100644
index 000000000..47228a4db
--- /dev/null
+++ b/test/functional/sys_controller_test.rb
@@ -0,0 +1,55 @@
+# Redmine - project management software
+# Copyright (C) 2006-2009 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sys_controller'
+
+# Re-raise errors caught by the controller.
+class SysController; def rescue_action(e) raise e end; end
+
+class SysControllerTest < Test::Unit::TestCase
+ fixtures :projects, :repositories
+
+ def setup
+ @controller = SysController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ Setting.sys_api_enabled = '1'
+ end
+
+ def test_projects_with_repository_enabled
+ get :projects
+ assert_response :success
+ assert_equal 'application/xml', @response.content_type
+ with_options :tag => 'projects' do |test|
+ test.assert_tag :children => { :count => Project.active.has_module(:repository).count }
+ end
+ end
+
+ def test_create_project_repository
+ assert_nil Project.find(4).repository
+
+ post :create_project_repository, :id => 4,
+ :vendor => 'Subversion',
+ :repository => { :url => 'file:///create/project/repository/subproject2'}
+ assert_response :created
+
+ r = Project.find(4).repository
+ assert r.is_a?(Repository::Subversion)
+ assert_equal 'file:///create/project/repository/subproject2', r.url
+ end
+end
diff --git a/test/functional/timelog_controller_test.rb b/test/functional/timelog_controller_test.rb
index 28f2a28e2..46c3f3ef8 100644
--- a/test/functional/timelog_controller_test.rb
+++ b/test/functional/timelog_controller_test.rb
@@ -30,6 +30,28 @@ class TimelogControllerTest < Test::Unit::TestCase
@response = ActionController::TestResponse.new
end
+ def test_edit_routing
+ assert_routing(
+ {:method => :get, :path => '/issues/567/time_entries/new'},
+ :controller => 'timelog', :action => 'edit', :issue_id => '567'
+ )
+ assert_routing(
+ {:method => :get, :path => '/projects/ecookbook/time_entries/new'},
+ :controller => 'timelog', :action => 'edit', :project_id => 'ecookbook'
+ )
+ assert_routing(
+ {:method => :get, :path => '/projects/ecookbook/issues/567/time_entries/new'},
+ :controller => 'timelog', :action => 'edit', :project_id => 'ecookbook', :issue_id => '567'
+ )
+
+ #TODO: change new form to POST to issue_time_entries_path instead of to edit action
+ #TODO: change edit form to PUT to time_entry_path
+ assert_routing(
+ {:method => :get, :path => '/time_entries/22/edit'},
+ :controller => 'timelog', :action => 'edit', :id => '22'
+ )
+ end
+
def test_get_edit
@request.session[:user_id] = 3
get :edit, :project_id => 1
@@ -40,7 +62,18 @@ class TimelogControllerTest < Test::Unit::TestCase
:content => 'Development'
end
+ def test_get_edit_existing_time
+ @request.session[:user_id] = 2
+ get :edit, :id => 2, :project_id => nil
+ assert_response :success
+ assert_template 'edit'
+ # Default activity selected
+ assert_tag :tag => 'form', :attributes => { :action => '/projects/ecookbook/timelog/edit/2' }
+ end
+
def test_post_edit
+ # TODO: should POST to issues’ time log instead of project. change form
+ # and routing
@request.session[:user_id] = 3
post :edit, :project_id => 1,
:time_entry => {:comments => 'Some work on TimelogControllerTest',
@@ -49,7 +82,7 @@ class TimelogControllerTest < Test::Unit::TestCase
:spent_on => '2008-03-14',
:issue_id => '1',
:hours => '7.3'}
- assert_redirected_to 'projects/ecookbook/timelog/details'
+ assert_redirected_to :action => 'details', :project_id => 'ecookbook'
i = Issue.find(1)
t = TimeEntry.find_by_comments('Some work on TimelogControllerTest')
@@ -70,7 +103,7 @@ class TimelogControllerTest < Test::Unit::TestCase
post :edit, :id => 1,
:time_entry => {:issue_id => '2',
:hours => '8'}
- assert_redirected_to 'projects/ecookbook/timelog/details'
+ assert_redirected_to :action => 'details', :project_id => 'ecookbook'
entry.reload
assert_equal 8, entry.hours
@@ -78,18 +111,44 @@ class TimelogControllerTest < Test::Unit::TestCase
assert_equal 2, entry.user_id
end
+ def test_destroy_routing
+ #TODO: use DELETE to time_entry_path
+ assert_routing(
+ {:method => :post, :path => '/time_entries/55/destroy'},
+ :controller => 'timelog', :action => 'destroy', :id => '55'
+ )
+ end
+
def test_destroy
@request.session[:user_id] = 2
post :destroy, :id => 1
- assert_redirected_to 'projects/ecookbook/timelog/details'
+ assert_redirected_to :action => 'details', :project_id => 'ecookbook'
assert_nil TimeEntry.find_by_id(1)
end
-
+
+ def test_report_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/time_entries/report'},
+ :controller => 'timelog', :action => 'report', :project_id => '567'
+ )
+ assert_routing(
+ {:method => :get, :path => '/projects/567/time_entries/report.csv'},
+ :controller => 'timelog', :action => 'report', :project_id => '567', :format => 'csv'
+ )
+ end
+
def test_report_no_criteria
get :report, :project_id => 1
assert_response :success
assert_template 'report'
end
+
+ def test_report_routing_for_all_projects
+ assert_routing(
+ {:method => :get, :path => '/time_entries/report'},
+ :controller => 'timelog', :action => 'report'
+ )
+ end
def test_report_all_projects
get :report
@@ -103,7 +162,7 @@ class TimelogControllerTest < Test::Unit::TestCase
r.permissions_will_change!
r.save
get :report
- assert_redirected_to '/account/login'
+ assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftime_entries%2Freport'
end
def test_report_all_projects_one_criteria
@@ -201,7 +260,14 @@ class TimelogControllerTest < Test::Unit::TestCase
assert_not_nil assigns(:total_hours)
assert_equal "162.90", "%.2f" % assigns(:total_hours)
end
-
+
+ def test_project_details_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/time_entries'},
+ :controller => 'timelog', :action => 'details', :project_id => '567'
+ )
+ end
+
def test_details_at_project_level
get :details, :project_id => 1
assert_response :success
@@ -239,6 +305,23 @@ class TimelogControllerTest < Test::Unit::TestCase
assert_equal Date.today, assigns(:to)
end
+ def test_issue_details_routing
+ assert_routing(
+ {:method => :get, :path => 'time_entries'},
+ :controller => 'timelog', :action => 'details'
+ )
+ assert_routing(
+ {:method => :get, :path => '/issues/234/time_entries'},
+ :controller => 'timelog', :action => 'details', :issue_id => '234'
+ )
+ # TODO: issue detail page shouldnt link to project_issue_time_entries_path but to normal issues one
+ # doesnt seem to have effect on resulting page so controller can be left untouched
+ assert_routing(
+ {:method => :get, :path => '/projects/ecookbook/issues/123/time_entries'},
+ :controller => 'timelog', :action => 'details', :project_id => 'ecookbook', :issue_id => '123'
+ )
+ end
+
def test_details_at_issue_level
get :details, :issue_id => 1
assert_response :success
@@ -252,6 +335,39 @@ class TimelogControllerTest < Test::Unit::TestCase
assert_equal '2007-04-22'.to_date, assigns(:to)
end
+ def test_details_formatted_routing
+ assert_routing(
+ {:method => :get, :path => 'time_entries.atom'},
+ :controller => 'timelog', :action => 'details', :format => 'atom'
+ )
+ assert_routing(
+ {:method => :get, :path => 'time_entries.csv'},
+ :controller => 'timelog', :action => 'details', :format => 'csv'
+ )
+ end
+
+ def test_details_for_project_formatted_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/time_entries.atom'},
+ :controller => 'timelog', :action => 'details', :format => 'atom', :project_id => '567'
+ )
+ assert_routing(
+ {:method => :get, :path => '/projects/567/time_entries.csv'},
+ :controller => 'timelog', :action => 'details', :format => 'csv', :project_id => '567'
+ )
+ end
+
+ def test_details_for_issue_formatted_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/ecookbook/issues/123/time_entries.atom'},
+ :controller => 'timelog', :action => 'details', :project_id => 'ecookbook', :issue_id => '123', :format => 'atom'
+ )
+ assert_routing(
+ {:method => :get, :path => '/projects/ecookbook/issues/123/time_entries.csv'},
+ :controller => 'timelog', :action => 'details', :project_id => 'ecookbook', :issue_id => '123', :format => 'csv'
+ )
+ end
+
def test_details_atom_feed
get :details, :project_id => 1, :format => 'atom'
assert_response :success
diff --git a/test/functional/trackers_controller_test.rb b/test/functional/trackers_controller_test.rb
new file mode 100644
index 000000000..89bbf228f
--- /dev/null
+++ b/test/functional/trackers_controller_test.rb
@@ -0,0 +1,68 @@
+# Redmine - project management software
+# Copyright (C) 2006-2009 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require File.dirname(__FILE__) + '/../test_helper'
+require 'trackers_controller'
+
+# Re-raise errors caught by the controller.
+class TrackersController; def rescue_action(e) raise e end; end
+
+class TrackersControllerTest < Test::Unit::TestCase
+ fixtures :trackers, :projects, :projects_trackers, :users
+
+ def setup
+ @controller = TrackersController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ User.current = nil
+ @request.session[:user_id] = 1 # admin
+ end
+
+ def test_get_edit
+ Tracker.find(1).project_ids = [1, 3]
+
+ get :edit, :id => 1
+ assert_response :success
+ assert_template 'edit'
+
+ assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
+ :value => '1',
+ :checked => 'checked' }
+
+ assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
+ :value => '2',
+ :checked => nil }
+
+ assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
+ :value => '',
+ :type => 'hidden'}
+ end
+
+ def test_post_edit
+ post :edit, :id => 1, :tracker => { :name => 'Renamed',
+ :project_ids => ['1', '2', ''] }
+ assert_redirected_to '/trackers/list'
+ assert_equal [1, 2], Tracker.find(1).project_ids.sort
+ end
+
+ def test_post_edit_without_projects
+ post :edit, :id => 1, :tracker => { :name => 'Renamed',
+ :project_ids => [''] }
+ assert_redirected_to '/trackers/list'
+ assert Tracker.find(1).project_ids.empty?
+ end
+end
diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb
index 8629a7131..42e69f48d 100644
--- a/test/functional/users_controller_test.rb
+++ b/test/functional/users_controller_test.rb
@@ -32,11 +32,27 @@ class UsersControllerTest < Test::Unit::TestCase
@request.session[:user_id] = 1 # admin
end
+ def test_index_routing
+ #TODO: unify with list
+ assert_generates(
+ '/users',
+ :controller => 'users', :action => 'index'
+ )
+ end
+
def test_index
get :index
assert_response :success
assert_template 'list'
end
+
+ def test_list_routing
+ #TODO: rename action to index
+ assert_routing(
+ {:method => :get, :path => '/users'},
+ :controller => 'users', :action => 'list'
+ )
+ end
def test_list
get :list
@@ -47,16 +63,80 @@ class UsersControllerTest < Test::Unit::TestCase
assert_nil assigns(:users).detect {|u| !u.active?}
end
+ def test_list_with_name_filter
+ get :list, :name => 'john'
+ assert_response :success
+ assert_template 'list'
+ users = assigns(:users)
+ assert_not_nil users
+ assert_equal 1, users.size
+ assert_equal 'John', users.first.firstname
+ end
+
+ def test_add_routing
+ assert_routing(
+ {:method => :get, :path => '/users/new'},
+ :controller => 'users', :action => 'add'
+ )
+ assert_recognizes(
+ #TODO: remove this and replace with POST to collection, need to modify form
+ {:controller => 'users', :action => 'add'},
+ {:method => :post, :path => '/users/new'}
+ )
+ assert_recognizes(
+ {:controller => 'users', :action => 'add'},
+ {:method => :post, :path => '/users'}
+ )
+ end
+
+ def test_edit_routing
+ assert_routing(
+ {:method => :get, :path => '/users/444/edit'},
+ :controller => 'users', :action => 'edit', :id => '444'
+ )
+ assert_routing(
+ {:method => :get, :path => '/users/222/edit/membership'},
+ :controller => 'users', :action => 'edit', :id => '222', :tab => 'membership'
+ )
+ assert_recognizes(
+ #TODO: use PUT on user_path, modify form
+ {:controller => 'users', :action => 'edit', :id => '444'},
+ {:method => :post, :path => '/users/444/edit'}
+ )
+ end
+
+ def test_add_membership_routing
+ assert_routing(
+ {:method => :post, :path => '/users/123/memberships'},
+ :controller => 'users', :action => 'edit_membership', :id => '123'
+ )
+ end
+
+ def test_edit_membership_routing
+ assert_routing(
+ {:method => :post, :path => '/users/123/memberships/55'},
+ :controller => 'users', :action => 'edit_membership', :id => '123', :membership_id => '55'
+ )
+ end
+
def test_edit_membership
post :edit_membership, :id => 2, :membership_id => 1,
:membership => { :role_id => 2}
- assert_redirected_to 'users/edit/2'
+ assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
assert_equal 2, Member.find(1).role_id
end
def test_destroy_membership
+ assert_routing(
+ #TODO: use DELETE method on user_membership_path, modify form
+ {:method => :post, :path => '/users/567/memberships/12/destroy'},
+ :controller => 'users', :action => 'destroy_membership', :id => '567', :membership_id => '12'
+ )
+ end
+
+ def test_destroy_membership
post :destroy_membership, :id => 2, :membership_id => 1
- assert_redirected_to 'users/edit/2'
+ assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships'
assert_nil Member.find_by_id(1)
end
end
diff --git a/test/functional/versions_controller_test.rb b/test/functional/versions_controller_test.rb
index 3a118701a..2ddf3a9f1 100644
--- a/test/functional/versions_controller_test.rb
+++ b/test/functional/versions_controller_test.rb
@@ -52,7 +52,7 @@ class VersionsControllerTest < Test::Unit::TestCase
post :edit, :id => 2,
:version => { :name => 'New version name',
:effective_date => Date.today.strftime("%Y-%m-%d")}
- assert_redirected_to 'projects/settings/ecookbook'
+ assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
version = Version.find(2)
assert_equal 'New version name', version.name
assert_equal Date.today, version.effective_date
@@ -61,7 +61,7 @@ class VersionsControllerTest < Test::Unit::TestCase
def test_destroy
@request.session[:user_id] = 2
post :destroy, :id => 3
- assert_redirected_to 'projects/settings/ecookbook'
+ assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
assert_nil Version.find_by_id(3)
end
diff --git a/test/functional/welcome_controller_test.rb b/test/functional/welcome_controller_test.rb
index df565a751..b45cb97c8 100644
--- a/test/functional/welcome_controller_test.rb
+++ b/test/functional/welcome_controller_test.rb
@@ -60,4 +60,11 @@ class WelcomeControllerTest < Test::Unit::TestCase
get :index
assert_equal :fr, @controller.current_language
end
+
+ def test_robots
+ get :robots
+ assert_response :success
+ assert_equal 'text/plain', @response.content_type
+ assert @response.body.match(%r{^Disallow: /projects/ecookbook/issues$})
+ end
end
diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb
index b5325357c..40dc04ae4 100644
--- a/test/functional/wiki_controller_test.rb
+++ b/test/functional/wiki_controller_test.rb
@@ -31,6 +31,21 @@ class WikiControllerTest < Test::Unit::TestCase
User.current = nil
end
+ def test_index_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/wiki'},
+ :controller => 'wiki', :action => 'index', :id => '567'
+ )
+ assert_routing(
+ {:method => :get, :path => '/projects/567/wiki/lalala'},
+ :controller => 'wiki', :action => 'index', :id => '567', :page => 'lalala'
+ )
+ assert_generates(
+ '/projects/567/wiki',
+ :controller => 'wiki', :action => 'index', :id => '567', :page => nil
+ )
+ end
+
def test_show_start_page
get :index, :id => 'ecookbook'
assert_response :success
@@ -40,7 +55,7 @@ class WikiControllerTest < Test::Unit::TestCase
# child_pages macro
assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
:child => { :tag => 'li',
- :child => { :tag => 'a', :attributes => { :href => '/wiki/ecookbook/Page_with_an_inline_image' },
+ :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
:content => 'Page with an inline image' } }
end
@@ -67,6 +82,17 @@ class WikiControllerTest < Test::Unit::TestCase
assert_template 'edit'
end
+ def test_edit_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/wiki/my_page/edit'},
+ :controller => 'wiki', :action => 'edit', :id => '567', :page => 'my_page'
+ )
+ assert_recognizes(#TODO: use PUT to page path, adjust forms accordingly
+ {:controller => 'wiki', :action => 'edit', :id => '567', :page => 'my_page'},
+ {:method => :post, :path => '/projects/567/wiki/my_page/edit'}
+ )
+ end
+
def test_create_page
@request.session[:user_id] = 2
post :edit, :id => 1,
@@ -74,13 +100,20 @@ class WikiControllerTest < Test::Unit::TestCase
:content => {:comments => 'Created the page',
:text => "h1. New page\n\nThis is a new page",
:version => 0}
- assert_redirected_to 'wiki/ecookbook/New_page'
+ assert_redirected_to :action => 'index', :id => 'ecookbook', :page => 'New_page'
page = Project.find(1).wiki.find_page('New page')
assert !page.new_record?
assert_not_nil page.content
assert_equal 'Created the page', page.content.comments
end
+ def test_preview_routing
+ assert_routing(
+ {:method => :post, :path => '/projects/567/wiki/CookBook_documentation/preview'},
+ :controller => 'wiki', :action => 'preview', :id => '567', :page => 'CookBook_documentation'
+ )
+ end
+
def test_preview
@request.session[:user_id] = 2
xhr :post, :preview, :id => 1, :page => 'CookBook_documentation',
@@ -103,6 +136,13 @@ class WikiControllerTest < Test::Unit::TestCase
assert_tag :tag => 'h1', :content => /New page/
end
+ def test_history_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/1/wiki/CookBook_documentation/history'},
+ :controller => 'wiki', :action => 'history', :id => '1', :page => 'CookBook_documentation'
+ )
+ end
+
def test_history
get :history, :id => 1, :page => 'CookBook_documentation'
assert_response :success
@@ -120,6 +160,13 @@ class WikiControllerTest < Test::Unit::TestCase
assert_equal 1, assigns(:versions).size
assert_select "input[type=submit][name=commit]", false
end
+
+ def test_diff_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/1/wiki/CookBook_documentation/diff/2/vs/1'},
+ :controller => 'wiki', :action => 'diff', :id => '1', :page => 'CookBook_documentation', :version => '2', :version_from => '1'
+ )
+ end
def test_diff
get :diff, :id => 1, :page => 'CookBook_documentation', :version => 2, :version_from => 1
@@ -129,6 +176,13 @@ class WikiControllerTest < Test::Unit::TestCase
:content => /updated/
end
+ def test_annotate_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/1/wiki/CookBook_documentation/annotate/2'},
+ :controller => 'wiki', :action => 'annotate', :id => '1', :page => 'CookBook_documentation', :version => '2'
+ )
+ end
+
def test_annotate
get :annotate, :id => 1, :page => 'CookBook_documentation', :version => 2
assert_response :success
@@ -143,12 +197,24 @@ class WikiControllerTest < Test::Unit::TestCase
:child => { :tag => 'td', :content => /Some updated \[\[documentation\]\] here/ }
end
+ def test_rename_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/22/wiki/ladida/rename'},
+ :controller => 'wiki', :action => 'rename', :id => '22', :page => 'ladida'
+ )
+ assert_recognizes(
+ #TODO: should be moved into a update action and use a PUT to the page URI
+ {:controller => 'wiki', :action => 'rename', :id => '22', :page => 'ladida'},
+ {:method => :post, :path => '/projects/22/wiki/ladida/rename'}
+ )
+ end
+
def test_rename_with_redirect
@request.session[:user_id] = 2
post :rename, :id => 1, :page => 'Another_page',
:wiki_page => { :title => 'Another renamed page',
:redirect_existing_links => 1 }
- assert_redirected_to 'wiki/ecookbook/Another_renamed_page'
+ assert_redirected_to :action => 'index', :id => 'ecookbook', :page => 'Another_renamed_page'
wiki = Project.find(1).wiki
# Check redirects
assert_not_nil wiki.find_page('Another page')
@@ -160,16 +226,43 @@ class WikiControllerTest < Test::Unit::TestCase
post :rename, :id => 1, :page => 'Another_page',
:wiki_page => { :title => 'Another renamed page',
:redirect_existing_links => "0" }
- assert_redirected_to 'wiki/ecookbook/Another_renamed_page'
+ assert_redirected_to :action => 'index', :id => 'ecookbook', :page => 'Another_renamed_page'
wiki = Project.find(1).wiki
# Check that there's no redirects
assert_nil wiki.find_page('Another page')
end
+ def test_destroy_routing
+ assert_recognizes(
+ #TODO: should use DELETE on page URI
+ {:controller => 'wiki', :action => 'destroy', :id => '22', :page => 'ladida'},
+ {:method => :post, :path => 'projects/22/wiki/ladida/destroy'}
+ )
+ end
+
def test_destroy
@request.session[:user_id] = 2
post :destroy, :id => 1, :page => 'CookBook_documentation'
- assert_redirected_to 'wiki/ecookbook/Page_index/special'
+ assert_redirected_to :action => 'special', :id => 'ecookbook', :page => 'Page_index'
+ end
+
+ def test_special_routing
+ assert_routing(
+ {:method => :get, :path => '/projects/567/wiki/page_index'},
+ :controller => 'wiki', :action => 'special', :id => '567', :page => 'page_index'
+ )
+ assert_routing(
+ {:method => :get, :path => '/projects/567/wiki/Page_Index'},
+ :controller => 'wiki', :action => 'special', :id => '567', :page => 'Page_Index'
+ )
+ assert_routing(
+ {:method => :get, :path => '/projects/567/wiki/date_index'},
+ :controller => 'wiki', :action => 'special', :id => '567', :page => 'date_index'
+ )
+ assert_routing(
+ {:method => :get, :path => '/projects/567/wiki/export'},
+ :controller => 'wiki', :action => 'special', :id => '567', :page => 'export'
+ )
end
def test_page_index
@@ -181,13 +274,13 @@ class WikiControllerTest < Test::Unit::TestCase
assert_equal Project.find(1).wiki.pages.size, pages.size
assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
- :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/wiki/ecookbook/CookBook_documentation' },
+ :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/CookBook_documentation' },
:content => 'CookBook documentation' },
:child => { :tag => 'ul',
:child => { :tag => 'li',
- :child => { :tag => 'a', :attributes => { :href => '/wiki/ecookbook/Page_with_an_inline_image' },
+ :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
:content => 'Page with an inline image' } } } },
- :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/wiki/ecookbook/Another_page' },
+ :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Another_page' },
:content => 'Another page' } }
end
@@ -196,12 +289,19 @@ class WikiControllerTest < Test::Unit::TestCase
assert_response 404
end
+ def test_protect_routing
+ assert_routing(
+ {:method => :post, :path => 'projects/22/wiki/ladida/protect'},
+ {:controller => 'wiki', :action => 'protect', :id => '22', :page => 'ladida'}
+ )
+ end
+
def test_protect_page
page = WikiPage.find_by_wiki_id_and_title(1, 'Another_page')
assert !page.protected?
@request.session[:user_id] = 2
post :protect, :id => 1, :page => page.title, :protected => '1'
- assert_redirected_to 'wiki/ecookbook/Another_page'
+ assert_redirected_to :action => 'index', :id => 'ecookbook', :page => 'Another_page'
assert page.reload.protected?
end
@@ -210,7 +310,7 @@ class WikiControllerTest < Test::Unit::TestCase
assert page.protected?
@request.session[:user_id] = 2
post :protect, :id => 1, :page => page.title, :protected => '0'
- assert_redirected_to 'wiki/ecookbook'
+ assert_redirected_to :action => 'index', :id => 'ecookbook', :page => 'CookBook_documentation'
assert !page.reload.protected?
end
@@ -219,7 +319,7 @@ class WikiControllerTest < Test::Unit::TestCase
get :index, :id => 1
assert_response :success
assert_template 'show'
- assert_tag :tag => 'a', :attributes => { :href => '/wiki/1/CookBook_documentation/edit' }
+ assert_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
end
def test_show_page_without_edit_link
@@ -227,7 +327,7 @@ class WikiControllerTest < Test::Unit::TestCase
get :index, :id => 1
assert_response :success
assert_template 'show'
- assert_no_tag :tag => 'a', :attributes => { :href => '/wiki/1/CookBook_documentation/edit' }
+ assert_no_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
end
def test_edit_unprotected_page
@@ -251,4 +351,9 @@ class WikiControllerTest < Test::Unit::TestCase
assert_response :success
assert_template 'edit'
end
+
+ def test_history_of_non_existing_page_should_return_404
+ get :history, :id => 1, :page => 'Unknown_page'
+ assert_response 404
+ end
end
diff --git a/test/functional/wikis_controller_test.rb b/test/functional/wikis_controller_test.rb
index 3e51314a5..4000b1128 100644
--- a/test/functional/wikis_controller_test.rb
+++ b/test/functional/wikis_controller_test.rb
@@ -31,6 +31,14 @@ class WikisControllerTest < Test::Unit::TestCase
User.current = nil
end
+ def test_edit_routing
+ assert_routing(
+ #TODO: use PUT
+ {:method => :post, :path => 'projects/ladida/wiki'},
+ :controller => 'wikis', :action => 'edit', :id => 'ladida'
+ )
+ end
+
def test_create
@request.session[:user_id] = 1
assert_nil Project.find(3).wiki
@@ -41,10 +49,21 @@ class WikisControllerTest < Test::Unit::TestCase
assert_equal 'Start page', wiki.start_page
end
+ def test_destroy_routing
+ assert_routing(
+ {:method => :get, :path => 'projects/ladida/wiki/destroy'},
+ :controller => 'wikis', :action => 'destroy', :id => 'ladida'
+ )
+ assert_recognizes( #TODO: use DELETE and update form
+ {:controller => 'wikis', :action => 'destroy', :id => 'ladida'},
+ {:method => :post, :path => 'projects/ladida/wiki/destroy'}
+ )
+ end
+
def test_destroy
@request.session[:user_id] = 1
post :destroy, :id => 1, :confirm => 1
- assert_redirected_to 'projects/settings/ecookbook'
+ assert_redirected_to :action => 'settings', :id => 'ecookbook', :tab => 'wiki'
assert_nil Project.find(1).wiki
end
@@ -53,4 +72,4 @@ class WikisControllerTest < Test::Unit::TestCase
post :destroy, :id => 999, :confirm => 1
assert_response 404
end
-end
+end
diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb
new file mode 100644
index 000000000..d6078bbb9
--- /dev/null
+++ b/test/functional/workflows_controller_test.rb
@@ -0,0 +1,84 @@
+# 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 File.dirname(__FILE__) + '/../test_helper'
+require 'workflows_controller'
+
+# Re-raise errors caught by the controller.
+class WorkflowsController; def rescue_action(e) raise e end; end
+
+class WorkflowsControllerTest < Test::Unit::TestCase
+ fixtures :roles, :trackers, :workflows
+
+ def setup
+ @controller = WorkflowsController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ User.current = nil
+ @request.session[:user_id] = 1 # admin
+ end
+
+ def test_index
+ get :index
+ assert_response :success
+ assert_template 'index'
+
+ count = Workflow.count(:all, :conditions => 'role_id = 1 AND tracker_id = 2')
+ assert_tag :tag => 'a', :content => count.to_s,
+ :attributes => { :href => '/workflows/edit?role_id=1&amp;tracker_id=2' }
+ end
+
+ def test_get_edit
+ get :edit
+ assert_response :success
+ assert_template 'edit'
+ assert_not_nil assigns(:roles)
+ assert_not_nil assigns(:trackers)
+ end
+
+ def test_get_edit_with_role_and_tracker
+ get :edit, :role_id => 2, :tracker_id => 1
+ assert_response :success
+ assert_template 'edit'
+ # allowed transitions
+ assert_tag :tag => 'input', :attributes => { :type => 'checkbox',
+ :name => 'issue_status[2][]',
+ :value => '1',
+ :checked => 'checked' }
+ # not allowed
+ assert_tag :tag => 'input', :attributes => { :type => 'checkbox',
+ :name => 'issue_status[2][]',
+ :value => '3',
+ :checked => nil }
+ end
+
+ def test_post_edit
+ post :edit, :role_id => 2, :tracker_id => 1, :issue_status => {'4' => ['5'], '3' => ['1', '2']}
+ assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1'
+
+ assert_equal 3, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2})
+ assert_not_nil Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2})
+ assert_nil Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4})
+ end
+
+ def test_clear_workflow
+ assert Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2}) > 0
+
+ post :edit, :role_id => 2, :tracker_id => 1
+ assert_equal 0, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2})
+ end
+end
diff --git a/test/integration/account_test.rb b/test/integration/account_test.rb
index c349200d3..c6cfd080e 100644
--- a/test/integration/account_test.rb
+++ b/test/integration/account_test.rb
@@ -44,7 +44,7 @@ class AccountTest < ActionController::IntegrationTest
assert_response :success
assert_template "account/lost_password"
- post "account/lost_password", :mail => 'jsmith@somenet.foo'
+ post "account/lost_password", :mail => 'jSmith@somenet.foo'
assert_redirected_to "account/login"
token = Token.find(:first)
diff --git a/test/integration/admin_test.rb b/test/integration/admin_test.rb
index 6e385873e..5182c9abd 100644
--- a/test/integration/admin_test.rb
+++ b/test/integration/admin_test.rb
@@ -42,10 +42,10 @@ class AdminTest < ActionController::IntegrationTest
def test_add_project
log_user("admin", "admin")
- get "projects/add"
+ get "projects/new"
assert_response :success
assert_template "projects/add"
- post "projects/add", :project => { :name => "blog",
+ post "projects", :project => { :name => "blog",
:description => "weblog",
:identifier => "blog",
:is_public => 1,
diff --git a/test/integration/issues_test.rb b/test/integration/issues_test.rb
index 2ef933fc2..61bbbce34 100644
--- a/test/integration/issues_test.rb
+++ b/test/integration/issues_test.rb
@@ -39,7 +39,7 @@ class IssuesTest < ActionController::IntegrationTest
assert_response :success
assert_template 'issues/new'
- post 'projects/1/issues/new', :tracker_id => "1",
+ post 'projects/1/issues', :tracker_id => "1",
:issue => { :start_date => "2006-12-26",
:priority_id => "3",
:subject => "new test issue",
@@ -54,7 +54,7 @@ class IssuesTest < ActionController::IntegrationTest
assert_kind_of Issue, issue
# check redirection
- assert_redirected_to "issues/show"
+ assert_redirected_to :controller => 'issues', :action => 'show'
follow_redirect!
assert_equal issue, assigns(:issue)
@@ -69,10 +69,10 @@ class IssuesTest < ActionController::IntegrationTest
log_user('jsmith', 'jsmith')
set_tmp_attachments_directory
- post 'issues/edit/1',
+ post 'issues/1/edit',
:notes => 'Some notes',
:attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
- assert_redirected_to "issues/show/1"
+ assert_redirected_to "issues/1"
# make sure attachment was saved
attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
diff --git a/test/integration/projects_test.rb b/test/integration/projects_test.rb
index e56bee484..14175ea98 100644
--- a/test/integration/projects_test.rb
+++ b/test/integration/projects_test.rb
@@ -27,18 +27,18 @@ class ProjectsTest < ActionController::IntegrationTest
assert_response :success
assert_template "admin/projects"
post "projects/archive", :id => 1
- assert_redirected_to "admin/projects"
+ assert_redirected_to "admin/projects"
assert !Project.find(1).active?
- get "projects/show", :id => 1
+ get 'projects/1'
assert_response 403
- get "projects/show", :id => subproject.id
+ get "projects/#{subproject.id}"
assert_response 403
post "projects/unarchive", :id => 1
- assert_redirected_to "admin/projects"
+ assert_redirected_to "admin/projects"
assert Project.find(1).active?
- get "projects/show", :id => 1
+ get "projects/1"
assert_response :success
end
end
diff --git a/test/mocks/open_id_authentication_mock.rb b/test/mocks/open_id_authentication_mock.rb
new file mode 100644
index 000000000..cca1e9edb
--- /dev/null
+++ b/test/mocks/open_id_authentication_mock.rb
@@ -0,0 +1,45 @@
+# Mocks out OpenID
+#
+# http://www.northpub.com/articles/2007/04/02/testing-openid-support
+module OpenIdAuthentication
+
+ EXTENSION_FIELDS = {'email' => 'user@somedomain.com',
+ 'nickname' => 'cool_user',
+ 'country' => 'US',
+ 'postcode' => '12345',
+ 'fullname' => 'Cool User',
+ 'dob' => '1970-04-01',
+ 'language' => 'en',
+ 'timezone' => 'America/New_York'}
+
+ protected
+
+ def authenticate_with_open_id(identity_url = params[:openid_url], options = {}) #:doc:
+ if User.find_by_identity_url(identity_url) || identity_url.include?('good')
+ # Don't process registration fields unless it is requested.
+ unless identity_url.include?('blank') || (options[:required].nil? && options[:optional].nil?)
+ extension_response_fields = {}
+
+ options[:required].each do |field|
+ extension_response_fields[field.to_s] = EXTENSION_FIELDS[field.to_s]
+ end unless options[:required].nil?
+
+ options[:optional].each do |field|
+ extension_response_fields[field.to_s] = EXTENSION_FIELDS[field.to_s]
+ end unless options[:optional].nil?
+ end
+
+ yield Result[:successful], identity_url , extension_response_fields
+ else
+ logger.info "OpenID authentication failed: #{identity_url}"
+ yield Result[:failed], identity_url, nil
+ end
+ end
+
+ private
+
+ def add_simple_registration_fields(open_id_response, fields)
+ open_id_response.add_extension_arg('sreg', 'required', [ fields[:required] ].flatten * ',') if fields[:required]
+ open_id_response.add_extension_arg('sreg', 'optional', [ fields[:optional] ].flatten * ',') if fields[:optional]
+ end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index f61b88d8c..ebc9bae46 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -19,6 +19,7 @@ ENV["RAILS_ENV"] ||= "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'test_help'
require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
+require File.join(RAILS_ROOT,'test', 'mocks', 'open_id_authentication_mock.rb')
class Test::Unit::TestCase
# Transactional fixtures accelerate your tests by wrapping each test method
diff --git a/test/unit/activity_test.rb b/test/unit/activity_test.rb
index ccda9f119..e5bc0d266 100644
--- a/test/unit/activity_test.rb
+++ b/test/unit/activity_test.rb
@@ -63,6 +63,15 @@ class ActivityTest < Test::Unit::TestCase
assert events.include?(Issue.find(4))
end
+ def test_user_activity
+ user = User.find(2)
+ events = Redmine::Activity::Fetcher.new(User.anonymous, :author => user).events(nil, nil, :limit => 10)
+
+ assert(events.size > 0)
+ assert(events.size <= 10)
+ assert_nil(events.detect {|e| e.event_author != user})
+ end
+
private
def find_events(user, options={})
diff --git a/test/unit/attachment_test.rb b/test/unit/attachment_test.rb
index 99f7c29f9..a8c533238 100644
--- a/test/unit/attachment_test.rb
+++ b/test/unit/attachment_test.rb
@@ -18,7 +18,8 @@
require File.dirname(__FILE__) + '/../test_helper'
class AttachmentTest < Test::Unit::TestCase
-
+ fixtures :issues, :users
+
def setup
end
@@ -29,4 +30,8 @@ class AttachmentTest < Test::Unit::TestCase
assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentué")[13..-1]
assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentué.ça")[13..-1]
end
+
+ def test_digest
+ assert_equal '1478adae0d4eb06d35897518540e25d6', Attachment.digest(Test::Unit::TestCase.fixture_path + "/files/testfile.txt")
+ end
end
diff --git a/test/unit/custom_field_test.rb b/test/unit/custom_field_test.rb
index 1b9c9aea9..2f17d99cf 100644
--- a/test/unit/custom_field_test.rb
+++ b/test/unit/custom_field_test.rb
@@ -25,6 +25,24 @@ class CustomFieldTest < Test::Unit::TestCase
assert field.save
end
+ def test_possible_values_should_accept_an_array
+ field = CustomField.new
+ field.possible_values = ["One value", ""]
+ assert_equal ["One value"], field.possible_values
+ end
+
+ def test_possible_values_should_accept_a_string
+ field = CustomField.new
+ field.possible_values = "One value"
+ assert_equal ["One value"], field.possible_values
+ end
+
+ def test_possible_values_should_accept_a_multiline_string
+ field = CustomField.new
+ field.possible_values = "One value\nAnd another one \r\n \n"
+ assert_equal ["One value", "And another one"], field.possible_values
+ end
+
def test_destroy
field = CustomField.find(1)
assert field.destroy
diff --git a/test/unit/document_test.rb b/test/unit/document_test.rb
new file mode 100644
index 000000000..17a0ad6ea
--- /dev/null
+++ b/test/unit/document_test.rb
@@ -0,0 +1,37 @@
+# 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 File.dirname(__FILE__) + '/../test_helper'
+
+class DocumentTest < Test::Unit::TestCase
+ fixtures :projects, :enumerations, :documents
+
+ def test_create
+ doc = Document.new(:project => Project.find(1), :title => 'New document', :category => Enumeration.find_by_name('User documentation'))
+ assert doc.save
+ end
+
+ def test_create_with_default_category
+ # Sets a default category
+ e = Enumeration.find_by_name('Technical documentation')
+ e.update_attributes(:is_default => true)
+
+ doc = Document.new(:project => Project.find(1), :title => 'New document')
+ assert_equal e, doc.category
+ assert doc.save
+ end
+end
diff --git a/test/unit/enumeration_test.rb b/test/unit/enumeration_test.rb
index 9b7bfd174..4da3f094b 100644
--- a/test/unit/enumeration_test.rb
+++ b/test/unit/enumeration_test.rb
@@ -37,6 +37,43 @@ class EnumerationTest < Test::Unit::TestCase
assert !Enumeration.find(7).in_use?
end
+ def test_default
+ e = Enumeration.default('IPRI')
+ assert e.is_a?(Enumeration)
+ assert e.is_default?
+ assert_equal 'Normal', e.name
+ end
+
+ def test_create
+ e = Enumeration.new(:opt => 'IPRI', :name => 'Very urgent', :is_default => false)
+ assert e.save
+ assert_equal 'Normal', Enumeration.default('IPRI').name
+ end
+
+ def test_create_as_default
+ e = Enumeration.new(:opt => 'IPRI', :name => 'Very urgent', :is_default => true)
+ assert e.save
+ assert_equal e, Enumeration.default('IPRI')
+ end
+
+ def test_update_default
+ e = Enumeration.default('IPRI')
+ e.update_attributes(:name => 'Changed', :is_default => true)
+ assert_equal e, Enumeration.default('IPRI')
+ end
+
+ def test_update_default_to_non_default
+ e = Enumeration.default('IPRI')
+ e.update_attributes(:name => 'Changed', :is_default => false)
+ assert_nil Enumeration.default('IPRI')
+ end
+
+ def test_change_default
+ e = Enumeration.find_by_name('Urgent')
+ e.update_attributes(:name => 'Urgent', :is_default => true)
+ assert_equal e, Enumeration.default('IPRI')
+ end
+
def test_destroy_with_reassign
Enumeration.find(4).destroy(Enumeration.find(6))
assert_nil Issue.find(:first, :conditions => {:priority_id => 4})
diff --git a/test/unit/helpers/application_helper_test.rb b/test/unit/helpers/application_helper_test.rb
index 35e26ebd4..c72ab1706 100644
--- a/test/unit/helpers/application_helper_test.rb
+++ b/test/unit/helpers/application_helper_test.rb
@@ -20,11 +20,12 @@ require File.dirname(__FILE__) + '/../../test_helper'
class ApplicationHelperTest < HelperTestCase
include ApplicationHelper
include ActionView::Helpers::TextHelper
- fixtures :projects, :roles, :enabled_modules,
+ fixtures :projects, :roles, :enabled_modules, :users,
:repositories, :changesets,
:trackers, :issue_statuses, :issues, :versions, :documents,
:wikis, :wiki_pages, :wiki_contents,
- :boards, :messages
+ :boards, :messages,
+ :attachments
def setup
super
@@ -35,6 +36,7 @@ class ApplicationHelperTest < HelperTestCase
'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
+ 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
@@ -51,6 +53,8 @@ class ApplicationHelperTest < HelperTestCase
'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
+ 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
+ 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
}
to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
end
@@ -65,16 +69,40 @@ class ApplicationHelperTest < HelperTestCase
'!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
- 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
+ # inline styles should be stripped
+ 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
+ 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
+ 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
}
to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
end
+ def test_acronyms
+ to_test = {
+ 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
+ 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>',
+ }
+ to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
+
+ end
+
+ def test_attached_images
+ to_test = {
+ 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
+ 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
+ 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
+ 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />'
+ }
+ attachments = Attachment.find(:all)
+ to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
+ end
+
def test_textile_external_links
to_test = {
'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
'"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
+ '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
"This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
# no multiline link text
"This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
@@ -148,24 +176,28 @@ class ApplicationHelperTest < HelperTestCase
def test_wiki_links
to_test = {
- '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
- '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
+ '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
+ '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
# link with anchor
- '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
- '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
+ '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
+ '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
# page that doesn't exist
- '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
- '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
+ '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
+ '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
# link to another project wiki
- '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
- '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
- '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
- '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
- '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
+ '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
+ '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
+ '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
+ '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
+ '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
# striked through link
- '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
+ '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
+ '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
# escaping
'![[Another page|Page]]' => '[[Another page|Page]]',
+ # project does not exist
+ '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
+ '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
}
@project = Project.find(1)
to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
@@ -181,7 +213,10 @@ class ApplicationHelperTest < HelperTestCase
"<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
"<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
"HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
- "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
+ "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
+ # remove attributes except class
+ "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
+ "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
}
to_test.each { |text, result| assert_equal result, textilizable(text) }
end
@@ -190,15 +225,31 @@ class ApplicationHelperTest < HelperTestCase
to_test = {
"<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
"<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
+ "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
}
to_test.each { |text, result| assert_equal result, textilizable(text) }
end
+ def syntax_highlight
+ raw = <<-RAW
+<pre><code class="ruby">
+# Some ruby code here
+</pre></code>
+RAW
+
+ expected = <<-EXPECTED
+<pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
+</pre></code>
+EXPECTED
+
+ assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
+ end
+
def test_wiki_links_in_tables
to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
- '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
- '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
- '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
+ '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
+ '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
+ '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
}
@project = Project.find(1)
to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
@@ -206,7 +257,10 @@ class ApplicationHelperTest < HelperTestCase
def test_text_formatting
to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
- '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
+ '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
+ 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
+ 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
+ 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
}
to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
end
@@ -216,6 +270,26 @@ class ApplicationHelperTest < HelperTestCase
assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
end
+ def test_acronym
+ assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
+ textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
+ end
+
+ def test_footnotes
+ raw = <<-RAW
+This is some text[1].
+
+fn1. This is the foot note
+RAW
+
+ expected = <<-EXPECTED
+<p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
+<p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
+EXPECTED
+
+ assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
+ end
+
def test_table_of_content
raw = <<-RAW
{{toc}}
@@ -224,10 +298,12 @@ h1. Title
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
-h2. Subtitle
+h2. Subtitle with a [[Wiki]] link
Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
+h2. Subtitle with [[Wiki|another Wiki]] link
+
h2. Subtitle with %{color:red}red text%
h1. Another title
@@ -236,7 +312,8 @@ RAW
expected = '<ul class="toc">' +
'<li class="heading1"><a href="#Title">Title</a></li>' +
- '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
+ '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
+ '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
'<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
'<li class="heading1"><a href="#Another-title">Another title</a></li>' +
'</ul>'
@@ -306,30 +383,11 @@ EXPECTED
assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
end
- def test_macro_hello_world
- text = "{{hello_world}}"
- assert textilizable(text).match(/Hello world!/)
- # escaping
- text = "!{{hello_world}}"
- assert_equal '<p>{{hello_world}}</p>', textilizable(text)
- end
-
- def test_macro_include
- @project = Project.find(1)
- # include a page of the current project wiki
- text = "{{include(Another page)}}"
- assert textilizable(text).match(/This is a link to a ticket/)
-
- @project = nil
- # include a page of a specific project wiki
- text = "{{include(ecookbook:Another page)}}"
- assert textilizable(text).match(/This is a link to a ticket/)
-
- text = "{{include(ecookbook:)}}"
- assert textilizable(text).match(/CookBook documentation/)
-
- text = "{{include(unknowidentifier:somepage)}}"
- assert textilizable(text).match(/Unknow project/)
+ def test_default_formatter
+ Setting.text_formatting = 'unknown'
+ text = 'a *link*: http://www.example.net/'
+ assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
+ Setting.text_formatting = 'textile'
end
def test_date_format_default
@@ -381,4 +439,17 @@ EXPECTED
assert_equal expected, due_date_distance_in_words(date)
end
end
+
+ def test_avatar
+ # turn on avatars
+ Setting.gravatar_enabled = '1'
+ assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
+ assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
+ assert_nil avatar('jsmith')
+ assert_nil avatar(nil)
+
+ # turn off avatars
+ Setting.gravatar_enabled = '0'
+ assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
+ end
end
diff --git a/test/unit/issue_status_test.rb b/test/unit/issue_status_test.rb
index 404bc36ba..aebe74cb6 100644
--- a/test/unit/issue_status_test.rb
+++ b/test/unit/issue_status_test.rb
@@ -18,7 +18,7 @@
require File.dirname(__FILE__) + '/../test_helper'
class IssueStatusTest < Test::Unit::TestCase
- fixtures :issue_statuses
+ fixtures :issue_statuses, :issues
def test_create
status = IssueStatus.new :name => "Assigned"
@@ -31,6 +31,19 @@ class IssueStatusTest < Test::Unit::TestCase
assert !status.is_default
end
+ def test_destroy
+ count_before = IssueStatus.count
+ status = IssueStatus.find(3)
+ assert status.destroy
+ assert_equal count_before - 1, IssueStatus.count
+ end
+
+ def test_destroy_status_in_use
+ # Status assigned to an Issue
+ status = Issue.find(1).status
+ assert_raise(RuntimeError, "Can't delete status") { status.destroy }
+ end
+
def test_default
status = IssueStatus.default
assert_kind_of IssueStatus, status
@@ -46,4 +59,11 @@ class IssueStatusTest < Test::Unit::TestCase
assert_equal status, IssueStatus.default
assert !IssueStatus.find(1).is_default
end
+
+ def test_reorder_should_not_clear_default_status
+ status = IssueStatus.default
+ status.move_to_bottom
+ status.reload
+ assert status.is_default?
+ end
end
diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb
index 12b4da336..89ec3c6da 100644
--- a/test/unit/issue_test.rb
+++ b/test/unit/issue_test.rb
@@ -33,6 +33,12 @@ class IssueTest < Test::Unit::TestCase
assert_equal 1.5, issue.estimated_hours
end
+ def test_create_minimal
+ issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'test_create')
+ assert issue.save
+ assert issue.description.nil?
+ end
+
def test_create_with_required_custom_field
field = IssueCustomField.find_by_name('Database')
field.update_attribute(:is_required, true)
@@ -185,9 +191,41 @@ class IssueTest < Test::Unit::TestCase
assert_nil issue.category_id
end
+ def test_copy_to_the_same_project
+ issue = Issue.find(1)
+ copy = nil
+ assert_difference 'Issue.count' do
+ copy = issue.move_to(issue.project, nil, :copy => true)
+ end
+ assert_kind_of Issue, copy
+ assert_equal issue.project, copy.project
+ assert_equal "125", copy.custom_value_for(2).value
+ end
+
+ def test_copy_to_another_project_and_tracker
+ issue = Issue.find(1)
+ copy = nil
+ assert_difference 'Issue.count' do
+ copy = issue.move_to(Project.find(3), Tracker.find(2), :copy => true)
+ end
+ assert_kind_of Issue, copy
+ assert_equal Project.find(3), copy.project
+ assert_equal Tracker.find(2), copy.tracker
+ # Custom field #2 is not associated with target tracker
+ assert_nil copy.custom_value_for(2)
+ end
+
def test_issue_destroy
Issue.find(1).destroy
assert_nil Issue.find_by_id(1)
assert_nil TimeEntry.find_by_issue_id(1)
end
+
+ def test_overdue
+ assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
+ assert !Issue.new(:due_date => Date.today).overdue?
+ assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
+ assert !Issue.new(:due_date => nil).overdue?
+ assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
+ end
end
diff --git a/test/unit/lib/redmine/access_control_test.rb b/test/unit/lib/redmine/access_control_test.rb
new file mode 100644
index 000000000..5dd87d28c
--- /dev/null
+++ b/test/unit/lib/redmine/access_control_test.rb
@@ -0,0 +1,49 @@
+# 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 File.dirname(__FILE__) + '/../../../test_helper'
+
+class Redmine::AccessControlTest < Test::Unit::TestCase
+
+ def setup
+ @access_module = Redmine::AccessControl
+ end
+
+ def test_permissions
+ perms = @access_module.permissions
+ assert perms.is_a?(Array)
+ assert perms.first.is_a?(Redmine::AccessControl::Permission)
+ end
+
+ def test_module_permission
+ perm = @access_module.permission(:view_issues)
+ assert perm.is_a?(Redmine::AccessControl::Permission)
+ assert_equal :view_issues, perm.name
+ assert_equal :issue_tracking, perm.project_module
+ assert perm.actions.is_a?(Array)
+ assert perm.actions.include?('issues/index')
+ end
+
+ def test_no_module_permission
+ perm = @access_module.permission(:edit_project)
+ assert perm.is_a?(Redmine::AccessControl::Permission)
+ assert_equal :edit_project, perm.name
+ assert_nil perm.project_module
+ assert perm.actions.is_a?(Array)
+ assert perm.actions.include?('projects/settings')
+ end
+end
diff --git a/test/unit/lib/redmine/hook_test.rb b/test/unit/lib/redmine/hook_test.rb
index 0d76374b6..12c89bf06 100644
--- a/test/unit/lib/redmine/hook_test.rb
+++ b/test/unit/lib/redmine/hook_test.rb
@@ -20,7 +20,7 @@ require File.dirname(__FILE__) + '/../../../test_helper'
class Redmine::Hook::ManagerTest < Test::Unit::TestCase
# Some hooks that are manually registered in these tests
- class TestHook < Redmine::Hook::Listener; end
+ class TestHook < Redmine::Hook::ViewListener; end
class TestHook1 < TestHook
def view_layouts_base_html_head(context)
@@ -39,6 +39,13 @@ class Redmine::Hook::ManagerTest < Test::Unit::TestCase
"Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}."
end
end
+
+ class TestLinkToHook < TestHook
+ def view_layouts_base_html_head(context)
+ link_to('Issues', :controller => 'issues')
+ end
+ end
+
Redmine::Hook.clear_listeners
def setup
@@ -47,6 +54,7 @@ class Redmine::Hook::ManagerTest < Test::Unit::TestCase
def teardown
@hook_module.clear_listeners
+ @hook_module.default_url_options = { }
end
def test_clear_listeners
@@ -67,17 +75,84 @@ class Redmine::Hook::ManagerTest < Test::Unit::TestCase
def test_call_hook
@hook_module.add_listener(TestHook1)
- assert_equal 'Test hook 1 listener.', @hook_module.call_hook(:view_layouts_base_html_head)
+ assert_equal ['Test hook 1 listener.'], @hook_module.call_hook(:view_layouts_base_html_head)
end
def test_call_hook_with_context
@hook_module.add_listener(TestHook3)
- assert_equal 'Context keys: bar, foo.', @hook_module.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a')
+ assert_equal ['Context keys: bar, foo.'], @hook_module.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a')
end
def test_call_hook_with_multiple_listeners
@hook_module.add_listener(TestHook1)
@hook_module.add_listener(TestHook2)
- assert_equal 'Test hook 1 listener.Test hook 2 listener.', @hook_module.call_hook(:view_layouts_base_html_head)
+ assert_equal ['Test hook 1 listener.', 'Test hook 2 listener.'], @hook_module.call_hook(:view_layouts_base_html_head)
+ end
+
+ # Context: Redmine::Hook::call_hook
+ def test_call_hook_default_url_options_set
+ request = ActionController::TestRequest.new
+ request.env = { "SERVER_NAME" => 'example.com'}
+ @hook_module.add_listener(TestLinkToHook)
+
+ assert_equal ['<a href="http://example.com/issues">Issues</a>'],
+ @hook_module.call_hook(:view_layouts_base_html_head, :request => request)
+ end
+
+ def test_call_hook_default_url_options_set_with_no_standard_request_port
+ request = ActionController::TestRequest.new
+ request.env = { "SERVER_NAME" => 'example.com', "SERVER_PORT" => 3000}
+ @hook_module.add_listener(TestLinkToHook)
+
+ assert_equal ['<a href="http://example.com:3000/issues">Issues</a>'],
+ @hook_module.call_hook(:view_layouts_base_html_head, :request => request)
+ end
+
+ def test_call_hook_default_url_options_set_with_ssl
+ request = ActionController::TestRequest.new
+ request.env = { "SERVER_NAME" => 'example.com', "HTTPS" => 'on'}
+ @hook_module.add_listener(TestLinkToHook)
+
+ assert_equal ['<a href="https://example.com/issues">Issues</a>'],
+ @hook_module.call_hook(:view_layouts_base_html_head, :request => request)
+ end
+
+ def test_call_hook_default_url_options_set_with_forwarded_ssl
+ request = ActionController::TestRequest.new
+ request.env = { "SERVER_NAME" => 'example.com', "HTTP_X_FORWARDED_PROTO" => "https"}
+ @hook_module.add_listener(TestLinkToHook)
+
+ assert_equal ['<a href="https://example.com/issues">Issues</a>'],
+ @hook_module.call_hook(:view_layouts_base_html_head, :request => request)
+ end
+
+ # Context: Redmine::Hook::Helper.call_hook
+ def test_call_hook_with_project_added_to_context
+ # TODO: Implement test
+ end
+
+ def test_call_hook_from_controller_with_controller_added_to_context
+ # TODO: Implement test
+ end
+
+ def test_call_hook_from_controller_with_request_added_to_context
+ # TODO: Implement test
+ end
+
+ def test_call_hook_from_view_with_project_added_to_context
+ # TODO: Implement test
+ end
+
+ def test_call_hook_from_view_with_controller_added_to_context
+ # TODO: Implement test
+ end
+
+ def test_call_hook_from_view_with_request_added_to_context
+ # TODO: Implement test
+ end
+
+ def test_call_hook_from_view_should_join_responses_with_a_space
+ # TODO: Implement test
end
end
+
diff --git a/test/unit/lib/redmine/plugin_test.rb b/test/unit/lib/redmine/plugin_test.rb
new file mode 100644
index 000000000..e6237c216
--- /dev/null
+++ b/test/unit/lib/redmine/plugin_test.rb
@@ -0,0 +1,78 @@
+# 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 File.dirname(__FILE__) + '/../../../test_helper'
+
+class Redmine::PluginTest < Test::Unit::TestCase
+
+ def setup
+ @klass = Redmine::Plugin
+ # In case some real plugins are installed
+ @klass.clear
+ end
+
+ def teardown
+ @klass.clear
+ end
+
+ def test_register
+ @klass.register :foo do
+ name 'Foo plugin'
+ url 'http://example.net/plugins/foo'
+ author 'John Smith'
+ author_url 'http://example.net/jsmith'
+ description 'This is a test plugin'
+ version '0.0.1'
+ settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings'
+ end
+
+ assert_equal 1, @klass.all.size
+
+ plugin = @klass.find('foo')
+ assert plugin.is_a?(Redmine::Plugin)
+ assert_equal :foo, plugin.id
+ assert_equal 'Foo plugin', plugin.name
+ assert_equal 'http://example.net/plugins/foo', plugin.url
+ assert_equal 'John Smith', plugin.author
+ assert_equal 'http://example.net/jsmith', plugin.author_url
+ assert_equal 'This is a test plugin', plugin.description
+ assert_equal '0.0.1', plugin.version
+ end
+
+ def test_requires_redmine
+ test = self
+ version = Redmine::VERSION.to_a.slice(0,3).join('.')
+
+ @klass.register :foo do
+ test.assert requires_redmine(:version_or_higher => '0.1.0')
+ test.assert requires_redmine(:version_or_higher => version)
+ test.assert requires_redmine(version)
+ test.assert_raise Redmine::PluginRequirementError do
+ requires_redmine(:version_or_higher => '99.0.0')
+ end
+
+ test.assert requires_redmine(:version => version)
+ test.assert requires_redmine(:version => [version, '99.0.0'])
+ test.assert_raise Redmine::PluginRequirementError do
+ requires_redmine(:version => '99.0.0')
+ end
+ test.assert_raise Redmine::PluginRequirementError do
+ requires_redmine(:version => ['98.0.0', '99.0.0'])
+ end
+ end
+ end
+end
diff --git a/test/unit/lib/redmine/unified_diff_test.rb b/test/unit/lib/redmine/unified_diff_test.rb
new file mode 100644
index 000000000..5e6ba1aef
--- /dev/null
+++ b/test/unit/lib/redmine/unified_diff_test.rb
@@ -0,0 +1,42 @@
+# 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 File.dirname(__FILE__) + '/../../../test_helper'
+
+class Redmine::UnifiedDiffTest < Test::Unit::TestCase
+
+ def setup
+ end
+
+ def test_subversion_diff
+ diff = Redmine::UnifiedDiff.new(read_diff_fixture('subversion.diff'))
+ # number of files
+ assert_equal 4, diff.size
+ assert diff.detect {|file| file.file_name =~ %r{^config/settings.yml}}
+ end
+
+ def test_truncate_diff
+ diff = Redmine::UnifiedDiff.new(read_diff_fixture('subversion.diff'), :max_lines => 20)
+ assert_equal 2, diff.size
+ end
+
+ private
+
+ def read_diff_fixture(filename)
+ File.new(File.join(File.dirname(__FILE__), '/../../../fixtures/diffs', filename)).read
+ end
+end
diff --git a/test/unit/lib/redmine/wiki_formatting/macros_test.rb b/test/unit/lib/redmine/wiki_formatting/macros_test.rb
new file mode 100644
index 000000000..a75289551
--- /dev/null
+++ b/test/unit/lib/redmine/wiki_formatting/macros_test.rb
@@ -0,0 +1,98 @@
+# 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 File.dirname(__FILE__) + '/../../../../test_helper'
+
+class Redmine::WikiFormatting::MacrosTest < HelperTestCase
+ include ApplicationHelper
+ include ActionView::Helpers::TextHelper
+ fixtures :projects, :roles, :enabled_modules, :users,
+ :repositories, :changesets,
+ :trackers, :issue_statuses, :issues,
+ :versions, :documents,
+ :wikis, :wiki_pages, :wiki_contents,
+ :boards, :messages,
+ :attachments
+
+ def setup
+ super
+ @project = nil
+ end
+
+ def teardown
+ end
+
+ def test_macro_hello_world
+ text = "{{hello_world}}"
+ assert textilizable(text).match(/Hello world!/)
+ # escaping
+ text = "!{{hello_world}}"
+ assert_equal '<p>{{hello_world}}</p>', textilizable(text)
+ end
+
+ def test_macro_include
+ @project = Project.find(1)
+ # include a page of the current project wiki
+ text = "{{include(Another page)}}"
+ assert textilizable(text).match(/This is a link to a ticket/)
+
+ @project = nil
+ # include a page of a specific project wiki
+ text = "{{include(ecookbook:Another page)}}"
+ assert textilizable(text).match(/This is a link to a ticket/)
+
+ text = "{{include(ecookbook:)}}"
+ assert textilizable(text).match(/CookBook documentation/)
+
+ text = "{{include(unknowidentifier:somepage)}}"
+ assert textilizable(text).match(/Page not found/)
+ end
+
+ def test_macro_child_pages
+ expected = "<p><ul class=\"pages-hierarchy\">\n" +
+ "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>\n" +
+ "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
+ "</ul>\n</p>"
+
+ @project = Project.find(1)
+ # child pages of the current wiki page
+ assert_equal expected, textilizable("{{child_pages}}", :object => WikiPage.find(2).content)
+ # child pages of another page
+ assert_equal expected, textilizable("{{child_pages(Another_page)}}", :object => WikiPage.find(1).content)
+
+ @project = Project.find(2)
+ assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page)}}", :object => WikiPage.find(1).content)
+ end
+
+ def test_macro_child_pages_with_option
+ expected = "<p><ul class=\"pages-hierarchy\">\n" +
+ "<li><a href=\"/projects/ecookbook/wiki/Another_page\">Another page</a>\n" +
+ "<ul class=\"pages-hierarchy\">\n" +
+ "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>\n" +
+ "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
+ "</ul>\n</li>\n</ul>\n</p>"
+
+ @project = Project.find(1)
+ # child pages of the current wiki page
+ assert_equal expected, textilizable("{{child_pages(parent=1)}}", :object => WikiPage.find(2).content)
+ # child pages of another page
+ assert_equal expected, textilizable("{{child_pages(Another_page, parent=1)}}", :object => WikiPage.find(1).content)
+
+ @project = Project.find(2)
+ assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page, parent=1)}}", :object => WikiPage.find(1).content)
+ end
+end
diff --git a/test/unit/mail_handler_test.rb b/test/unit/mail_handler_test.rb
index b3628e0d5..4ec9684a0 100644
--- a/test/unit/mail_handler_test.rb
+++ b/test/unit/mail_handler_test.rb
@@ -23,10 +23,16 @@ class MailHandlerTest < Test::Unit::TestCase
:roles,
:members,
:issues,
+ :issue_statuses,
+ :workflows,
:trackers,
:projects_trackers,
:enumerations,
- :issue_categories
+ :issue_categories,
+ :custom_fields,
+ :custom_fields_trackers,
+ :boards,
+ :messages
FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
@@ -43,7 +49,11 @@ class MailHandlerTest < Test::Unit::TestCase
assert_equal 'New ticket on a given project', issue.subject
assert_equal User.find_by_login('jsmith'), issue.author
assert_equal Project.find(2), issue.project
+ assert_equal IssueStatus.find_by_name('Resolved'), issue.status
assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
+ # keywords should be removed from the email body
+ assert !issue.description.match(/^Project:/i)
+ assert !issue.description.match(/^Status:/i)
end
def test_add_issue_with_status
@@ -100,6 +110,25 @@ class MailHandlerTest < Test::Unit::TestCase
assert_equal 10790, issue.attachments.first.filesize
end
+ def test_add_issue_with_custom_fields
+ issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
+ assert issue.is_a?(Issue)
+ assert !issue.new_record?
+ issue.reload
+ assert_equal 'New ticket with custom field values', issue.subject
+ assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
+ assert !issue.description.match(/^searchable field:/i)
+ end
+
+ def test_add_issue_with_cc
+ issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
+ assert issue.is_a?(Issue)
+ assert !issue.new_record?
+ issue.reload
+ assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
+ assert_equal 1, issue.watchers.size
+ end
+
def test_add_issue_note
journal = submit_email('ticket_reply.eml')
assert journal.is_a?(Journal)
@@ -118,6 +147,34 @@ class MailHandlerTest < Test::Unit::TestCase
assert_match /This is reply/, journal.notes
assert_equal IssueStatus.find_by_name("Resolved"), issue.status
end
+
+ def test_reply_to_a_message
+ m = submit_email('message_reply.eml')
+ assert m.is_a?(Message)
+ assert !m.new_record?
+ m.reload
+ assert_equal 'Reply via email', m.subject
+ # The email replies to message #2 which is part of the thread of message #1
+ assert_equal Message.find(1), m.parent
+ end
+
+ def test_reply_to_a_message_by_subject
+ m = submit_email('message_reply_by_subject.eml')
+ assert m.is_a?(Message)
+ assert !m.new_record?
+ m.reload
+ assert_equal 'Reply to the first post', m.subject
+ assert_equal Message.find(1), m.parent
+ end
+
+ def test_should_strip_tags_of_html_only_emails
+ issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
+ assert issue.is_a?(Issue)
+ assert !issue.new_record?
+ issue.reload
+ assert_equal 'HTML email', issue.subject
+ assert_equal 'This is a html-only email.', issue.description
+ end
private
diff --git a/test/unit/mailer_test.rb b/test/unit/mailer_test.rb
index 402624f5f..141ff40b9 100644
--- a/test/unit/mailer_test.rb
+++ b/test/unit/mailer_test.rb
@@ -18,7 +18,7 @@
require File.dirname(__FILE__) + '/../test_helper'
class MailerTest < Test::Unit::TestCase
- fixtures :projects, :issues, :users, :members, :documents, :attachments, :news, :tokens, :journals, :journal_details, :changesets, :trackers, :issue_statuses, :enumerations
+ fixtures :projects, :issues, :users, :members, :documents, :attachments, :news, :tokens, :journals, :journal_details, :changesets, :trackers, :issue_statuses, :enumerations, :messages, :boards, :repositories
def test_generated_links_in_emails
ActionMailer::Base.deliveries.clear
@@ -31,12 +31,108 @@ class MailerTest < Test::Unit::TestCase
mail = ActionMailer::Base.deliveries.last
assert_kind_of TMail::Mail, mail
# link to the main ticket
- assert mail.body.include?('<a href="https://mydomain.foo/issues/show/1">Bug #1: Can\'t print recipes</a>')
+ assert mail.body.include?('<a href="https://mydomain.foo/issues/1">Bug #1: Can\'t print recipes</a>')
# link to a referenced ticket
- assert mail.body.include?('<a href="https://mydomain.foo/issues/show/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
+ assert mail.body.include?('<a href="https://mydomain.foo/issues/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
# link to a changeset
- assert mail.body.include?('<a href="https://mydomain.foo/repositories/revision/ecookbook/2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
+ assert mail.body.include?('<a href="https://mydomain.foo/projects/ecookbook/repository/revisions/2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
+ end
+
+ def test_generated_links_with_prefix
+ relative_url_root = Redmine::Utils.relative_url_root
+ ActionMailer::Base.deliveries.clear
+ Setting.host_name = 'mydomain.foo/rdm'
+ Setting.protocol = 'http'
+ Redmine::Utils.relative_url_root = '/rdm'
+
+ journal = Journal.find(2)
+ assert Mailer.deliver_issue_edit(journal)
+
+ mail = ActionMailer::Base.deliveries.last
+ assert_kind_of TMail::Mail, mail
+ # link to the main ticket
+ assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/1">Bug #1: Can\'t print recipes</a>')
+
+ # link to a referenced ticket
+ assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
+ # link to a changeset
+ assert mail.body.include?('<a href="http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
+ ensure
+ # restore it
+ Redmine::Utils.relative_url_root = relative_url_root
+ end
+
+ def test_generated_links_with_prefix_and_no_relative_url_root
+ relative_url_root = Redmine::Utils.relative_url_root
+ ActionMailer::Base.deliveries.clear
+ Setting.host_name = 'mydomain.foo/rdm'
+ Setting.protocol = 'http'
+ Redmine::Utils.relative_url_root = nil
+
+ journal = Journal.find(2)
+ assert Mailer.deliver_issue_edit(journal)
+
+ mail = ActionMailer::Base.deliveries.last
+ assert_kind_of TMail::Mail, mail
+ # link to the main ticket
+ assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/1">Bug #1: Can\'t print recipes</a>')
+
+ # link to a referenced ticket
+ assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
+ # link to a changeset
+ assert mail.body.include?('<a href="http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
+ ensure
+ # restore it
+ Redmine::Utils.relative_url_root = relative_url_root
+ end
+
+ def test_plain_text_mail
+ Setting.plain_text_mail = 1
+ journal = Journal.find(2)
+ Mailer.deliver_issue_edit(journal)
+ mail = ActionMailer::Base.deliveries.last
+ assert !mail.body.include?('<a href="https://mydomain.foo/issues/1">Bug #1: Can\'t print recipes</a>')
+ end
+
+ def test_issue_add_message_id
+ ActionMailer::Base.deliveries.clear
+ issue = Issue.find(1)
+ Mailer.deliver_issue_add(issue)
+ mail = ActionMailer::Base.deliveries.last
+ assert_not_nil mail
+ assert_equal Mailer.message_id_for(issue), mail.message_id
+ assert_nil mail.references
+ end
+
+ def test_issue_edit_message_id
+ ActionMailer::Base.deliveries.clear
+ journal = Journal.find(1)
+ Mailer.deliver_issue_edit(journal)
+ mail = ActionMailer::Base.deliveries.last
+ assert_not_nil mail
+ assert_equal Mailer.message_id_for(journal), mail.message_id
+ assert_equal Mailer.message_id_for(journal.issue), mail.references.to_s
+ end
+
+ def test_message_posted_message_id
+ ActionMailer::Base.deliveries.clear
+ message = Message.find(1)
+ Mailer.deliver_message_posted(message, message.author.mail)
+ mail = ActionMailer::Base.deliveries.last
+ assert_not_nil mail
+ assert_equal Mailer.message_id_for(message), mail.message_id
+ assert_nil mail.references
+ end
+
+ def test_reply_posted_message_id
+ ActionMailer::Base.deliveries.clear
+ message = Message.find(3)
+ Mailer.deliver_message_posted(message, message.author.mail)
+ mail = ActionMailer::Base.deliveries.last
+ assert_not_nil mail
+ assert_equal Mailer.message_id_for(message), mail.message_id
+ assert_equal Mailer.message_id_for(message.parent), mail.references.to_s
end
# test mailer methods for each language
diff --git a/test/unit/message_test.rb b/test/unit/message_test.rb
index 6e8e8fb26..b907cfef3 100644
--- a/test/unit/message_test.rb
+++ b/test/unit/message_test.rb
@@ -1,7 +1,7 @@
require File.dirname(__FILE__) + '/../test_helper'
class MessageTest < Test::Unit::TestCase
- fixtures :projects, :boards, :messages, :users, :watchers
+ fixtures :projects, :roles, :members, :boards, :messages, :users, :watchers
def setup
@board = Board.find(1)
@@ -76,4 +76,22 @@ class MessageTest < Test::Unit::TestCase
assert_equal topics_count, board.topics_count
assert_equal messages_count - 1, board.messages_count
end
+
+ def test_editable_by
+ message = Message.find(6)
+ author = message.author
+ assert message.editable_by?(author)
+
+ author.role_for_project(message.project).remove_permission!(:edit_own_messages)
+ assert !message.reload.editable_by?(author.reload)
+ end
+
+ def test_destroyable_by
+ message = Message.find(6)
+ author = message.author
+ assert message.destroyable_by?(author)
+
+ author.role_for_project(message.project).remove_permission!(:delete_own_messages)
+ assert !message.reload.destroyable_by?(author.reload)
+ end
end
diff --git a/test/unit/news_test.rb b/test/unit/news_test.rb
new file mode 100644
index 000000000..527715b00
--- /dev/null
+++ b/test/unit/news_test.rb
@@ -0,0 +1,63 @@
+# 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 File.dirname(__FILE__) + '/../test_helper'
+
+class NewsTest < Test::Unit::TestCase
+ fixtures :projects, :users, :roles, :members, :enabled_modules, :news
+
+ def setup
+ end
+
+ def test_should_include_news_for_projects_with_news_enabled
+ project = projects(:projects_001)
+ assert project.enabled_modules.any?{ |em| em.name == 'news' }
+
+ # News.latest should return news from projects_001
+ assert News.latest.any? { |news| news.project == project }
+ end
+
+ def test_should_not_include_news_for_projects_with_news_disabled
+ # The projects_002 (OnlineStore) doesn't have the news module enabled, use that project for this test
+ project = projects(:projects_002)
+ assert ! project.enabled_modules.any?{ |em| em.name == 'news' }
+
+ # Add a piece of news to the project
+ news = project.news.create(:title => 'Test news', :description => 'This should not be returned by News.latest')
+
+ # News.latest should not return that new piece of news
+ assert News.latest.include?(news) == false
+ end
+
+ def test_should_only_include_news_from_projects_visibly_to_the_user
+ # users_001 has no memberships so can only get news from public project
+ assert News.latest(users(:users_001)).all? { |news| news.project.is_public? }
+ end
+
+ def test_should_limit_the_amount_of_returned_news
+ # Make sure we have a bunch of news stories
+ 10.times { projects(:projects_001).news.create(:title => 'Test news', :description => 'Lorem ipsum etc') }
+ assert_equal 2, News.latest(users(:users_002), 2).size
+ assert_equal 6, News.latest(users(:users_002), 6).size
+ end
+
+ def test_should_return_5_news_stories_by_default
+ # Make sure we have a bunch of news stories
+ 10.times { projects(:projects_001).news.create(:title => 'Test news', :description => 'Lorem ipsum etc') }
+ assert_equal 5, News.latest(users(:users_004)).size
+ end
+end
diff --git a/test/unit/project_test.rb b/test/unit/project_test.rb
index 6e32c02e7..94177f433 100644
--- a/test/unit/project_test.rb
+++ b/test/unit/project_test.rb
@@ -45,12 +45,6 @@ class ProjectTest < Test::Unit::TestCase
assert_equal "activerecord_error_blank", @ecookbook.errors.on(:name)
end
- def test_public_projects
- public_projects = Project.find(:all, :conditions => ["is_public=?", true])
- assert_equal 3, public_projects.length
- assert_equal true, public_projects[0].is_public?
- end
-
def test_archive
user = @ecookbook.members.first.user
@ecookbook.archive
@@ -60,7 +54,7 @@ class ProjectTest < Test::Unit::TestCase
assert !user.projects.include?(@ecookbook)
# Subproject are also archived
assert !@ecookbook.children.empty?
- assert @ecookbook.active_children.empty?
+ assert @ecookbook.descendants.active.empty?
end
def test_unarchive
@@ -95,25 +89,98 @@ class ProjectTest < Test::Unit::TestCase
assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
end
- def test_subproject_ok
+ def test_move_an_orphan_project_to_a_root_project
sub = Project.find(2)
- sub.parent = @ecookbook
- assert sub.save
+ sub.set_parent! @ecookbook
assert_equal @ecookbook.id, sub.parent.id
@ecookbook.reload
assert_equal 4, @ecookbook.children.size
end
- def test_subproject_invalid
+ def test_move_an_orphan_project_to_a_subproject
sub = Project.find(2)
- sub.parent = @ecookbook_sub1
- assert !sub.save
+ assert sub.set_parent!(@ecookbook_sub1)
+ end
+
+ def test_move_a_root_project_to_a_project
+ sub = @ecookbook
+ assert sub.set_parent!(Project.find(2))
end
- def test_subproject_invalid_2
+ def test_should_not_move_a_project_to_its_children
sub = @ecookbook
- sub.parent = Project.find(2)
- assert !sub.save
+ assert !(sub.set_parent!(Project.find(3)))
+ end
+
+ def test_set_parent_should_add_roots_in_alphabetical_order
+ ProjectCustomField.delete_all
+ Project.delete_all
+ Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
+ Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
+ Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
+ Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
+
+ assert_equal 4, Project.count
+ assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
+ end
+
+ def test_set_parent_should_add_children_in_alphabetical_order
+ ProjectCustomField.delete_all
+ parent = Project.create!(:name => 'Parent', :identifier => 'parent')
+ Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
+ Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
+ Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
+ Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
+
+ parent.reload
+ assert_equal 4, parent.children.size
+ assert_equal parent.children.sort_by(&:name), parent.children
+ end
+
+ def test_rebuild_should_sort_children_alphabetically
+ ProjectCustomField.delete_all
+ parent = Project.create!(:name => 'Parent', :identifier => 'parent')
+ Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
+ Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
+ Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
+ Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
+
+ Project.update_all("lft = NULL, rgt = NULL")
+ Project.rebuild!
+
+ parent.reload
+ assert_equal 4, parent.children.size
+ assert_equal parent.children.sort_by(&:name), parent.children
+ end
+
+ def test_parent
+ p = Project.find(6).parent
+ assert p.is_a?(Project)
+ assert_equal 5, p.id
+ end
+
+ def test_ancestors
+ a = Project.find(6).ancestors
+ assert a.first.is_a?(Project)
+ assert_equal [1, 5], a.collect(&:id)
+ end
+
+ def test_root
+ r = Project.find(6).root
+ assert r.is_a?(Project)
+ assert_equal 1, r.id
+ end
+
+ def test_children
+ c = Project.find(1).children
+ assert c.first.is_a?(Project)
+ assert_equal [5, 3, 4], c.collect(&:id)
+ end
+
+ def test_descendants
+ d = Project.find(1).descendants
+ assert d.first.is_a?(Project)
+ assert_equal [5, 6, 3, 4], d.collect(&:id)
end
def test_rolled_up_trackers
@@ -131,6 +198,16 @@ class ProjectTest < Test::Unit::TestCase
assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
end
+ def test_rolled_up_trackers_should_ignore_archived_subprojects
+ parent = Project.find(1)
+ parent.trackers = Tracker.find([1,2])
+ child = parent.children.find(3)
+ child.trackers = Tracker.find([1,3])
+ parent.children.each(&:archive)
+
+ assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
+ end
+
def test_next_identifier
ProjectCustomField.delete_all
Project.create!(:name => 'last', :identifier => 'p2008040')
diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb
index c243dfbad..d568604c3 100644
--- a/test/unit/query_test.rb
+++ b/test/unit/query_test.rb
@@ -18,7 +18,7 @@
require File.dirname(__FILE__) + '/../test_helper'
class QueryTest < Test::Unit::TestCase
- fixtures :projects, :users, :members, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :queries
+ fixtures :projects, :enabled_modules, :users, :members, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :watchers, :custom_fields, :custom_values, :versions, :queries
def test_custom_fields_for_all_projects_should_be_available_in_global_queries
query = Query.new(:project => nil, :name => '_')
@@ -75,37 +75,76 @@ class QueryTest < Test::Unit::TestCase
end
def test_operator_in_more_than
+ Issue.find(7).update_attribute(:due_date, (Date.today + 15))
query = Query.new(:project => Project.find(1), :name => '_')
query.add_filter('due_date', '>t+', ['15'])
- assert query.statement.include?("#{Issue.table_name}.due_date >=")
- find_issues_with_query(query)
+ issues = find_issues_with_query(query)
+ assert !issues.empty?
+ issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
end
def test_operator_in_less_than
query = Query.new(:project => Project.find(1), :name => '_')
query.add_filter('due_date', '<t+', ['15'])
- assert query.statement.include?("#{Issue.table_name}.due_date BETWEEN")
- find_issues_with_query(query)
+ issues = find_issues_with_query(query)
+ assert !issues.empty?
+ issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
+ end
+
+ def test_operator_less_than_ago
+ Issue.find(7).update_attribute(:due_date, (Date.today - 3))
+ query = Query.new(:project => Project.find(1), :name => '_')
+ query.add_filter('due_date', '>t-', ['3'])
+ issues = find_issues_with_query(query)
+ assert !issues.empty?
+ issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
+ end
+
+ def test_operator_more_than_ago
+ Issue.find(7).update_attribute(:due_date, (Date.today - 10))
+ query = Query.new(:project => Project.find(1), :name => '_')
+ query.add_filter('due_date', '<t-', ['10'])
+ assert query.statement.include?("#{Issue.table_name}.due_date <=")
+ issues = find_issues_with_query(query)
+ assert !issues.empty?
+ issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
+ end
+
+ def test_operator_in
+ Issue.find(7).update_attribute(:due_date, (Date.today + 2))
+ query = Query.new(:project => Project.find(1), :name => '_')
+ query.add_filter('due_date', 't+', ['2'])
+ issues = find_issues_with_query(query)
+ assert !issues.empty?
+ issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
+ end
+
+ def test_operator_ago
+ Issue.find(7).update_attribute(:due_date, (Date.today - 3))
+ query = Query.new(:project => Project.find(1), :name => '_')
+ query.add_filter('due_date', 't-', ['3'])
+ issues = find_issues_with_query(query)
+ assert !issues.empty?
+ issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
end
def test_operator_today
query = Query.new(:project => Project.find(1), :name => '_')
query.add_filter('due_date', 't', [''])
- assert query.statement.include?("#{Issue.table_name}.due_date BETWEEN")
- find_issues_with_query(query)
+ issues = find_issues_with_query(query)
+ assert !issues.empty?
+ issues.each {|issue| assert_equal Date.today, issue.due_date}
end
def test_operator_this_week_on_date
query = Query.new(:project => Project.find(1), :name => '_')
query.add_filter('due_date', 'w', [''])
- assert query.statement.include?("#{Issue.table_name}.due_date BETWEEN")
find_issues_with_query(query)
end
def test_operator_this_week_on_datetime
query = Query.new(:project => Project.find(1), :name => '_')
query.add_filter('created_on', 'w', [''])
- assert query.statement.include?("#{Issue.table_name}.created_on BETWEEN")
find_issues_with_query(query)
end
@@ -123,6 +162,26 @@ class QueryTest < Test::Unit::TestCase
find_issues_with_query(query)
end
+ def test_filter_watched_issues
+ User.current = User.find(1)
+ query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
+ result = find_issues_with_query(query)
+ assert_not_nil result
+ assert !result.empty?
+ assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
+ User.current = nil
+ end
+
+ def test_filter_unwatched_issues
+ User.current = User.find(1)
+ query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
+ result = find_issues_with_query(query)
+ assert_not_nil result
+ assert !result.empty?
+ assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
+ User.current = nil
+ end
+
def test_default_columns
q = Query.new
assert !q.columns.empty?
@@ -136,6 +195,48 @@ class QueryTest < Test::Unit::TestCase
assert q.has_column?(c)
end
+ def test_sort_by_string_custom_field_asc
+ q = Query.new
+ c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
+ assert c
+ assert c.sortable
+ issues = Issue.find :all,
+ :include => [ :assigned_to, :status, :tracker, :project, :priority ],
+ :conditions => q.statement,
+ :order => "#{c.sortable} ASC"
+ values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
+ assert !values.empty?
+ assert_equal values.sort, values
+ end
+
+ def test_sort_by_string_custom_field_desc
+ q = Query.new
+ c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
+ assert c
+ assert c.sortable
+ issues = Issue.find :all,
+ :include => [ :assigned_to, :status, :tracker, :project, :priority ],
+ :conditions => q.statement,
+ :order => "#{c.sortable} DESC"
+ values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
+ assert !values.empty?
+ assert_equal values.sort.reverse, values
+ end
+
+ def test_sort_by_float_custom_field_asc
+ q = Query.new
+ c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
+ assert c
+ assert c.sortable
+ issues = Issue.find :all,
+ :include => [ :assigned_to, :status, :tracker, :project, :priority ],
+ :conditions => q.statement,
+ :order => "#{c.sortable} ASC"
+ values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
+ assert !values.empty?
+ assert_equal values.sort, values
+ end
+
def test_label_for
q = Query.new
assert_equal 'assigned_to', q.label_for('assigned_to_id')
diff --git a/test/unit/repository_cvs_test.rb b/test/unit/repository_cvs_test.rb
index 6615f73bf..47c407037 100644
--- a/test/unit/repository_cvs_test.rb
+++ b/test/unit/repository_cvs_test.rb
@@ -53,6 +53,12 @@ class RepositoryCvsTest < Test::Unit::TestCase
@repository.fetch_changesets
assert_equal 5, @repository.changesets.count
end
+
+ def test_deleted_files_should_not_be_listed
+ entries = @repository.entries('sources')
+ assert entries.detect {|e| e.name == 'watchers_controller.rb'}
+ assert_nil entries.detect {|e| e.name == 'welcome_controller.rb'}
+ end
else
puts "CVS test repository NOT FOUND. Skipping unit tests !!!"
def test_fake; assert true end
diff --git a/test/unit/repository_darcs_test.rb b/test/unit/repository_darcs_test.rb
index ca8c267f2..0c8c9a143 100644
--- a/test/unit/repository_darcs_test.rb
+++ b/test/unit/repository_darcs_test.rb
@@ -49,6 +49,12 @@ class RepositoryDarcsTest < Test::Unit::TestCase
assert_equal 6, @repository.changesets.count
end
+ def test_deleted_files_should_not_be_listed
+ entries = @repository.entries('sources')
+ assert entries.detect {|e| e.name == 'watchers_controller.rb'}
+ assert_nil entries.detect {|e| e.name == 'welcome_controller.rb'}
+ end
+
def test_cat
@repository.fetch_changesets
cat = @repository.cat("sources/welcome_controller.rb", 2)
diff --git a/test/unit/repository_git_test.rb b/test/unit/repository_git_test.rb
index 8a6f1ddd0..bc997b96c 100644
--- a/test/unit/repository_git_test.rb
+++ b/test/unit/repository_git_test.rb
@@ -36,13 +36,26 @@ class RepositoryGitTest < Test::Unit::TestCase
assert_equal 6, @repository.changesets.count
assert_equal 11, @repository.changes.count
- assert_equal "Initial import.\nThe repository contains 3 files.", @repository.changesets.find(:first, :order => 'id ASC').comments
+
+ commit = @repository.changesets.find(:first, :order => 'committed_on ASC')
+ assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments
+ assert_equal "jsmith <jsmith@foo.bar>", commit.committer
+ assert_equal User.find_by_login('jsmith'), commit.user
+ # TODO: add a commit with commit time <> author time to the test repository
+ assert_equal "2007-12-14 09:22:52".to_time, commit.committed_on
+ assert_equal "2007-12-14".to_date, commit.commit_date
+ assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.revision
+ assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.scmid
+ assert_equal 3, commit.changes.count
+ change = commit.changes.sort_by(&:path).first
+ assert_equal "README", change.path
+ assert_equal "A", change.action
end
def test_fetch_changesets_incremental
@repository.fetch_changesets
# Remove the 3 latest changesets
- @repository.changesets.find(:all, :order => 'id DESC', :limit => 3).each(&:destroy)
+ @repository.changesets.find(:all, :order => 'committed_on DESC', :limit => 3).each(&:destroy)
@repository.reload
assert_equal 3, @repository.changesets.count
diff --git a/test/unit/repository_subversion_test.rb b/test/unit/repository_subversion_test.rb
index 7a1c9df4a..4054a0e41 100644
--- a/test/unit/repository_subversion_test.rb
+++ b/test/unit/repository_subversion_test.rb
@@ -48,6 +48,13 @@ class RepositorySubversionTest < Test::Unit::TestCase
@repository.fetch_changesets
assert_equal 8, @repository.changesets.count
end
+
+ def test_changesets_for_path_with_limit
+ @repository.fetch_changesets
+ changesets = @repository.changesets_for_path('', :limit => 2)
+ assert_equal 2, changesets.size
+ assert_equal @repository.changesets_for_path('').slice(0,2), changesets
+ end
else
puts "Subversion test repository NOT FOUND. Skipping unit tests !!!"
def test_fake; assert true end
diff --git a/test/unit/repository_test.rb b/test/unit/repository_test.rb
index 0973c7fdb..6d4073c79 100644
--- a/test/unit/repository_test.rb
+++ b/test/unit/repository_test.rb
@@ -66,6 +66,8 @@ class RepositoryTest < Test::Unit::TestCase
end
def test_scan_changesets_for_issue_ids
+ Setting.default_language = 'en'
+
# choosing a status to apply to fix issues
Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
Setting.commit_fix_done_ratio = "90"
@@ -127,26 +129,26 @@ class RepositoryTest < Test::Unit::TestCase
assert_equal ':pserver:login:password@host:/path/to/the/repository', repository.url
assert_equal 'foo', repository.root_url
end
-
- def test_local_cache
- dir = Setting.repositories_cache_directory.gsub(/^([^#{File::SEPARATOR}].*)/, RAILS_ROOT + '/\1')
-
- project = projects(:projects_001)
-
- repository = Repository::Git.new(:project => Project.find_by_name(project.name), :url => "git://github.com/olabini/paipr.git")
- repository.scm
- assert_equal(dir + project.identifier, repository.cache_path)
-
- repository = Repository::Git.new(:project => Project.find(:first), :url => "/var/cache/git/paipr/.git")
- repository.init_cache
- assert repository.cache_path.blank?
-
- repository = Repository::Subversion.new(:project => Project.find(:first), :url => "svn://github.com/olabini/paipr.git")
- repository.init_cache
- assert repository.cache_path.blank?
-
- repository = Repository::Subversion.new(:project => Project.find_by_name(project.name), :url => "svn://github.com/olabini/paipr.git", :cache => true)
- repository.init_cache
- assert_equal(dir + project.identifier, repository.cache_path)
+
+ def test_manual_user_mapping
+ assert_no_difference "Changeset.count(:conditions => 'user_id <> 2')" do
+ c = Changeset.create!(:repository => @repository, :committer => 'foo', :committed_on => Time.now, :revision => 100, :comments => 'Committed by foo.')
+ assert_nil c.user
+ @repository.committer_ids = {'foo' => '2'}
+ assert_equal User.find(2), c.reload.user
+ # committer is now mapped
+ c = Changeset.create!(:repository => @repository, :committer => 'foo', :committed_on => Time.now, :revision => 101, :comments => 'Another commit by foo.')
+ assert_equal User.find(2), c.user
+ end
+ end
+
+ def test_auto_user_mapping_by_username
+ c = Changeset.create!(:repository => @repository, :committer => 'jsmith', :committed_on => Time.now, :revision => 100, :comments => 'Committed by john.')
+ assert_equal User.find(2), c.user
+ end
+
+ def test_auto_user_mapping_by_email
+ c = Changeset.create!(:repository => @repository, :committer => 'john <jsmith@somenet.foo>', :committed_on => Time.now, :revision => 100, :comments => 'Committed by john.')
+ assert_equal User.find(2), c.user
end
end
diff --git a/test/unit/time_entry_test.rb b/test/unit/time_entry_test.rb
index f86e42eab..dd54fd1b2 100644
--- a/test/unit/time_entry_test.rb
+++ b/test/unit/time_entry_test.rb
@@ -24,6 +24,7 @@ class TimeEntryTest < Test::Unit::TestCase
assertions = { "2" => 2.0,
"21.1" => 21.1,
"2,1" => 2.1,
+ "1,5h" => 1.5,
"7:12" => 7.2,
"10h" => 10.0,
"10 h" => 10.0,
@@ -40,7 +41,7 @@ class TimeEntryTest < Test::Unit::TestCase
assertions.each do |k, v|
t = TimeEntry.new(:hours => k)
- assert_equal v, t.hours
+ assert_equal v, t.hours, "Converting #{k} failed:"
end
end
end
diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb
index 80011f4bf..6bf0e41fd 100644
--- a/test/unit/user_test.rb
+++ b/test/unit/user_test.rb
@@ -1,34 +1,34 @@
-# redMine - project management software
-# Copyright (C) 2006 Jean-Philippe Lang
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-require File.dirname(__FILE__) + '/../test_helper'
-
-class UserTest < Test::Unit::TestCase
- fixtures :users, :members, :projects
-
- def setup
- @admin = User.find(1)
- @jsmith = User.find(2)
- @dlopper = User.find(3)
- end
-
- def test_truth
- assert_kind_of User, @jsmith
- end
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require File.dirname(__FILE__) + '/../test_helper'
+
+class UserTest < Test::Unit::TestCase
+ fixtures :users, :members, :projects
+
+ def setup
+ @admin = User.find(1)
+ @jsmith = User.find(2)
+ @dlopper = User.find(3)
+ end
+
+ def test_truth
+ assert_kind_of User, @jsmith
+ end
def test_create
user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
@@ -48,114 +48,165 @@ class UserTest < Test::Unit::TestCase
user.password, user.password_confirmation = "password", "password"
assert user.save
end
-
- def test_update
- assert_equal "admin", @admin.login
- @admin.login = "john"
- assert @admin.save, @admin.errors.full_messages.join("; ")
- @admin.reload
- assert_equal "john", @admin.login
- end
-
- def test_destroy
- User.find(2).destroy
- assert_nil User.find_by_id(2)
- assert Member.find_all_by_user_id(2).empty?
- end
-
- def test_validate
- @admin.login = ""
- assert !@admin.save
- assert_equal 1, @admin.errors.count
- end
-
- def test_password
- user = User.try_to_login("admin", "admin")
- assert_kind_of User, user
- assert_equal "admin", user.login
- user.password = "hello"
- assert user.save
-
- user = User.try_to_login("admin", "hello")
- assert_kind_of User, user
- assert_equal "admin", user.login
- assert_equal User.hash_password("hello"), user.hashed_password
- end
-
- def test_name_format
- assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
- Setting.user_format = :firstname_lastname
- assert_equal 'John Smith', @jsmith.name
- Setting.user_format = :username
- assert_equal 'jsmith', @jsmith.name
- end
-
- def test_lock
- user = User.try_to_login("jsmith", "jsmith")
- assert_equal @jsmith, user
-
- @jsmith.status = User::STATUS_LOCKED
- assert @jsmith.save
-
- user = User.try_to_login("jsmith", "jsmith")
- assert_equal nil, user
- end
-
- def test_create_anonymous
- AnonymousUser.delete_all
- anon = User.anonymous
- assert !anon.new_record?
- assert_kind_of AnonymousUser, anon
- end
-
- def test_rss_key
- assert_nil @jsmith.rss_token
- key = @jsmith.rss_key
- assert_equal 40, key.length
-
- @jsmith.reload
- assert_equal key, @jsmith.rss_key
- end
-
- def test_role_for_project
- # user with a role
- role = @jsmith.role_for_project(Project.find(1))
- assert_kind_of Role, role
- assert_equal "Manager", role.name
-
- # user with no role
- assert !@dlopper.role_for_project(Project.find(2)).member?
- end
-
- def test_mail_notification_all
- @jsmith.mail_notification = true
- @jsmith.notified_project_ids = []
- @jsmith.save
- @jsmith.reload
- assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
- end
-
- def test_mail_notification_selected
- @jsmith.mail_notification = false
- @jsmith.notified_project_ids = [1]
- @jsmith.save
- @jsmith.reload
- assert Project.find(1).recipients.include?(@jsmith.mail)
- end
-
- def test_mail_notification_none
- @jsmith.mail_notification = false
- @jsmith.notified_project_ids = []
- @jsmith.save
- @jsmith.reload
- assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
- end
-
- def test_comments_sorting_preference
- assert !@jsmith.wants_comments_in_reverse_order?
- @jsmith.pref.comments_sorting = 'asc'
- assert !@jsmith.wants_comments_in_reverse_order?
- @jsmith.pref.comments_sorting = 'desc'
- assert @jsmith.wants_comments_in_reverse_order?
- end
-end
+
+ def test_mail_uniqueness_should_not_be_case_sensitive
+ u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
+ u.login = 'newuser1'
+ u.password, u.password_confirmation = "password", "password"
+ assert u.save
+
+ u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
+ u.login = 'newuser2'
+ u.password, u.password_confirmation = "password", "password"
+ assert !u.save
+ assert_equal 'activerecord_error_taken', u.errors.on(:mail)
+ end
+
+ def test_update
+ assert_equal "admin", @admin.login
+ @admin.login = "john"
+ assert @admin.save, @admin.errors.full_messages.join("; ")
+ @admin.reload
+ assert_equal "john", @admin.login
+ end
+
+ def test_destroy
+ User.find(2).destroy
+ assert_nil User.find_by_id(2)
+ assert Member.find_all_by_user_id(2).empty?
+ end
+
+ def test_validate
+ @admin.login = ""
+ assert !@admin.save
+ assert_equal 1, @admin.errors.count
+ end
+
+ def test_password
+ user = User.try_to_login("admin", "admin")
+ assert_kind_of User, user
+ assert_equal "admin", user.login
+ user.password = "hello"
+ assert user.save
+
+ user = User.try_to_login("admin", "hello")
+ assert_kind_of User, user
+ assert_equal "admin", user.login
+ assert_equal User.hash_password("hello"), user.hashed_password
+ end
+
+ def test_name_format
+ assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
+ Setting.user_format = :firstname_lastname
+ assert_equal 'John Smith', @jsmith.reload.name
+ Setting.user_format = :username
+ assert_equal 'jsmith', @jsmith.reload.name
+ end
+
+ def test_lock
+ user = User.try_to_login("jsmith", "jsmith")
+ assert_equal @jsmith, user
+
+ @jsmith.status = User::STATUS_LOCKED
+ assert @jsmith.save
+
+ user = User.try_to_login("jsmith", "jsmith")
+ assert_equal nil, user
+ end
+
+ def test_create_anonymous
+ AnonymousUser.delete_all
+ anon = User.anonymous
+ assert !anon.new_record?
+ assert_kind_of AnonymousUser, anon
+ end
+
+ def test_rss_key
+ assert_nil @jsmith.rss_token
+ key = @jsmith.rss_key
+ assert_equal 40, key.length
+
+ @jsmith.reload
+ assert_equal key, @jsmith.rss_key
+ end
+
+ def test_role_for_project
+ # user with a role
+ role = @jsmith.role_for_project(Project.find(1))
+ assert_kind_of Role, role
+ assert_equal "Manager", role.name
+
+ # user with no role
+ assert !@dlopper.role_for_project(Project.find(2)).member?
+ end
+
+ def test_mail_notification_all
+ @jsmith.mail_notification = true
+ @jsmith.notified_project_ids = []
+ @jsmith.save
+ @jsmith.reload
+ assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
+ end
+
+ def test_mail_notification_selected
+ @jsmith.mail_notification = false
+ @jsmith.notified_project_ids = [1]
+ @jsmith.save
+ @jsmith.reload
+ assert Project.find(1).recipients.include?(@jsmith.mail)
+ end
+
+ def test_mail_notification_none
+ @jsmith.mail_notification = false
+ @jsmith.notified_project_ids = []
+ @jsmith.save
+ @jsmith.reload
+ assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
+ end
+
+ def test_comments_sorting_preference
+ assert !@jsmith.wants_comments_in_reverse_order?
+ @jsmith.pref.comments_sorting = 'asc'
+ assert !@jsmith.wants_comments_in_reverse_order?
+ @jsmith.pref.comments_sorting = 'desc'
+ assert @jsmith.wants_comments_in_reverse_order?
+ end
+
+ def test_find_by_mail_should_be_case_insensitive
+ u = User.find_by_mail('JSmith@somenet.foo')
+ assert_not_nil u
+ assert_equal 'jsmith@somenet.foo', u.mail
+ end
+
+ def test_random_password
+ u = User.new
+ u.random_password
+ assert !u.password.blank?
+ assert !u.password_confirmation.blank?
+ end
+
+ if Object.const_defined?(:OpenID)
+
+ def test_setting_identity_url
+ normalized_open_id_url = 'http://example.com/'
+ u = User.new( :identity_url => 'http://example.com/' )
+ assert_equal normalized_open_id_url, u.identity_url
+ end
+
+ def test_setting_identity_url_without_trailing_slash
+ normalized_open_id_url = 'http://example.com/'
+ u = User.new( :identity_url => 'http://example.com' )
+ assert_equal normalized_open_id_url, u.identity_url
+ end
+
+ def test_setting_identity_url_without_protocol
+ normalized_open_id_url = 'http://example.com/'
+ u = User.new( :identity_url => 'example.com' )
+ assert_equal normalized_open_id_url, u.identity_url
+ end
+
+ else
+ puts "Skipping openid tests."
+ end
+
+end
diff --git a/test/unit/version_test.rb b/test/unit/version_test.rb
index 29bdc0379..72f049335 100644
--- a/test/unit/version_test.rb
+++ b/test/unit/version_test.rb
@@ -18,7 +18,7 @@
require File.dirname(__FILE__) + '/../test_helper'
class VersionTest < Test::Unit::TestCase
- fixtures :projects, :issues, :issue_statuses, :versions
+ fixtures :projects, :users, :issues, :issue_statuses, :trackers, :enumerations, :versions
def setup
end
@@ -33,4 +33,88 @@ class VersionTest < Test::Unit::TestCase
assert !v.save
assert_equal 'activerecord_error_not_a_date', v.errors.on(:effective_date)
end
+
+ def test_progress_should_be_0_with_no_assigned_issues
+ project = Project.find(1)
+ v = Version.create!(:project => project, :name => 'Progress')
+ assert_equal 0, v.completed_pourcent
+ assert_equal 0, v.closed_pourcent
+ end
+
+ def test_progress_should_be_0_with_unbegun_assigned_issues
+ project = Project.find(1)
+ v = Version.create!(:project => project, :name => 'Progress')
+ add_issue(v)
+ add_issue(v, :done_ratio => 0)
+ assert_progress_equal 0, v.completed_pourcent
+ assert_progress_equal 0, v.closed_pourcent
+ end
+
+ def test_progress_should_be_100_with_closed_assigned_issues
+ project = Project.find(1)
+ status = IssueStatus.find(:first, :conditions => {:is_closed => true})
+ v = Version.create!(:project => project, :name => 'Progress')
+ add_issue(v, :status => status)
+ add_issue(v, :status => status, :done_ratio => 20)
+ add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25)
+ add_issue(v, :status => status, :estimated_hours => 15)
+ assert_progress_equal 100.0, v.completed_pourcent
+ assert_progress_equal 100.0, v.closed_pourcent
+ end
+
+ def test_progress_should_consider_done_ratio_of_open_assigned_issues
+ project = Project.find(1)
+ v = Version.create!(:project => project, :name => 'Progress')
+ add_issue(v)
+ add_issue(v, :done_ratio => 20)
+ add_issue(v, :done_ratio => 70)
+ assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_pourcent
+ assert_progress_equal 0, v.closed_pourcent
+ end
+
+ def test_progress_should_consider_closed_issues_as_completed
+ project = Project.find(1)
+ v = Version.create!(:project => project, :name => 'Progress')
+ add_issue(v)
+ add_issue(v, :done_ratio => 20)
+ add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
+ assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_pourcent
+ assert_progress_equal (100.0)/3, v.closed_pourcent
+ end
+
+ def test_progress_should_consider_estimated_hours_to_weigth_issues
+ project = Project.find(1)
+ v = Version.create!(:project => project, :name => 'Progress')
+ add_issue(v, :estimated_hours => 10)
+ add_issue(v, :estimated_hours => 20, :done_ratio => 30)
+ add_issue(v, :estimated_hours => 40, :done_ratio => 10)
+ add_issue(v, :estimated_hours => 25, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
+ assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_pourcent
+ assert_progress_equal 25.0/95.0*100, v.closed_pourcent
+ end
+
+ def test_progress_should_consider_average_estimated_hours_to_weigth_unestimated_issues
+ project = Project.find(1)
+ v = Version.create!(:project => project, :name => 'Progress')
+ add_issue(v, :done_ratio => 20)
+ add_issue(v, :status => IssueStatus.find(:first, :conditions => {:is_closed => true}))
+ add_issue(v, :estimated_hours => 10, :done_ratio => 30)
+ add_issue(v, :estimated_hours => 40, :done_ratio => 10)
+ assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_pourcent
+ assert_progress_equal 25.0/100.0*100, v.closed_pourcent
+ end
+
+ private
+
+ def add_issue(version, attributes={})
+ Issue.create!({:project => version.project,
+ :fixed_version => version,
+ :subject => 'Test',
+ :author => User.find(:first),
+ :tracker => version.project.trackers.find(:first)}.merge(attributes))
+ end
+
+ def assert_progress_equal(expected_float, actual_float, message="")
+ assert_in_delta(expected_float, actual_float, 0.000001, message="")
+ end
end
diff --git a/vendor/plugins/actionwebservice/CHANGELOG b/vendor/plugins/actionwebservice/CHANGELOG
deleted file mode 100644
index bb5280356..000000000
--- a/vendor/plugins/actionwebservice/CHANGELOG
+++ /dev/null
@@ -1,265 +0,0 @@
-*SVN*
-
-* Documentation for ActionWebService::API::Base. Closes #7275. [zackchandler]
-
-* Allow action_web_service to handle various HTTP methods including GET. Closes #7011. [zackchandler]
-
-* Ensure that DispatcherError is being thrown when a malformed request is received. [Kent Sibilev]
-
-* Added support for decimal types. Closes #6676. [Kent Sibilev]
-
-* Removed deprecated end_form_tag helper. [Kent Sibilev]
-
-* Removed deprecated @request and @response usages. [Kent Sibilev]
-
-* Removed invocation of deprecated before_action and around_action filter methods. Corresponding before_invocation and after_invocation methods should be used instead. #6275 [Kent Sibilev]
-
-* Provide access to the underlying SOAP driver. #6212 [bmilekic, Kent Sibilev]
-
-* Deprecation: update docs. #5998 [jakob@mentalized.net, Kevin Clark]
-
-* ActionWebService WSDL generation ignores HTTP_X_FORWARDED_HOST [Paul Butcher <paul@paulbutcher.com>]
-
-* Tighten rescue clauses. #5985 [james@grayproductions.net]
-
-* Fixed XMLRPC multicall when one of the called methods returns a struct object. [Kent Sibilev]
-
-* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
-
-* Fix invoke_layered since api_method didn't declare :expects. Closes #4720. [Kevin Ballard <kevin@sb.org>, Kent Sibilev]
-
-* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
-
-* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
-
-* Fix test database name typo. [Marcel Molina Jr.]
-
-*1.1.2* (April 9th, 2006)
-
-* Rely on Active Record 1.14.2
-
-
-*1.1.1* (April 6th, 2006)
-
-* Do not convert driver options to strings (#4499)
-
-
-*1.1.0* (March 27th, 2006)
-
-* Make ActiveWebService::Struct type reloadable
-
-* Fix scaffolding action when one of the members of a structural type has date or time type
-
-* Remove extra index hash when generating scaffold html for parameters of structural type #4374 [joe@mjg2.com]
-
-* Fix Scaffold Fails with Struct as a Parameter #4363 [joe@mjg2.com]
-
-* Fix soap type registration of multidimensional arrays (#4232)
-
-* Fix that marshaler couldn't handle ActiveRecord models defined in a different namespace (#2392).
-
-* Fix that marshaler couldn't handle structs with members of ActiveRecord type (#1889).
-
-* Fix that marshaler couldn't handle nil values for inner structs (#3576).
-
-* Fix that changes to ActiveWebService::API::Base required restarting of the server (#2390).
-
-* Fix scaffolding for signatures with :date, :time and :base64 types (#3321, #2769, #2078).
-
-* Fix for incorrect casting of TrueClass/FalseClass instances (#2633, #3421).
-
-* Fix for incompatibility problems with SOAP4R 1.5.5 (#2553) [Kent Sibilev]
-
-
-*1.0.0* (December 13th, 2005)
-
-* Become part of Rails 1.0
-
-*0.9.4* (December 7th, 2005)
-
-* Update from LGPL to MIT license as per Minero Aoki's permission. [Marcel Molina Jr.]
-
-* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
-
-* Fix that XML-RPC date/time values did not have well-defined behaviour (#2516, #2534). This fix has one caveat, in that we can't support pre-1970 dates from XML-RPC clients.
-
-*0.9.3* (November 7th, 2005)
-
-* Upgraded to Action Pack 1.11.0 and Active Record 1.13.0
-
-
-*0.9.2* (October 26th, 2005)
-
-* Upgraded to Action Pack 1.10.2 and Active Record 1.12.2
-
-
-*0.9.1* (October 19th, 2005)
-
-* Upgraded to Action Pack 1.10.1 and Active Record 1.12.1
-
-
-*0.9.0* (October 16th, 2005)
-
-* Fix invalid XML request generation bug in test_invoke [Ken Barker]
-
-* Add XML-RPC 'system.multicall' support #1941 [jbonnar]
-
-* Fix duplicate XSD entries for custom types shared across delegated/layered services #1729 [Tyler Kovacs]
-
-* Allow multiple invocations in the same test method #1720 [dkhawk]
-
-* Added ActionWebService::API::Base.soap_client and ActionWebService::API::Base.xmlrpc_client helper methods to create the internal clients for an API, useful for testing from ./script/console
-
-* ActionWebService now always returns UTF-8 responses.
-
-
-*0.8.1* (11 July, 2005)
-
-* Fix scaffolding for Action Pack controller changes
-
-
-*0.8.0* (6 July, 2005)
-
-* Fix WSDL generation by aliasing #inherited instead of trying to overwrite it, or the WSDL action may end up not being defined in the controller
-
-* Add ActionController::Base.wsdl_namespace option, to allow overriding of the namespace used in generated WSDL and SOAP messages. This is equivalent to the [WebService(Namespace = "Value")] attribute in .NET.
-
-* Add workaround for Ruby 1.8.3's SOAP4R changing the return value of SOAP::Mapping::Registry#find_mapped_soap_class #1414 [Shugo Maeda]
-
-* Fix moduled controller URLs in WSDL, and add unit test to verify the generated URL #1428
-
-* Fix scaffolding template paths, it was broken on Win32
-
-* Fix that functional testing of :layered controllers failed when using the SOAP protocol
-
-* Allow invocation filters in :direct controllers as well, as they have access to more information regarding the web service request than ActionPack filters
-
-* Add support for a :base64 signature type #1272 [Shugo Maeda]
-
-* Fix that boolean fields were not rendered correctly in scaffolding
-
-* Fix that scaffolding was not working for :delegated dispatching
-
-* Add support for structured types as input parameters to scaffolding, this should let one test the blogging APIs using scaffolding as well
-
-* Fix that generated WSDL was not using relative_url_root for base URI #1210 [Shugo Maeda]
-
-* Use UTF-8 encoding by default for SOAP responses, but if an encoding is supplied by caller, use that for the response #1211 [Shugo Maeda, NAKAMURA Hiroshi]
-
-* If the WSDL was retrieved over HTTPS, use HTTPS URLs in the WSDL too
-
-* Fix that casting change in 0.7.0 would convert nil values to the default value for the type instead of leaving it as nil
-
-
-*0.7.1* (20th April, 2005)
-
-* Depend on Active Record 1.10.1 and Action Pack 1.8.1
-
-
-*0.7.0* (19th April, 2005)
-
-* When casting structured types, don't try to send obj.name= unless obj responds to it, causes casting to be less likely to fail for XML-RPC
-
-* Add scaffolding via ActionController::Base.web_service_scaffold for quick testing using a web browser
-
-* ActionWebService::API::Base#api_methods now returns a hash containing ActionWebService::API::Method objects instead of hashes. However, ActionWebService::API::Method defines a #[]() backwards compatibility method so any existing code utilizing this will still work.
-
-* The :layered dispatching mode can now be used with SOAP as well, allowing you to support SOAP and XML-RPC clients for APIs like the metaWeblog API
-
-* Remove ActiveRecordSoapMarshallable workaround, see #912 for details
-
-* Generalize casting code to be used by both SOAP and XML-RPC (previously, it was only XML-RPC)
-
-* Ensure return value is properly cast as well, fixes XML-RPC interoperability with Ecto and possibly other clients
-
-* Include backtraces in 500 error responses for failed request parsing, and remove "rescue nil" statements obscuring real errors for XML-RPC
-
-* Perform casting of struct members even if the structure is already of the correct type, so that the type we specify for the struct member is always the type of the value seen by the API implementation
-
-
-*0.6.2* (27th March, 2005)
-
-* Allow method declarations for direct dispatching to declare parameters as well. We treat an arity of < 0 or > 0 as an indication that we should send through parameters. Closes #939.
-
-
-*0.6.1* (22th March, 2005)
-
-* Fix that method response QNames mismatched with that declared in the WSDL, makes SOAP::WSDLDriverFactory work against AWS again
-
-* Fix that @request.env was being modified, instead, dup the value gotten from env
-
-* Fix XML-RPC example to use :layered mode, so it works again
-
-* Support casting '0' or 0 into false, and '1' or 1 into true, when expecting a boolean value
-
-* Fix that SOAP fault response fault code values were not QName's #804
-
-
-*0.6.0* (7th March, 2005)
-
-* Add action_controller/test_invoke, used for integrating AWS with the Rails testing infrastructure
-
-* Allow passing through options to the SOAP RPC driver for the SOAP client
-
-* Make the SOAP WS marshaler use #columns to decide which fields to marshal as well, avoids providing attributes brought in by associations
-
-* Add <tt>ActionWebService::API::Base.allow_active_record_expects</tt> option, with a default of false. Setting this to true will allow specifying ActiveRecord::Base model classes in <tt>:expects</tt>. API writers should take care to validate the received ActiveRecord model objects when turning it on, and/or have an authentication mechanism in place to reduce the security risk.
-
-* Improve error message reporting. Bugs in either AWS or the web service itself will send back a protocol-specific error report message if possible, otherwise, provide as much detail as possible.
-
-* Removed type checking of received parameters, and perform casting for XML-RPC if possible, but fallback to the received parameters if casting fails, closes #677
-
-* Refactored SOAP and XML-RPC marshaling and encoding into a small library devoted exclusively to protocol specifics, also cleaned up the SOAP marshaling approach, so that array and custom type marshaling should be a bit faster.
-
-* Add namespaced XML-RPC method name support, closes #678
-
-* Replace '::' with '..' in fully qualified type names for marshaling and WSDL. This improves interoperability with .NET, and closes #676.
-
-
-*0.5.0* (24th February, 2005)
-
- * lib/action_service/dispatcher*: replace "router" fragments with
- one file for Action Controllers, moves dispatching work out of
- the container
- * lib/*,test/*,examples/*: rename project to
- ActionWebService. prefix all generic "service" type names with web_.
- update all using code as well as the RDoc.
- * lib/action_service/router/wsdl.rb: ensure that #wsdl is
- defined in the final container class, or the new ActionPack
- filtering will exclude it
- * lib/action_service/struct.rb,test/struct_test.rb: create a
- default #initialize on inherit that accepts a Hash containing
- the default member values
- * lib/action_service/api/action_controller.rb: add support and
- tests for #client_api in controller
- * test/router_wsdl_test.rb: add tests to ensure declared
- service names don't contain ':', as ':' causes interoperability
- issues
- * lib/*, test/*: rename "interface" concept to "api", and change all
- related uses to reflect this change. update all uses of Inflector
- to call the method on String instead.
- * test/api_test.rb: add test to ensure API definition not
- instantiatable
- * lib/action_service/invocation.rb: change @invocation_params to
- @method_params
- * lib/*: update RDoc
- * lib/action_service/struct.rb: update to support base types
- * lib/action_service/support/signature.rb: support the notion of
- "base types" in signatures, with well-known unambiguous names such as :int,
- :bool, etc, which map to the correct Ruby class. accept the same names
- used by ActiveRecord as well as longer versions of each, as aliases.
- * examples/*: update for seperate API definition updates
- * lib/action_service/*, test/*: extensive refactoring: define API methods in
- a seperate class, and specify it wherever used with 'service_api'.
- this makes writing a client API for accessing defined API methods
- with ActionWebService really easy.
- * lib/action_service/container.rb: fix a bug in default call
- handling for direct dispatching, and add ActionController filter
- support for direct dispatching.
- * test/router_action_controller_test.rb: add tests to ensure
- ActionController filters are actually called.
- * test/protocol_soap_test.rb: add more tests for direct dispatching.
-
-0.3.0
-
- * First public release
diff --git a/vendor/plugins/actionwebservice/README b/vendor/plugins/actionwebservice/README
deleted file mode 100644
index 78b91f081..000000000
--- a/vendor/plugins/actionwebservice/README
+++ /dev/null
@@ -1,364 +0,0 @@
-= Action Web Service -- Serving APIs on rails
-
-Action Web Service provides a way to publish interoperable web service APIs with
-Rails without spending a lot of time delving into protocol details.
-
-
-== Features
-
-* SOAP RPC protocol support
-* Dynamic WSDL generation for APIs
-* XML-RPC protocol support
-* Clients that use the same API definitions as the server for
- easy interoperability with other Action Web Service based applications
-* Type signature hints to improve interoperability with static languages
-* Active Record model class support in signatures
-
-
-== Defining your APIs
-
-You specify the methods you want to make available as API methods in an
-ActionWebService::API::Base derivative, and then specify this API
-definition class wherever you want to use that API.
-
-The implementation of the methods is done separately from the API
-specification.
-
-
-==== Method name inflection
-
-Action Web Service will camelcase the method names according to Rails Inflector
-rules for the API visible to public callers. What this means, for example,
-is that the method names in generated WSDL will be camelcased, and callers will
-have to supply the camelcased name in their requests for the request to
-succeed.
-
-If you do not desire this behaviour, you can turn it off with the
-ActionWebService::API::Base +inflect_names+ option.
-
-
-==== Inflection examples
-
- :add => Add
- :find_all => FindAll
-
-
-==== Disabling inflection
-
- class PersonAPI < ActionWebService::API::Base
- inflect_names false
- end
-
-
-==== API definition example
-
- class PersonAPI < ActionWebService::API::Base
- api_method :add, :expects => [:string, :string, :bool], :returns => [:int]
- api_method :remove, :expects => [:int], :returns => [:bool]
- end
-
-==== API usage example
-
- class PersonController < ActionController::Base
- web_service_api PersonAPI
-
- def add
- end
-
- def remove
- end
- end
-
-
-== Publishing your APIs
-
-Action Web Service uses Action Pack to process protocol requests. There are two
-modes of dispatching protocol requests, _Direct_, and _Delegated_.
-
-
-=== Direct dispatching
-
-This is the default mode. In this mode, public controller instance methods
-implement the API methods, and parameters are passed through to the methods in
-accordance with the API specification.
-
-The return value of the method is sent back as the return value to the
-caller.
-
-In this mode, a special <tt>api</tt> action is generated in the target
-controller to unwrap the protocol request, forward it on to the relevant method
-and send back the wrapped return value. <em>This action must not be
-overridden.</em>
-
-==== Direct dispatching example
-
- class PersonController < ApplicationController
- web_service_api PersonAPI
-
- def add
- end
-
- def remove
- end
- end
-
- class PersonAPI < ActionWebService::API::Base
- ...
- end
-
-
-For this example, protocol requests for +Add+ and +Remove+ methods sent to
-<tt>/person/api</tt> will be routed to the controller methods +add+ and +remove+.
-
-
-=== Delegated dispatching
-
-This mode can be turned on by setting the +web_service_dispatching_mode+ option
-in a controller to <tt>:delegated</tt>.
-
-In this mode, the controller contains one or more web service objects (objects
-that implement an ActionWebService::API::Base definition). These web service
-objects are each mapped onto one controller action only.
-
-==== Delegated dispatching example
-
- class ApiController < ApplicationController
- web_service_dispatching_mode :delegated
-
- web_service :person, PersonService.new
- end
-
- class PersonService < ActionWebService::Base
- web_service_api PersonAPI
-
- def add
- end
-
- def remove
- end
- end
-
- class PersonAPI < ActionWebService::API::Base
- ...
- end
-
-
-For this example, all protocol requests for +PersonService+ are
-sent to the <tt>/api/person</tt> action.
-
-The <tt>/api/person</tt> action is generated when the +web_service+
-method is called. <em>This action must not be overridden.</em>
-
-Other controller actions (actions that aren't the target of a +web_service+ call)
-are ignored for ActionWebService purposes, and can do normal action tasks.
-
-
-=== Layered dispatching
-
-This mode can be turned on by setting the +web_service_dispatching_mode+ option
-in a controller to <tt>:layered</tt>.
-
-This mode is similar to _delegated_ mode, in that multiple web service objects
-can be attached to one controller, however, all protocol requests are sent to a
-single endpoint.
-
-Use this mode when you want to share code between XML-RPC and SOAP clients,
-for APIs where the XML-RPC method names have prefixes. An example of such
-a method name would be <tt>blogger.newPost</tt>.
-
-
-==== Layered dispatching example
-
-
- class ApiController < ApplicationController
- web_service_dispatching_mode :layered
-
- web_service :mt, MovableTypeService.new
- web_service :blogger, BloggerService.new
- web_service :metaWeblog, MetaWeblogService.new
- end
-
- class MovableTypeService < ActionWebService::Base
- ...
- end
-
- class BloggerService < ActionWebService::Base
- ...
- end
-
- class MetaWeblogService < ActionWebService::API::Base
- ...
- end
-
-
-For this example, an XML-RPC call for a method with a name like
-<tt>mt.getCategories</tt> will be sent to the <tt>getCategories</tt>
-method on the <tt>:mt</tt> service.
-
-
-== Customizing WSDL generation
-
-You can customize the names used for the SOAP bindings in the generated
-WSDL by using the wsdl_service_name option in a controller:
-
- class WsController < ApplicationController
- wsdl_service_name 'MyApp'
- end
-
-You can also customize the namespace used in the generated WSDL for
-custom types and message definition types:
-
- class WsController < ApplicationController
- wsdl_namespace 'http://my.company.com/app/wsapi'
- end
-
-The default namespace used is 'urn:ActionWebService', if you don't supply
-one.
-
-
-== ActionWebService and UTF-8
-
-If you're going to be sending back strings containing non-ASCII UTF-8
-characters using the <tt>:string</tt> data type, you need to make sure that
-Ruby is using UTF-8 as the default encoding for its strings.
-
-The default in Ruby is to use US-ASCII encoding for strings, which causes a string
-validation check in the Ruby SOAP library to fail and your string to be sent
-back as a Base-64 value, which may confuse clients that expected strings
-because of the WSDL.
-
-Two ways of setting the default string encoding are:
-
-* Start Ruby using the <tt>-Ku</tt> command-line option to the Ruby executable
-* Set the <tt>$KCODE</tt> flag in <tt>config/environment.rb</tt> to the
- string <tt>'UTF8'</tt>
-
-
-== Testing your APIs
-
-
-=== Functional testing
-
-You can perform testing of your APIs by creating a functional test for the
-controller dispatching the API, and calling #invoke in the test case to
-perform the invocation.
-
-Example:
-
- class PersonApiControllerTest < Test::Unit::TestCase
- def setup
- @controller = PersonController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
- end
-
- def test_add
- result = invoke :remove, 1
- assert_equal true, result
- end
- end
-
-This example invokes the API method <tt>test</tt>, defined on
-the PersonController, and returns the result.
-
-
-=== Scaffolding
-
-You can also test your APIs with a web browser by attaching scaffolding
-to the controller.
-
-Example:
-
- class PersonController
- web_service_scaffold :invocation
- end
-
-This creates an action named <tt>invocation</tt> on the PersonController.
-
-Navigating to this action lets you select the method to invoke, supply the parameters,
-and view the result of the invocation.
-
-
-== Using the client support
-
-Action Web Service includes client classes that can use the same API
-definition as the server. The advantage of this approach is that your client
-will have the same support for Active Record and structured types as the
-server, and can just use them directly, and rely on the marshaling to Do The
-Right Thing.
-
-*Note*: The client support is intended for communication between Ruby on Rails
-applications that both use Action Web Service. It may work with other servers, but
-that is not its intended use, and interoperability can't be guaranteed, especially
-not for .NET web services.
-
-Web services protocol specifications are complex, and Action Web Service client
-support can only be guaranteed to work with a subset.
-
-
-==== Factory created client example
-
- class BlogManagerController < ApplicationController
- web_client_api :blogger, :xmlrpc, 'http://url/to/blog/api/RPC2', :handler_name => 'blogger'
- end
-
- class SearchingController < ApplicationController
- web_client_api :google, :soap, 'http://url/to/blog/api/beta', :service_name => 'GoogleSearch'
- end
-
-See ActionWebService::API::ActionController::ClassMethods for more details.
-
-==== Manually created client example
-
- class PersonAPI < ActionWebService::API::Base
- api_method :find_all, :returns => [[Person]]
- end
-
- soap_client = ActionWebService::Client::Soap.new(PersonAPI, "http://...")
- persons = soap_client.find_all
-
- class BloggerAPI < ActionWebService::API::Base
- inflect_names false
- api_method :getRecentPosts, :returns => [[Blog::Post]]
- end
-
- blog = ActionWebService::Client::XmlRpc.new(BloggerAPI, "http://.../xmlrpc", :handler_name => "blogger")
- posts = blog.getRecentPosts
-
-
-See ActionWebService::Client::Soap and ActionWebService::Client::XmlRpc for more details.
-
-== Dependencies
-
-Action Web Service requires that the Action Pack and Active Record are either
-available to be required immediately or are accessible as GEMs.
-
-It also requires a version of Ruby that includes SOAP support in the standard
-library. At least version 1.8.2 final (2004-12-25) of Ruby is recommended; this
-is the version tested against.
-
-
-== Download
-
-The latest Action Web Service version can be downloaded from
-http://rubyforge.org/projects/actionservice
-
-
-== Installation
-
-You can install Action Web Service with the following command.
-
- % [sudo] ruby setup.rb
-
-
-== License
-
-Action Web Service is released under the MIT license.
-
-
-== Support
-
-The Ruby on Rails mailing list
-
-Or, to contact the author, send mail to bitserf@gmail.com
-
diff --git a/vendor/plugins/actionwebservice/Rakefile b/vendor/plugins/actionwebservice/Rakefile
deleted file mode 100644
index ad2ad223e..000000000
--- a/vendor/plugins/actionwebservice/Rakefile
+++ /dev/null
@@ -1,172 +0,0 @@
-require 'rubygems'
-require 'rake'
-require 'rake/testtask'
-require 'rake/rdoctask'
-require 'rake/packagetask'
-require 'rake/gempackagetask'
-require 'rake/contrib/rubyforgepublisher'
-require 'fileutils'
-require File.join(File.dirname(__FILE__), 'lib', 'action_web_service', 'version')
-
-PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
-PKG_NAME = 'actionwebservice'
-PKG_VERSION = ActionWebService::VERSION::STRING + PKG_BUILD
-PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
-PKG_DESTINATION = ENV["RAILS_PKG_DESTINATION"] || "../#{PKG_NAME}"
-
-RELEASE_NAME = "REL #{PKG_VERSION}"
-
-RUBY_FORGE_PROJECT = "aws"
-RUBY_FORGE_USER = "webster132"
-
-desc "Default Task"
-task :default => [ :test ]
-
-
-# Run the unit tests
-Rake::TestTask.new { |t|
- t.libs << "test"
- t.test_files = Dir['test/*_test.rb']
- t.verbose = true
-}
-
-SCHEMA_PATH = File.join(File.dirname(__FILE__), *%w(test fixtures db_definitions))
-
-desc 'Build the MySQL test database'
-task :build_database do
- %x( mysqladmin create actionwebservice_unittest )
- %x( mysql actionwebservice_unittest < #{File.join(SCHEMA_PATH, 'mysql.sql')} )
-end
-
-
-# Generate the RDoc documentation
-Rake::RDocTask.new { |rdoc|
- rdoc.rdoc_dir = 'doc'
- rdoc.title = "Action Web Service -- Web services for Action Pack"
- rdoc.options << '--line-numbers' << '--inline-source'
- rdoc.options << '--charset' << 'utf-8'
- rdoc.template = "#{ENV['template']}.rb" if ENV['template']
- rdoc.rdoc_files.include('README')
- rdoc.rdoc_files.include('CHANGELOG')
- rdoc.rdoc_files.include('lib/action_web_service.rb')
- rdoc.rdoc_files.include('lib/action_web_service/*.rb')
- rdoc.rdoc_files.include('lib/action_web_service/api/*.rb')
- rdoc.rdoc_files.include('lib/action_web_service/client/*.rb')
- rdoc.rdoc_files.include('lib/action_web_service/container/*.rb')
- rdoc.rdoc_files.include('lib/action_web_service/dispatcher/*.rb')
- rdoc.rdoc_files.include('lib/action_web_service/protocol/*.rb')
- rdoc.rdoc_files.include('lib/action_web_service/support/*.rb')
-}
-
-
-# Create compressed packages
-spec = Gem::Specification.new do |s|
- s.platform = Gem::Platform::RUBY
- s.name = PKG_NAME
- s.summary = "Web service support for Action Pack."
- s.description = %q{Adds WSDL/SOAP and XML-RPC web service support to Action Pack}
- s.version = PKG_VERSION
-
- s.author = "Leon Breedt"
- s.email = "bitserf@gmail.com"
- s.rubyforge_project = "aws"
- s.homepage = "http://www.rubyonrails.org"
-
- s.add_dependency('actionpack', '= 1.13.5' + PKG_BUILD)
- s.add_dependency('activerecord', '= 1.15.5' + PKG_BUILD)
-
- s.has_rdoc = true
- s.requirements << 'none'
- s.require_path = 'lib'
- s.autorequire = 'action_web_service'
-
- s.files = [ "Rakefile", "setup.rb", "README", "TODO", "CHANGELOG", "MIT-LICENSE" ]
- s.files = s.files + Dir.glob( "examples/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
- s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
- s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
-end
-Rake::GemPackageTask.new(spec) do |p|
- p.gem_spec = spec
- p.need_tar = true
- p.need_zip = true
-end
-
-
-# Publish beta gem
-desc "Publish the API documentation"
-task :pgem => [:package] do
- Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
- `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
-end
-
-# Publish documentation
-desc "Publish the API documentation"
-task :pdoc => [:rdoc] do
- Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/aws", "doc").upload
-end
-
-
-def each_source_file(*args)
- prefix, includes, excludes, open_file = args
- prefix ||= File.dirname(__FILE__)
- open_file = true if open_file.nil?
- includes ||= %w[lib\/action_web_service\.rb$ lib\/action_web_service\/.*\.rb$]
- excludes ||= %w[lib\/action_web_service\/vendor]
- Find.find(prefix) do |file_name|
- next if file_name =~ /\.svn/
- file_name.gsub!(/^\.\//, '')
- continue = false
- includes.each do |inc|
- if file_name.match(/#{inc}/)
- continue = true
- break
- end
- end
- next unless continue
- excludes.each do |exc|
- if file_name.match(/#{exc}/)
- continue = false
- break
- end
- end
- next unless continue
- if open_file
- File.open(file_name) do |f|
- yield file_name, f
- end
- else
- yield file_name
- end
- end
-end
-
-desc "Count lines of the AWS source code"
-task :lines do
- total_lines = total_loc = 0
- puts "Per File:"
- each_source_file do |file_name, f|
- file_lines = file_loc = 0
- while line = f.gets
- file_lines += 1
- next if line =~ /^\s*$/
- next if line =~ /^\s*#/
- file_loc += 1
- end
- puts " #{file_name}: Lines #{file_lines}, LOC #{file_loc}"
- total_lines += file_lines
- total_loc += file_loc
- end
- puts "Total:"
- puts " Lines #{total_lines}, LOC #{total_loc}"
-end
-
-desc "Publish the release files to RubyForge."
-task :release => [ :package ] do
- require 'rubyforge'
-
- packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
-
- rubyforge = RubyForge.new
- rubyforge.login
- rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
-end
diff --git a/vendor/plugins/actionwebservice/TODO b/vendor/plugins/actionwebservice/TODO
deleted file mode 100644
index 7c022c14c..000000000
--- a/vendor/plugins/actionwebservice/TODO
+++ /dev/null
@@ -1,32 +0,0 @@
-= Post-1.0
- - Document/Literal SOAP support
- - URL-based dispatching, URL identifies method
-
- - Add :rest dispatching mode, a.l.a. Backpack API. Clean up dispatching
- in general. Support vanilla XML-format as a "Rails" protocol?
- XML::Simple deserialization into params?
-
- web_service_dispatching_mode :rest
-
- def method1(params)
- end
-
- def method2(params)
- end
-
-
- /ws/method1
- <xml>
- /ws/method2
- <yaml>
-
- - Allow locking down a controller to only accept messages for a particular
- protocol. This will allow us to generate fully conformant error messages
- in cases where we currently fudge it if we don't know the protocol.
-
- - Allow AWS user to participate in typecasting, so they can centralize
- workarounds for buggy input in one place
-
-= Refactoring
- - Don't have clean way to go from SOAP Class object to the xsd:NAME type
- string -- NaHi possibly looking at remedying this situation
diff --git a/vendor/plugins/actionwebservice/init.rb b/vendor/plugins/actionwebservice/init.rb
deleted file mode 100644
index ade118c0f..000000000
--- a/vendor/plugins/actionwebservice/init.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'action_web_service'
-
-# These need to be in the load path for action_web_service to work
-Dependencies.load_paths += ["#{RAILS_ROOT}/app/apis"]
-
-# AWS Test helpers
-require 'action_web_service/test_invoke' if ENV['RAILS_ENV'] && ENV['RAILS_ENV'] =~ /^test/
diff --git a/vendor/plugins/actionwebservice/install.rb b/vendor/plugins/actionwebservice/install.rb
deleted file mode 100644
index da08bf5f9..000000000
--- a/vendor/plugins/actionwebservice/install.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'rbconfig'
-require 'find'
-require 'ftools'
-
-include Config
-
-# this was adapted from rdoc's install.rb by way of Log4r
-
-$sitedir = CONFIG["sitelibdir"]
-unless $sitedir
- version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
- $libdir = File.join(CONFIG["libdir"], "ruby", version)
- $sitedir = $:.find {|x| x =~ /site_ruby/ }
- if !$sitedir
- $sitedir = File.join($libdir, "site_ruby")
- elsif $sitedir !~ Regexp.quote(version)
- $sitedir = File.join($sitedir, version)
- end
-end
-
-# the actual gruntwork
-Dir.chdir("lib")
-
-Find.find("action_web_service", "action_web_service.rb") { |f|
- if f[-3..-1] == ".rb"
- File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
- else
- File::makedirs(File.join($sitedir, *f.split(/\//)))
- end
-}
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service.rb b/vendor/plugins/actionwebservice/lib/action_web_service.rb
deleted file mode 100644
index 0632dd1ec..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-#--
-# Copyright (C) 2005 Leon Breedt
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#++
-
-begin
- require 'active_support'
- require 'action_controller'
- require 'active_record'
-rescue LoadError
- require 'rubygems'
- gem 'activesupport', '>= 1.0.2'
- gem 'actionpack', '>= 1.6.0'
- gem 'activerecord', '>= 1.9.0'
-end
-
-$:.unshift(File.dirname(__FILE__) + "/action_web_service/vendor/")
-
-require 'action_web_service/support/class_inheritable_options'
-require 'action_web_service/support/signature_types'
-require 'action_web_service/base'
-require 'action_web_service/client'
-require 'action_web_service/invocation'
-require 'action_web_service/api'
-require 'action_web_service/casting'
-require 'action_web_service/struct'
-require 'action_web_service/container'
-require 'action_web_service/protocol'
-require 'action_web_service/dispatcher'
-require 'action_web_service/scaffolding'
-
-ActionWebService::Base.class_eval do
- include ActionWebService::Container::Direct
- include ActionWebService::Invocation
-end
-
-ActionController::Base.class_eval do
- include ActionWebService::Protocol::Discovery
- include ActionWebService::Protocol::Soap
- include ActionWebService::Protocol::XmlRpc
- include ActionWebService::Container::Direct
- include ActionWebService::Container::Delegated
- include ActionWebService::Container::ActionController
- include ActionWebService::Invocation
- include ActionWebService::Dispatcher
- include ActionWebService::Dispatcher::ActionController
- include ActionWebService::Scaffolding
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/api.rb b/vendor/plugins/actionwebservice/lib/action_web_service/api.rb
deleted file mode 100644
index d16dc420d..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/api.rb
+++ /dev/null
@@ -1,297 +0,0 @@
-module ActionWebService # :nodoc:
- module API # :nodoc:
- # A web service API class specifies the methods that will be available for
- # invocation for an API. It also contains metadata such as the method type
- # signature hints.
- #
- # It is not intended to be instantiated.
- #
- # It is attached to web service implementation classes like
- # ActionWebService::Base and ActionController::Base derivatives by using
- # <tt>container.web_service_api</tt>, where <tt>container</tt> is an
- # ActionController::Base or a ActionWebService::Base.
- #
- # See ActionWebService::Container::Direct::ClassMethods for an example
- # of use.
- class Base
- # Whether to transform the public API method names into camel-cased names
- class_inheritable_option :inflect_names, true
-
- # By default only HTTP POST requests are processed
- class_inheritable_option :allowed_http_methods, [ :post ]
-
- # Whether to allow ActiveRecord::Base models in <tt>:expects</tt>.
- # The default is +false+; you should be aware of the security implications
- # of allowing this, and ensure that you don't allow remote callers to
- # easily overwrite data they should not have access to.
- class_inheritable_option :allow_active_record_expects, false
-
- # If present, the name of a method to call when the remote caller
- # tried to call a nonexistent method. Semantically equivalent to
- # +method_missing+.
- class_inheritable_option :default_api_method
-
- # Disallow instantiation
- private_class_method :new, :allocate
-
- class << self
- include ActionWebService::SignatureTypes
-
- # API methods have a +name+, which must be the Ruby method name to use when
- # performing the invocation on the web service object.
- #
- # The signatures for the method input parameters and return value can
- # by specified in +options+.
- #
- # A signature is an array of one or more parameter specifiers.
- # A parameter specifier can be one of the following:
- #
- # * A symbol or string representing one of the Action Web Service base types.
- # See ActionWebService::SignatureTypes for a canonical list of the base types.
- # * The Class object of the parameter type
- # * A single-element Array containing one of the two preceding items. This
- # will cause Action Web Service to treat the parameter at that position
- # as an array containing only values of the given type.
- # * A Hash containing as key the name of the parameter, and as value
- # one of the three preceding items
- #
- # If no method input parameter or method return value signatures are given,
- # the method is assumed to take no parameters and/or return no values of
- # interest, and any values that are received by the server will be
- # discarded and ignored.
- #
- # Valid options:
- # [<tt>:expects</tt>] Signature for the method input parameters
- # [<tt>:returns</tt>] Signature for the method return value
- # [<tt>:expects_and_returns</tt>] Signature for both input parameters and return value
- def api_method(name, options={})
- unless options.is_a?(Hash)
- raise(ActionWebServiceError, "Expected a Hash for options")
- end
- validate_options([:expects, :returns, :expects_and_returns], options.keys)
- if options[:expects_and_returns]
- expects = options[:expects_and_returns]
- returns = options[:expects_and_returns]
- else
- expects = options[:expects]
- returns = options[:returns]
- end
- expects = canonical_signature(expects)
- returns = canonical_signature(returns)
- if expects
- expects.each do |type|
- type = type.element_type if type.is_a?(ArrayType)
- if type.type_class.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects
- raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
- end
- end
- end
- name = name.to_sym
- public_name = public_api_method_name(name)
- method = Method.new(name, public_name, expects, returns)
- write_inheritable_hash("api_methods", name => method)
- write_inheritable_hash("api_public_method_names", public_name => name)
- end
-
- # Whether the given method name is a service method on this API
- #
- # class ProjectsApi < ActionWebService::API::Base
- # api_method :getCount, :returns => [:int]
- # end
- #
- # ProjectsApi.has_api_method?('GetCount') #=> false
- # ProjectsApi.has_api_method?(:getCount) #=> true
- def has_api_method?(name)
- api_methods.has_key?(name)
- end
-
- # Whether the given public method name has a corresponding service method
- # on this API
- #
- # class ProjectsApi < ActionWebService::API::Base
- # api_method :getCount, :returns => [:int]
- # end
- #
- # ProjectsApi.has_api_method?(:getCount) #=> false
- # ProjectsApi.has_api_method?('GetCount') #=> true
- def has_public_api_method?(public_name)
- api_public_method_names.has_key?(public_name)
- end
-
- # The corresponding public method name for the given service method name
- #
- # ProjectsApi.public_api_method_name('GetCount') #=> "GetCount"
- # ProjectsApi.public_api_method_name(:getCount) #=> "GetCount"
- def public_api_method_name(name)
- if inflect_names
- name.to_s.camelize
- else
- name.to_s
- end
- end
-
- # The corresponding service method name for the given public method name
- #
- # class ProjectsApi < ActionWebService::API::Base
- # api_method :getCount, :returns => [:int]
- # end
- #
- # ProjectsApi.api_method_name('GetCount') #=> :getCount
- def api_method_name(public_name)
- api_public_method_names[public_name]
- end
-
- # A Hash containing all service methods on this API, and their
- # associated metadata.
- #
- # class ProjectsApi < ActionWebService::API::Base
- # api_method :getCount, :returns => [:int]
- # api_method :getCompletedCount, :returns => [:int]
- # end
- #
- # ProjectsApi.api_methods #=>
- # {:getCount=>#<ActionWebService::API::Method:0x24379d8 ...>,
- # :getCompletedCount=>#<ActionWebService::API::Method:0x2437794 ...>}
- # ProjectsApi.api_methods[:getCount].public_name #=> "GetCount"
- def api_methods
- read_inheritable_attribute("api_methods") || {}
- end
-
- # The Method instance for the given public API method name, if any
- #
- # class ProjectsApi < ActionWebService::API::Base
- # api_method :getCount, :returns => [:int]
- # api_method :getCompletedCount, :returns => [:int]
- # end
- #
- # ProjectsApi.public_api_method_instance('GetCount') #=> <#<ActionWebService::API::Method:0x24379d8 ...>
- # ProjectsApi.public_api_method_instance(:getCount) #=> nil
- def public_api_method_instance(public_method_name)
- api_method_instance(api_method_name(public_method_name))
- end
-
- # The Method instance for the given API method name, if any
- #
- # class ProjectsApi < ActionWebService::API::Base
- # api_method :getCount, :returns => [:int]
- # api_method :getCompletedCount, :returns => [:int]
- # end
- #
- # ProjectsApi.api_method_instance(:getCount) #=> <ActionWebService::API::Method:0x24379d8 ...>
- # ProjectsApi.api_method_instance('GetCount') #=> <ActionWebService::API::Method:0x24379d8 ...>
- def api_method_instance(method_name)
- api_methods[method_name]
- end
-
- # The Method instance for the default API method, if any
- def default_api_method_instance
- return nil unless name = default_api_method
- instance = read_inheritable_attribute("default_api_method_instance")
- if instance && instance.name == name
- return instance
- end
- instance = Method.new(name, public_api_method_name(name), nil, nil)
- write_inheritable_attribute("default_api_method_instance", instance)
- instance
- end
-
- private
- def api_public_method_names
- read_inheritable_attribute("api_public_method_names") || {}
- end
-
- def validate_options(valid_option_keys, supplied_option_keys)
- unknown_option_keys = supplied_option_keys - valid_option_keys
- unless unknown_option_keys.empty?
- raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}")
- end
- end
- end
- end
-
- # Represents an API method and its associated metadata, and provides functionality
- # to assist in commonly performed API method tasks.
- class Method
- attr :name
- attr :public_name
- attr :expects
- attr :returns
-
- def initialize(name, public_name, expects, returns)
- @name = name
- @public_name = public_name
- @expects = expects
- @returns = returns
- @caster = ActionWebService::Casting::BaseCaster.new(self)
- end
-
- # The list of parameter names for this method
- def param_names
- return [] unless @expects
- @expects.map{ |type| type.name }
- end
-
- # Casts a set of Ruby values into the expected Ruby values
- def cast_expects(params)
- @caster.cast_expects(params)
- end
-
- # Cast a Ruby return value into the expected Ruby value
- def cast_returns(return_value)
- @caster.cast_returns(return_value)
- end
-
- # Returns the index of the first expected parameter
- # with the given name
- def expects_index_of(param_name)
- return -1 if @expects.nil?
- (0..(@expects.length-1)).each do |i|
- return i if @expects[i].name.to_s == param_name.to_s
- end
- -1
- end
-
- # Returns a hash keyed by parameter name for the given
- # parameter list
- def expects_to_hash(params)
- return {} if @expects.nil?
- h = {}
- @expects.zip(params){ |type, param| h[type.name] = param }
- h
- end
-
- # Backwards compatibility with previous API
- def [](sig_type)
- case sig_type
- when :expects
- @expects.map{|x| compat_signature_entry(x)}
- when :returns
- @returns.map{|x| compat_signature_entry(x)}
- end
- end
-
- # String representation of this method
- def to_s
- fqn = ""
- fqn << (@returns ? (@returns[0].human_name(false) + " ") : "void ")
- fqn << "#{@public_name}("
- fqn << @expects.map{ |p| p.human_name }.join(", ") if @expects
- fqn << ")"
- fqn
- end
-
- private
- def compat_signature_entry(entry)
- if entry.array?
- [compat_signature_entry(entry.element_type)]
- else
- if entry.spec.is_a?(Hash)
- {entry.spec.keys.first => entry.type_class}
- else
- entry.type_class
- end
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/base.rb b/vendor/plugins/actionwebservice/lib/action_web_service/base.rb
deleted file mode 100644
index 6282061d8..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/base.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-module ActionWebService # :nodoc:
- class ActionWebServiceError < StandardError # :nodoc:
- end
-
- # An Action Web Service object implements a specified API.
- #
- # Used by controllers operating in _Delegated_ dispatching mode.
- #
- # ==== Example
- #
- # class PersonService < ActionWebService::Base
- # web_service_api PersonAPI
- #
- # def find_person(criteria)
- # Person.find(:all) [...]
- # end
- #
- # def delete_person(id)
- # Person.find_by_id(id).destroy
- # end
- # end
- #
- # class PersonAPI < ActionWebService::API::Base
- # api_method :find_person, :expects => [SearchCriteria], :returns => [[Person]]
- # api_method :delete_person, :expects => [:int]
- # end
- #
- # class SearchCriteria < ActionWebService::Struct
- # member :firstname, :string
- # member :lastname, :string
- # member :email, :string
- # end
- class Base
- # Whether to report exceptions back to the caller in the protocol's exception
- # format
- class_inheritable_option :web_service_exception_reporting, true
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/casting.rb b/vendor/plugins/actionwebservice/lib/action_web_service/casting.rb
deleted file mode 100644
index 71f422eae..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/casting.rb
+++ /dev/null
@@ -1,138 +0,0 @@
-require 'time'
-require 'date'
-require 'xmlrpc/datetime'
-
-module ActionWebService # :nodoc:
- module Casting # :nodoc:
- class CastingError < ActionWebServiceError # :nodoc:
- end
-
- # Performs casting of arbitrary values into the correct types for the signature
- class BaseCaster # :nodoc:
- def initialize(api_method)
- @api_method = api_method
- end
-
- # Coerces the parameters in +params+ (an Enumerable) into the types
- # this method expects
- def cast_expects(params)
- self.class.cast_expects(@api_method, params)
- end
-
- # Coerces the given +return_value+ into the type returned by this
- # method
- def cast_returns(return_value)
- self.class.cast_returns(@api_method, return_value)
- end
-
- class << self
- include ActionWebService::SignatureTypes
-
- def cast_expects(api_method, params) # :nodoc:
- return [] if api_method.expects.nil?
- api_method.expects.zip(params).map{ |type, param| cast(param, type) }
- end
-
- def cast_returns(api_method, return_value) # :nodoc:
- return nil if api_method.returns.nil?
- cast(return_value, api_method.returns[0])
- end
-
- def cast(value, signature_type) # :nodoc:
- return value if signature_type.nil? # signature.length != params.length
- return nil if value.nil?
- # XMLRPC protocol doesn't support nil values. It uses false instead.
- # It should never happen for SOAP.
- if signature_type.structured? && value.equal?(false)
- return nil
- end
- unless signature_type.array? || signature_type.structured?
- return value if canonical_type(value.class) == signature_type.type
- end
- if signature_type.array?
- unless value.respond_to?(:entries) && !value.is_a?(String)
- raise CastingError, "Don't know how to cast #{value.class} into #{signature_type.type.inspect}"
- end
- value.entries.map do |entry|
- cast(entry, signature_type.element_type)
- end
- elsif signature_type.structured?
- cast_to_structured_type(value, signature_type)
- elsif !signature_type.custom?
- cast_base_type(value, signature_type)
- end
- end
-
- def cast_base_type(value, signature_type) # :nodoc:
- # This is a work-around for the fact that XML-RPC special-cases DateTime values into its own DateTime type
- # in order to support iso8601 dates. This doesn't work too well for us, so we'll convert it into a Time,
- # with the caveat that we won't be able to handle pre-1970 dates that are sent to us.
- #
- # See http://dev.rubyonrails.com/ticket/2516
- value = value.to_time if value.is_a?(XMLRPC::DateTime)
-
- case signature_type.type
- when :int
- Integer(value)
- when :string
- value.to_s
- when :base64
- if value.is_a?(ActionWebService::Base64)
- value
- else
- ActionWebService::Base64.new(value.to_s)
- end
- when :bool
- return false if value.nil?
- return value if value == true || value == false
- case value.to_s.downcase
- when '1', 'true', 'y', 'yes'
- true
- when '0', 'false', 'n', 'no'
- false
- else
- raise CastingError, "Don't know how to cast #{value.class} into Boolean"
- end
- when :float
- Float(value)
- when :decimal
- BigDecimal(value.to_s)
- when :time
- value = "%s/%s/%s %s:%s:%s" % value.values_at(*%w[2 3 1 4 5 6]) if value.kind_of?(Hash)
- value.kind_of?(Time) ? value : Time.parse(value.to_s)
- when :date
- value = "%s/%s/%s" % value.values_at(*%w[2 3 1]) if value.kind_of?(Hash)
- value.kind_of?(Date) ? value : Date.parse(value.to_s)
- when :datetime
- value = "%s/%s/%s %s:%s:%s" % value.values_at(*%w[2 3 1 4 5 6]) if value.kind_of?(Hash)
- value.kind_of?(DateTime) ? value : DateTime.parse(value.to_s)
- end
- end
-
- def cast_to_structured_type(value, signature_type) # :nodoc:
- obj = nil
- obj = value if canonical_type(value.class) == canonical_type(signature_type.type)
- obj ||= signature_type.type_class.new
- if value.respond_to?(:each_pair)
- klass = signature_type.type_class
- value.each_pair do |name, val|
- type = klass.respond_to?(:member_type) ? klass.member_type(name) : nil
- val = cast(val, type) if type
- # See http://dev.rubyonrails.com/ticket/3567
- val = val.to_time if val.is_a?(XMLRPC::DateTime)
- obj.__send__("#{name}=", val) if obj.respond_to?(name)
- end
- elsif value.respond_to?(:attributes)
- signature_type.each_member do |name, type|
- val = value.__send__(name)
- obj.__send__("#{name}=", cast(val, type)) if obj.respond_to?(name)
- end
- else
- raise CastingError, "Don't know how to cast #{value.class} to #{signature_type.type_class}"
- end
- obj
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/client.rb b/vendor/plugins/actionwebservice/lib/action_web_service/client.rb
deleted file mode 100644
index 2a1e33054..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/client.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require 'action_web_service/client/base'
-require 'action_web_service/client/soap_client'
-require 'action_web_service/client/xmlrpc_client'
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/client/base.rb b/vendor/plugins/actionwebservice/lib/action_web_service/client/base.rb
deleted file mode 100644
index 9dada7bf9..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/client/base.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module ActionWebService # :nodoc:
- module Client # :nodoc:
- class ClientError < StandardError # :nodoc:
- end
-
- class Base # :nodoc:
- def initialize(api, endpoint_uri)
- @api = api
- @endpoint_uri = endpoint_uri
- end
-
- def method_missing(name, *args) # :nodoc:
- call_name = method_name(name)
- return super(name, *args) if call_name.nil?
- self.perform_invocation(call_name, args)
- end
-
- private
- def method_name(name)
- if @api.has_api_method?(name.to_sym)
- name.to_s
- elsif @api.has_public_api_method?(name.to_s)
- @api.api_method_name(name.to_s).to_s
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/client/soap_client.rb b/vendor/plugins/actionwebservice/lib/action_web_service/client/soap_client.rb
deleted file mode 100644
index ebabd8ea8..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/client/soap_client.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-require 'soap/rpc/driver'
-require 'uri'
-
-module ActionWebService # :nodoc:
- module Client # :nodoc:
-
- # Implements SOAP client support (using RPC encoding for the messages).
- #
- # ==== Example Usage
- #
- # class PersonAPI < ActionWebService::API::Base
- # api_method :find_all, :returns => [[Person]]
- # end
- #
- # soap_client = ActionWebService::Client::Soap.new(PersonAPI, "http://...")
- # persons = soap_client.find_all
- #
- class Soap < Base
- # provides access to the underlying soap driver
- attr_reader :driver
-
- # Creates a new web service client using the SOAP RPC protocol.
- #
- # +api+ must be an ActionWebService::API::Base derivative, and
- # +endpoint_uri+ must point at the relevant URL to which protocol requests
- # will be sent with HTTP POST.
- #
- # Valid options:
- # [<tt>:namespace</tt>] If the remote server has used a custom namespace to
- # declare its custom types, you can specify it here. This would
- # be the namespace declared with a [WebService(Namespace = "http://namespace")] attribute
- # in .NET, for example.
- # [<tt>:driver_options</tt>] If you want to supply any custom SOAP RPC driver
- # options, you can provide them as a Hash here
- #
- # The <tt>:driver_options</tt> option can be used to configure the backend SOAP
- # RPC driver. An example of configuring the SOAP backend to do
- # client-certificate authenticated SSL connections to the server:
- #
- # opts = {}
- # opts['protocol.http.ssl_config.verify_mode'] = 'OpenSSL::SSL::VERIFY_PEER'
- # opts['protocol.http.ssl_config.client_cert'] = client_cert_file_path
- # opts['protocol.http.ssl_config.client_key'] = client_key_file_path
- # opts['protocol.http.ssl_config.ca_file'] = ca_cert_file_path
- # client = ActionWebService::Client::Soap.new(api, 'https://some/service', :driver_options => opts)
- def initialize(api, endpoint_uri, options={})
- super(api, endpoint_uri)
- @namespace = options[:namespace] || 'urn:ActionWebService'
- @driver_options = options[:driver_options] || {}
- @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new @namespace
- @soap_action_base = options[:soap_action_base]
- @soap_action_base ||= URI.parse(endpoint_uri).path
- @driver = create_soap_rpc_driver(api, endpoint_uri)
- @driver_options.each do |name, value|
- @driver.options[name.to_s] = value
- end
- end
-
- protected
- def perform_invocation(method_name, args)
- method = @api.api_methods[method_name.to_sym]
- args = method.cast_expects(args.dup) rescue args
- return_value = @driver.send(method_name, *args)
- method.cast_returns(return_value.dup) rescue return_value
- end
-
- def soap_action(method_name)
- "#{@soap_action_base}/#{method_name}"
- end
-
- private
- def create_soap_rpc_driver(api, endpoint_uri)
- @protocol.register_api(api)
- driver = SoapDriver.new(endpoint_uri, nil)
- driver.mapping_registry = @protocol.marshaler.registry
- api.api_methods.each do |name, method|
- qname = XSD::QName.new(@namespace, method.public_name)
- action = soap_action(method.public_name)
- expects = method.expects
- returns = method.returns
- param_def = []
- if expects
- expects.each do |type|
- type_binding = @protocol.marshaler.lookup_type(type)
- if SOAP::Version >= "1.5.5"
- param_def << ['in', type.name.to_s, [type_binding.type.type_class.to_s]]
- else
- param_def << ['in', type.name, type_binding.mapping]
- end
- end
- end
- if returns
- type_binding = @protocol.marshaler.lookup_type(returns[0])
- if SOAP::Version >= "1.5.5"
- param_def << ['retval', 'return', [type_binding.type.type_class.to_s]]
- else
- param_def << ['retval', 'return', type_binding.mapping]
- end
- end
- driver.add_method(qname, action, method.name.to_s, param_def)
- end
- driver
- end
-
- class SoapDriver < SOAP::RPC::Driver # :nodoc:
- def add_method(qname, soapaction, name, param_def)
- @proxy.add_rpc_method(qname, soapaction, name, param_def)
- add_rpc_method_interface(name, param_def)
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb b/vendor/plugins/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb
deleted file mode 100644
index 42b5c5d4f..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-require 'uri'
-require 'xmlrpc/client'
-
-module ActionWebService # :nodoc:
- module Client # :nodoc:
-
- # Implements XML-RPC client support
- #
- # ==== Example Usage
- #
- # class BloggerAPI < ActionWebService::API::Base
- # inflect_names false
- # api_method :getRecentPosts, :returns => [[Blog::Post]]
- # end
- #
- # blog = ActionWebService::Client::XmlRpc.new(BloggerAPI, "http://.../RPC", :handler_name => "blogger")
- # posts = blog.getRecentPosts
- class XmlRpc < Base
-
- # Creates a new web service client using the XML-RPC protocol.
- #
- # +api+ must be an ActionWebService::API::Base derivative, and
- # +endpoint_uri+ must point at the relevant URL to which protocol requests
- # will be sent with HTTP POST.
- #
- # Valid options:
- # [<tt>:handler_name</tt>] If the remote server defines its services inside special
- # handler (the Blogger API uses a <tt>"blogger"</tt> handler name for example),
- # provide it here, or your method calls will fail
- def initialize(api, endpoint_uri, options={})
- @api = api
- @handler_name = options[:handler_name]
- @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new
- @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout])
- end
-
- protected
- def perform_invocation(method_name, args)
- method = @api.api_methods[method_name.to_sym]
- if method.expects && method.expects.length != args.length
- raise(ArgumentError, "#{method.public_name}: wrong number of arguments (#{args.length} for #{method.expects.length})")
- end
- args = method.cast_expects(args.dup) rescue args
- if method.expects
- method.expects.each_with_index{ |type, i| args[i] = @protocol.value_to_xmlrpc_wire_format(args[i], type) }
- end
- ok, return_value = @client.call2(public_name(method_name), *args)
- return (method.cast_returns(return_value.dup) rescue return_value) if ok
- raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}")
- end
-
- def public_name(method_name)
- public_name = @api.public_api_method_name(method_name)
- @handler_name ? "#{@handler_name}.#{public_name}" : public_name
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/container.rb b/vendor/plugins/actionwebservice/lib/action_web_service/container.rb
deleted file mode 100644
index 13d9d8ab5..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/container.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require 'action_web_service/container/direct_container'
-require 'action_web_service/container/delegated_container'
-require 'action_web_service/container/action_controller_container'
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/container/action_controller_container.rb b/vendor/plugins/actionwebservice/lib/action_web_service/container/action_controller_container.rb
deleted file mode 100644
index bbc28083c..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/container/action_controller_container.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-module ActionWebService # :nodoc:
- module Container # :nodoc:
- module ActionController # :nodoc:
- def self.included(base) # :nodoc:
- class << base
- include ClassMethods
- alias_method_chain :inherited, :api
- alias_method_chain :web_service_api, :require
- end
- end
-
- module ClassMethods
- # Creates a client for accessing remote web services, using the
- # given +protocol+ to communicate with the +endpoint_uri+.
- #
- # ==== Example
- #
- # class MyController < ActionController::Base
- # web_client_api :blogger, :xmlrpc, "http://blogger.com/myblog/api/RPC2", :handler_name => 'blogger'
- # end
- #
- # In this example, a protected method named <tt>blogger</tt> will
- # now exist on the controller, and calling it will return the
- # XML-RPC client object for working with that remote service.
- #
- # +options+ is the set of protocol client specific options (see
- # a protocol client class for details).
- #
- # If your API definition does not exist on the load path with the
- # correct rules for it to be found using +name+, you can pass in
- # the API definition class via +options+, using a key of <tt>:api</tt>
- def web_client_api(name, protocol, endpoint_uri, options={})
- unless method_defined?(name)
- api_klass = options.delete(:api) || require_web_service_api(name)
- class_eval do
- define_method(name) do
- create_web_service_client(api_klass, protocol, endpoint_uri, options)
- end
- protected name
- end
- end
- end
-
- def web_service_api_with_require(definition=nil) # :nodoc:
- return web_service_api_without_require if definition.nil?
- case definition
- when String, Symbol
- klass = require_web_service_api(definition)
- else
- klass = definition
- end
- web_service_api_without_require(klass)
- end
-
- def require_web_service_api(name) # :nodoc:
- case name
- when String, Symbol
- file_name = name.to_s.underscore + "_api"
- class_name = file_name.camelize
- class_names = [class_name, class_name.sub(/Api$/, 'API')]
- begin
- require_dependency(file_name)
- rescue LoadError => load_error
- requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
- msg = requiree == file_name ? "Missing API definition file in apis/#{file_name}.rb" : "Can't load file: #{requiree}"
- raise LoadError.new(msg).copy_blame!(load_error)
- end
- klass = nil
- class_names.each do |name|
- klass = name.constantize rescue nil
- break unless klass.nil?
- end
- unless klass
- raise(NameError, "neither #{class_names[0]} or #{class_names[1]} found")
- end
- klass
- else
- raise(ArgumentError, "expected String or Symbol argument")
- end
- end
-
- private
- def inherited_with_api(child)
- inherited_without_api(child)
- begin child.web_service_api(child.controller_path)
- rescue MissingSourceFile => e
- raise unless e.is_missing?("apis/#{child.controller_path}_api")
- end
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/container/delegated_container.rb b/vendor/plugins/actionwebservice/lib/action_web_service/container/delegated_container.rb
deleted file mode 100644
index 5477f8d10..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/container/delegated_container.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-module ActionWebService # :nodoc:
- module Container # :nodoc:
- module Delegated # :nodoc:
- class ContainerError < ActionWebServiceError # :nodoc:
- end
-
- def self.included(base) # :nodoc:
- base.extend(ClassMethods)
- base.send(:include, ActionWebService::Container::Delegated::InstanceMethods)
- end
-
- module ClassMethods
- # Declares a web service that will provide access to the API of the given
- # +object+. +object+ must be an ActionWebService::Base derivative.
- #
- # Web service object creation can either be _immediate_, where the object
- # instance is given at class definition time, or _deferred_, where
- # object instantiation is delayed until request time.
- #
- # ==== Immediate web service object example
- #
- # class ApiController < ApplicationController
- # web_service_dispatching_mode :delegated
- #
- # web_service :person, PersonService.new
- # end
- #
- # For deferred instantiation, a block should be given instead of an
- # object instance. This block will be executed in controller instance
- # context, so it can rely on controller instance variables being present.
- #
- # ==== Deferred web service object example
- #
- # class ApiController < ApplicationController
- # web_service_dispatching_mode :delegated
- #
- # web_service(:person) { PersonService.new(request.env) }
- # end
- def web_service(name, object=nil, &block)
- if (object && block_given?) || (object.nil? && block.nil?)
- raise(ContainerError, "either service, or a block must be given")
- end
- name = name.to_sym
- if block_given?
- info = { name => { :block => block } }
- else
- info = { name => { :object => object } }
- end
- write_inheritable_hash("web_services", info)
- call_web_service_definition_callbacks(self, name, info)
- end
-
- # Whether this service contains a service with the given +name+
- def has_web_service?(name)
- web_services.has_key?(name.to_sym)
- end
-
- def web_services # :nodoc:
- read_inheritable_attribute("web_services") || {}
- end
-
- def add_web_service_definition_callback(&block) # :nodoc:
- write_inheritable_array("web_service_definition_callbacks", [block])
- end
-
- private
- def call_web_service_definition_callbacks(container_class, web_service_name, service_info)
- (read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block|
- block.call(container_class, web_service_name, service_info)
- end
- end
- end
-
- module InstanceMethods # :nodoc:
- def web_service_object(web_service_name)
- info = self.class.web_services[web_service_name.to_sym]
- unless info
- raise(ContainerError, "no such web service '#{web_service_name}'")
- end
- service = info[:block]
- service ? self.instance_eval(&service) : info[:object]
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/container/direct_container.rb b/vendor/plugins/actionwebservice/lib/action_web_service/container/direct_container.rb
deleted file mode 100644
index 8818d8f45..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/container/direct_container.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-module ActionWebService # :nodoc:
- module Container # :nodoc:
- module Direct # :nodoc:
- class ContainerError < ActionWebServiceError # :nodoc:
- end
-
- def self.included(base) # :nodoc:
- base.extend(ClassMethods)
- end
-
- module ClassMethods
- # Attaches ActionWebService API +definition+ to the calling class.
- #
- # Action Controllers can have a default associated API, removing the need
- # to call this method if you follow the Action Web Service naming conventions.
- #
- # A controller with a class name of GoogleSearchController will
- # implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the
- # API definition class to be named <tt>GoogleSearchAPI</tt> or
- # <tt>GoogleSearchApi</tt>.
- #
- # ==== Service class example
- #
- # class MyService < ActionWebService::Base
- # web_service_api MyAPI
- # end
- #
- # class MyAPI < ActionWebService::API::Base
- # ...
- # end
- #
- # ==== Controller class example
- #
- # class MyController < ActionController::Base
- # web_service_api MyAPI
- # end
- #
- # class MyAPI < ActionWebService::API::Base
- # ...
- # end
- def web_service_api(definition=nil)
- if definition.nil?
- read_inheritable_attribute("web_service_api")
- else
- if definition.is_a?(Symbol)
- raise(ContainerError, "symbols can only be used for #web_service_api inside of a controller")
- end
- unless definition.respond_to?(:ancestors) && definition.ancestors.include?(ActionWebService::API::Base)
- raise(ContainerError, "#{definition.to_s} is not a valid API definition")
- end
- write_inheritable_attribute("web_service_api", definition)
- call_web_service_api_callbacks(self, definition)
- end
- end
-
- def add_web_service_api_callback(&block) # :nodoc:
- write_inheritable_array("web_service_api_callbacks", [block])
- end
-
- private
- def call_web_service_api_callbacks(container_class, definition)
- (read_inheritable_attribute("web_service_api_callbacks") || []).each do |block|
- block.call(container_class, definition)
- end
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher.rb b/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher.rb
deleted file mode 100644
index 601d83137..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-require 'action_web_service/dispatcher/abstract'
-require 'action_web_service/dispatcher/action_controller_dispatcher'
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/abstract.rb b/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/abstract.rb
deleted file mode 100644
index cb94d649e..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/abstract.rb
+++ /dev/null
@@ -1,207 +0,0 @@
-require 'benchmark'
-
-module ActionWebService # :nodoc:
- module Dispatcher # :nodoc:
- class DispatcherError < ActionWebService::ActionWebServiceError # :nodoc:
- def initialize(*args)
- super
- set_backtrace(caller)
- end
- end
-
- def self.included(base) # :nodoc:
- base.class_inheritable_option(:web_service_dispatching_mode, :direct)
- base.class_inheritable_option(:web_service_exception_reporting, true)
- base.send(:include, ActionWebService::Dispatcher::InstanceMethods)
- end
-
- module InstanceMethods # :nodoc:
- private
- def invoke_web_service_request(protocol_request)
- invocation = web_service_invocation(protocol_request)
- if invocation.is_a?(Array) && protocol_request.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol)
- xmlrpc_multicall_invoke(invocation)
- else
- web_service_invoke(invocation)
- end
- end
-
- def web_service_direct_invoke(invocation)
- @method_params = invocation.method_ordered_params
- arity = method(invocation.api_method.name).arity rescue 0
- if arity < 0 || arity > 0
- params = @method_params
- else
- params = []
- end
- web_service_filtered_invoke(invocation, params)
- end
-
- def web_service_delegated_invoke(invocation)
- web_service_filtered_invoke(invocation, invocation.method_ordered_params)
- end
-
- def web_service_filtered_invoke(invocation, params)
- cancellation_reason = nil
- return_value = invocation.service.perform_invocation(invocation.api_method.name, params) do |x|
- cancellation_reason = x
- end
- if cancellation_reason
- raise(DispatcherError, "request canceled: #{cancellation_reason}")
- end
- return_value
- end
-
- def web_service_invoke(invocation)
- case web_service_dispatching_mode
- when :direct
- return_value = web_service_direct_invoke(invocation)
- when :delegated, :layered
- return_value = web_service_delegated_invoke(invocation)
- end
- web_service_create_response(invocation.protocol, invocation.protocol_options, invocation.api, invocation.api_method, return_value)
- end
-
- def xmlrpc_multicall_invoke(invocations)
- responses = []
- invocations.each do |invocation|
- if invocation.is_a?(Hash)
- responses << [invocation, nil]
- next
- end
- begin
- case web_service_dispatching_mode
- when :direct
- return_value = web_service_direct_invoke(invocation)
- when :delegated, :layered
- return_value = web_service_delegated_invoke(invocation)
- end
- api_method = invocation.api_method
- if invocation.api.has_api_method?(api_method.name)
- response_type = (api_method.returns ? api_method.returns[0] : nil)
- return_value = api_method.cast_returns(return_value)
- else
- response_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
- end
- responses << [return_value, response_type]
- rescue Exception => e
- responses << [{ 'faultCode' => 3, 'faultString' => e.message }, nil]
- end
- end
- invocation = invocations[0]
- invocation.protocol.encode_multicall_response(responses, invocation.protocol_options)
- end
-
- def web_service_invocation(request, level = 0)
- public_method_name = request.method_name
- invocation = Invocation.new
- invocation.protocol = request.protocol
- invocation.protocol_options = request.protocol_options
- invocation.service_name = request.service_name
- if web_service_dispatching_mode == :layered
- case invocation.protocol
- when Protocol::Soap::SoapProtocol
- soap_action = request.protocol_options[:soap_action]
- if soap_action && soap_action =~ /^\/\w+\/(\w+)\//
- invocation.service_name = $1
- end
- when Protocol::XmlRpc::XmlRpcProtocol
- if request.method_name =~ /^([^\.]+)\.(.*)$/
- public_method_name = $2
- invocation.service_name = $1
- end
- end
- end
- if invocation.protocol.is_a? Protocol::XmlRpc::XmlRpcProtocol
- if public_method_name == 'multicall' && invocation.service_name == 'system'
- if level > 0
- raise(DispatcherError, "Recursive system.multicall invocations not allowed")
- end
- multicall = request.method_params.dup
- unless multicall.is_a?(Array) && multicall[0].is_a?(Array)
- raise(DispatcherError, "Malformed multicall (expected array of Hash elements)")
- end
- multicall = multicall[0]
- return multicall.map do |item|
- raise(DispatcherError, "Multicall elements must be Hash") unless item.is_a?(Hash)
- raise(DispatcherError, "Multicall elements must contain a 'methodName' key") unless item.has_key?('methodName')
- method_name = item['methodName']
- params = item.has_key?('params') ? item['params'] : []
- multicall_request = request.dup
- multicall_request.method_name = method_name
- multicall_request.method_params = params
- begin
- web_service_invocation(multicall_request, level + 1)
- rescue Exception => e
- {'faultCode' => 4, 'faultMessage' => e.message}
- end
- end
- end
- end
- case web_service_dispatching_mode
- when :direct
- invocation.api = self.class.web_service_api
- invocation.service = self
- when :delegated, :layered
- invocation.service = web_service_object(invocation.service_name)
- invocation.api = invocation.service.class.web_service_api
- end
- if invocation.api.nil?
- raise(DispatcherError, "no API attached to #{invocation.service.class}")
- end
- invocation.protocol.register_api(invocation.api)
- request.api = invocation.api
- if invocation.api.has_public_api_method?(public_method_name)
- invocation.api_method = invocation.api.public_api_method_instance(public_method_name)
- else
- if invocation.api.default_api_method.nil?
- raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}")
- else
- invocation.api_method = invocation.api.default_api_method_instance
- end
- end
- if invocation.service.nil?
- raise(DispatcherError, "no service available for service name #{invocation.service_name}")
- end
- unless invocation.service.respond_to?(invocation.api_method.name)
- raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})")
- end
- request.api_method = invocation.api_method
- begin
- invocation.method_ordered_params = invocation.api_method.cast_expects(request.method_params.dup)
- rescue
- logger.warn "Casting of method parameters failed" unless logger.nil?
- invocation.method_ordered_params = request.method_params
- end
- request.method_params = invocation.method_ordered_params
- invocation.method_named_params = {}
- invocation.api_method.param_names.inject(0) do |m, n|
- invocation.method_named_params[n] = invocation.method_ordered_params[m]
- m + 1
- end
- invocation
- end
-
- def web_service_create_response(protocol, protocol_options, api, api_method, return_value)
- if api.has_api_method?(api_method.name)
- return_type = api_method.returns ? api_method.returns[0] : nil
- return_value = api_method.cast_returns(return_value)
- else
- return_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
- end
- protocol.encode_response(api_method.public_name + 'Response', return_value, return_type, protocol_options)
- end
-
- class Invocation # :nodoc:
- attr_accessor :protocol
- attr_accessor :protocol_options
- attr_accessor :service_name
- attr_accessor :api
- attr_accessor :api_method
- attr_accessor :method_ordered_params
- attr_accessor :method_named_params
- attr_accessor :service
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb b/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
deleted file mode 100644
index f9995197a..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
+++ /dev/null
@@ -1,379 +0,0 @@
-require 'benchmark'
-require 'builder/xmlmarkup'
-
-module ActionWebService # :nodoc:
- module Dispatcher # :nodoc:
- module ActionController # :nodoc:
- def self.included(base) # :nodoc:
- class << base
- include ClassMethods
- alias_method_chain :inherited, :action_controller
- end
- base.class_eval do
- alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke
- end
- base.add_web_service_api_callback do |klass, api|
- if klass.web_service_dispatching_mode == :direct
- klass.class_eval 'def api; dispatch_web_service_request; end'
- end
- end
- base.add_web_service_definition_callback do |klass, name, info|
- if klass.web_service_dispatching_mode == :delegated
- klass.class_eval "def #{name}; dispatch_web_service_request; end"
- elsif klass.web_service_dispatching_mode == :layered
- klass.class_eval 'def api; dispatch_web_service_request; end'
- end
- end
- base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods)
- end
-
- module ClassMethods # :nodoc:
- def inherited_with_action_controller(child)
- inherited_without_action_controller(child)
- child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction)
- end
- end
-
- module InstanceMethods # :nodoc:
- private
- def dispatch_web_service_request
- method = request.method.to_s.upcase
- allowed_methods = self.class.web_service_api ? (self.class.web_service_api.allowed_http_methods || []) : [ :post ]
- allowed_methods = allowed_methods.map{|m| m.to_s.upcase }
- if !allowed_methods.include?(method)
- render :text => "#{method} not supported", :status=>500
- return
- end
- exception = nil
- begin
- ws_request = discover_web_service_request(request)
- rescue Exception => e
- exception = e
- end
- if ws_request
- ws_response = nil
- exception = nil
- bm = Benchmark.measure do
- begin
- ws_response = invoke_web_service_request(ws_request)
- rescue Exception => e
- exception = e
- end
- end
- log_request(ws_request, request.raw_post)
- if exception
- log_error(exception) unless logger.nil?
- send_web_service_error_response(ws_request, exception)
- else
- send_web_service_response(ws_response, bm.real)
- end
- else
- exception ||= DispatcherError.new("Malformed SOAP or XML-RPC protocol message")
- log_error(exception) unless logger.nil?
- send_web_service_error_response(ws_request, exception)
- end
- rescue Exception => e
- log_error(e) unless logger.nil?
- send_web_service_error_response(ws_request, e)
- end
-
- def send_web_service_response(ws_response, elapsed=nil)
- log_response(ws_response, elapsed)
- options = { :type => ws_response.content_type, :disposition => 'inline' }
- send_data(ws_response.body, options)
- end
-
- def send_web_service_error_response(ws_request, exception)
- if ws_request
- unless self.class.web_service_exception_reporting
- exception = DispatcherError.new("Internal server error (exception raised)")
- end
- api_method = ws_request.api_method
- public_method_name = api_method ? api_method.public_name : ws_request.method_name
- return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0)
- ws_response = ws_request.protocol.encode_response(public_method_name + 'Response', exception, return_type, ws_request.protocol_options)
- send_web_service_response(ws_response)
- else
- if self.class.web_service_exception_reporting
- message = exception.message
- backtrace = "\nBacktrace:\n#{exception.backtrace.join("\n")}"
- else
- message = "Exception raised"
- backtrace = ""
- end
- render :text => "Internal protocol error: #{message}#{backtrace}", :status => 500
- end
- end
-
- def web_service_direct_invoke(invocation)
- invocation.method_named_params.each do |name, value|
- params[name] = value
- end
- web_service_direct_invoke_without_controller(invocation)
- end
-
- def log_request(ws_request, body)
- unless logger.nil?
- name = ws_request.method_name
- api_method = ws_request.api_method
- params = ws_request.method_params
- if api_method && api_method.expects
- params = api_method.expects.zip(params).map{ |type, param| "#{type.name}=>#{param.inspect}" }
- else
- params = params.map{ |param| param.inspect }
- end
- service = ws_request.service_name
- logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}")
- logger.debug(indent(body))
- end
- end
-
- def log_response(ws_response, elapsed=nil)
- unless logger.nil?
- elapsed = (elapsed ? " (%f):" % elapsed : ":")
- logger.debug("\nWeb Service Response" + elapsed + " => #{ws_response.return_value.inspect}")
- logger.debug(indent(ws_response.body))
- end
- end
-
- def indent(body)
- body.split(/\n/).map{|x| " #{x}"}.join("\n")
- end
- end
-
- module WsdlAction # :nodoc:
- XsdNs = 'http://www.w3.org/2001/XMLSchema'
- WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
- SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
- SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/'
- SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'
-
- def wsdl
- case request.method
- when :get
- begin
- options = { :type => 'text/xml', :disposition => 'inline' }
- send_data(to_wsdl, options)
- rescue Exception => e
- log_error(e) unless logger.nil?
- end
- when :post
- render :text => 'POST not supported', :status => 500
- end
- end
-
- private
- def base_uri
- host = request.host_with_port
- relative_url_root = request.relative_url_root
- scheme = request.ssl? ? 'https' : 'http'
- '%s://%s%s/%s/' % [scheme, host, relative_url_root, self.class.controller_path]
- end
-
- def to_wsdl
- xml = ''
- dispatching_mode = web_service_dispatching_mode
- global_service_name = wsdl_service_name
- namespace = wsdl_namespace || 'urn:ActionWebService'
- soap_action_base = "/#{controller_name}"
-
- marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace)
- apis = {}
- case dispatching_mode
- when :direct
- api = self.class.web_service_api
- web_service_name = controller_class_name.sub(/Controller$/, '').underscore
- apis[web_service_name] = [api, register_api(api, marshaler)]
- when :delegated, :layered
- self.class.web_services.each do |web_service_name, info|
- service = web_service_object(web_service_name)
- api = service.class.web_service_api
- apis[web_service_name] = [api, register_api(api, marshaler)]
- end
- end
- custom_types = []
- apis.values.each do |api, bindings|
- bindings.each do |b|
- custom_types << b unless custom_types.include?(b)
- end
- end
-
- xm = Builder::XmlMarkup.new(:target => xml, :indent => 2)
- xm.instruct!
- xm.definitions('name' => wsdl_service_name,
- 'targetNamespace' => namespace,
- 'xmlns:typens' => namespace,
- 'xmlns:xsd' => XsdNs,
- 'xmlns:soap' => SoapNs,
- 'xmlns:soapenc' => SoapEncodingNs,
- 'xmlns:wsdl' => WsdlNs,
- 'xmlns' => WsdlNs) do
- # Generate XSD
- if custom_types.size > 0
- xm.types do
- xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
- custom_types.each do |binding|
- case
- when binding.type.array?
- xm.xsd(:complexType, 'name' => binding.type_name) do
- xm.xsd(:complexContent) do
- xm.xsd(:restriction, 'base' => 'soapenc:Array') do
- xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
- 'wsdl:arrayType' => binding.element_binding.qualified_type_name('typens') + '[]')
- end
- end
- end
- when binding.type.structured?
- xm.xsd(:complexType, 'name' => binding.type_name) do
- xm.xsd(:all) do
- binding.type.each_member do |name, type|
- b = marshaler.register_type(type)
- xm.xsd(:element, 'name' => name, 'type' => b.qualified_type_name('typens'))
- end
- end
- end
- end
- end
- end
- end
- end
-
- # APIs
- apis.each do |api_name, values|
- api = values[0]
- api.api_methods.each do |name, method|
- gen = lambda do |msg_name, direction|
- xm.message('name' => message_name_for(api_name, msg_name)) do
- sym = nil
- if direction == :out
- returns = method.returns
- if returns
- binding = marshaler.register_type(returns[0])
- xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens'))
- end
- else
- expects = method.expects
- expects.each do |type|
- binding = marshaler.register_type(type)
- xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens'))
- end if expects
- end
- end
- end
- public_name = method.public_name
- gen.call(public_name, :in)
- gen.call("#{public_name}Response", :out)
- end
-
- # Port
- port_name = port_name_for(global_service_name, api_name)
- xm.portType('name' => port_name) do
- api.api_methods.each do |name, method|
- xm.operation('name' => method.public_name) do
- xm.input('message' => "typens:" + message_name_for(api_name, method.public_name))
- xm.output('message' => "typens:" + message_name_for(api_name, "#{method.public_name}Response"))
- end
- end
- end
-
- # Bind it
- binding_name = binding_name_for(global_service_name, api_name)
- xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
- xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
- api.api_methods.each do |name, method|
- xm.operation('name' => method.public_name) do
- case web_service_dispatching_mode
- when :direct
- soap_action = soap_action_base + "/api/" + method.public_name
- when :delegated, :layered
- soap_action = soap_action_base \
- + "/" + api_name.to_s \
- + "/" + method.public_name
- end
- xm.soap(:operation, 'soapAction' => soap_action)
- xm.input do
- xm.soap(:body,
- 'use' => 'encoded',
- 'namespace' => namespace,
- 'encodingStyle' => SoapEncodingNs)
- end
- xm.output do
- xm.soap(:body,
- 'use' => 'encoded',
- 'namespace' => namespace,
- 'encodingStyle' => SoapEncodingNs)
- end
- end
- end
- end
- end
-
- # Define it
- xm.service('name' => "#{global_service_name}Service") do
- apis.each do |api_name, values|
- port_name = port_name_for(global_service_name, api_name)
- binding_name = binding_name_for(global_service_name, api_name)
- case web_service_dispatching_mode
- when :direct, :layered
- binding_target = 'api'
- when :delegated
- binding_target = api_name.to_s
- end
- xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
- xm.soap(:address, 'location' => "#{base_uri}#{binding_target}")
- end
- end
- end
- end
- end
-
- def port_name_for(global_service, service)
- "#{global_service}#{service.to_s.camelize}Port"
- end
-
- def binding_name_for(global_service, service)
- "#{global_service}#{service.to_s.camelize}Binding"
- end
-
- def message_name_for(api_name, message_name)
- mode = web_service_dispatching_mode
- if mode == :layered || mode == :delegated
- api_name.to_s + '-' + message_name
- else
- message_name
- end
- end
-
- def register_api(api, marshaler)
- bindings = {}
- traverse_custom_types(api, marshaler, bindings) do |binding|
- bindings[binding] = nil unless bindings.has_key?(binding)
- element_binding = binding.element_binding
- bindings[element_binding] = nil if element_binding && !bindings.has_key?(element_binding)
- end
- bindings.keys
- end
-
- def traverse_custom_types(api, marshaler, bindings, &block)
- api.api_methods.each do |name, method|
- expects, returns = method.expects, method.returns
- expects.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if expects
- returns.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if returns
- end
- end
-
- def traverse_type(marshaler, type, bindings, &block)
- binding = marshaler.register_type(type)
- return if bindings.has_key?(binding)
- bindings[binding] = nil
- yield binding
- if type.array?
- yield marshaler.register_type(type.element_type)
- type = type.element_type
- end
- type.each_member{ |name, type| traverse_type(marshaler, type, bindings, &block) } if type.structured?
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/invocation.rb b/vendor/plugins/actionwebservice/lib/action_web_service/invocation.rb
deleted file mode 100644
index 2a9121ee2..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/invocation.rb
+++ /dev/null
@@ -1,202 +0,0 @@
-module ActionWebService # :nodoc:
- module Invocation # :nodoc:
- class InvocationError < ActionWebService::ActionWebServiceError # :nodoc:
- end
-
- def self.included(base) # :nodoc:
- base.extend(ClassMethods)
- base.send(:include, ActionWebService::Invocation::InstanceMethods)
- end
-
- # Invocation interceptors provide a means to execute custom code before
- # and after method invocations on ActionWebService::Base objects.
- #
- # When running in _Direct_ dispatching mode, ActionController filters
- # should be used for this functionality instead.
- #
- # The semantics of invocation interceptors are the same as ActionController
- # filters, and accept the same parameters and options.
- #
- # A _before_ interceptor can also cancel execution by returning +false+,
- # or returning a <tt>[false, "cancel reason"]</tt> array if it wishes to supply
- # a reason for canceling the request.
- #
- # === Example
- #
- # class CustomService < ActionWebService::Base
- # before_invocation :intercept_add, :only => [:add]
- #
- # def add(a, b)
- # a + b
- # end
- #
- # private
- # def intercept_add
- # return [false, "permission denied"] # cancel it
- # end
- # end
- #
- # Options:
- # [<tt>:except</tt>] A list of methods for which the interceptor will NOT be called
- # [<tt>:only</tt>] A list of methods for which the interceptor WILL be called
- module ClassMethods
- # Appends the given +interceptors+ to be called
- # _before_ method invocation.
- def append_before_invocation(*interceptors, &block)
- conditions = extract_conditions!(interceptors)
- interceptors << block if block_given?
- add_interception_conditions(interceptors, conditions)
- append_interceptors_to_chain("before", interceptors)
- end
-
- # Prepends the given +interceptors+ to be called
- # _before_ method invocation.
- def prepend_before_invocation(*interceptors, &block)
- conditions = extract_conditions!(interceptors)
- interceptors << block if block_given?
- add_interception_conditions(interceptors, conditions)
- prepend_interceptors_to_chain("before", interceptors)
- end
-
- alias :before_invocation :append_before_invocation
-
- # Appends the given +interceptors+ to be called
- # _after_ method invocation.
- def append_after_invocation(*interceptors, &block)
- conditions = extract_conditions!(interceptors)
- interceptors << block if block_given?
- add_interception_conditions(interceptors, conditions)
- append_interceptors_to_chain("after", interceptors)
- end
-
- # Prepends the given +interceptors+ to be called
- # _after_ method invocation.
- def prepend_after_invocation(*interceptors, &block)
- conditions = extract_conditions!(interceptors)
- interceptors << block if block_given?
- add_interception_conditions(interceptors, conditions)
- prepend_interceptors_to_chain("after", interceptors)
- end
-
- alias :after_invocation :append_after_invocation
-
- def before_invocation_interceptors # :nodoc:
- read_inheritable_attribute("before_invocation_interceptors")
- end
-
- def after_invocation_interceptors # :nodoc:
- read_inheritable_attribute("after_invocation_interceptors")
- end
-
- def included_intercepted_methods # :nodoc:
- read_inheritable_attribute("included_intercepted_methods") || {}
- end
-
- def excluded_intercepted_methods # :nodoc:
- read_inheritable_attribute("excluded_intercepted_methods") || {}
- end
-
- private
- def append_interceptors_to_chain(condition, interceptors)
- write_inheritable_array("#{condition}_invocation_interceptors", interceptors)
- end
-
- def prepend_interceptors_to_chain(condition, interceptors)
- interceptors = interceptors + read_inheritable_attribute("#{condition}_invocation_interceptors")
- write_inheritable_attribute("#{condition}_invocation_interceptors", interceptors)
- end
-
- def extract_conditions!(interceptors)
- return nil unless interceptors.last.is_a? Hash
- interceptors.pop
- end
-
- def add_interception_conditions(interceptors, conditions)
- return unless conditions
- included, excluded = conditions[:only], conditions[:except]
- write_inheritable_hash("included_intercepted_methods", condition_hash(interceptors, included)) && return if included
- write_inheritable_hash("excluded_intercepted_methods", condition_hash(interceptors, excluded)) if excluded
- end
-
- def condition_hash(interceptors, *methods)
- interceptors.inject({}) {|hash, interceptor| hash.merge(interceptor => methods.flatten.map {|method| method.to_s})}
- end
- end
-
- module InstanceMethods # :nodoc:
- def self.included(base)
- base.class_eval do
- alias_method_chain :perform_invocation, :interception
- end
- end
-
- def perform_invocation_with_interception(method_name, params, &block)
- return if before_invocation(method_name, params, &block) == false
- return_value = perform_invocation_without_interception(method_name, params)
- after_invocation(method_name, params, return_value)
- return_value
- end
-
- def perform_invocation(method_name, params)
- send(method_name, *params)
- end
-
- def before_invocation(name, args, &block)
- call_interceptors(self.class.before_invocation_interceptors, [name, args], &block)
- end
-
- def after_invocation(name, args, result)
- call_interceptors(self.class.after_invocation_interceptors, [name, args, result])
- end
-
- private
-
- def call_interceptors(interceptors, interceptor_args, &block)
- if interceptors and not interceptors.empty?
- interceptors.each do |interceptor|
- next if method_exempted?(interceptor, interceptor_args[0].to_s)
- result = case
- when interceptor.is_a?(Symbol)
- self.send(interceptor, *interceptor_args)
- when interceptor_block?(interceptor)
- interceptor.call(self, *interceptor_args)
- when interceptor_class?(interceptor)
- interceptor.intercept(self, *interceptor_args)
- else
- raise(
- InvocationError,
- "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method"
- )
- end
- reason = nil
- if result.is_a?(Array)
- reason = result[1] if result[1]
- result = result[0]
- end
- if result == false
- block.call(reason) if block && reason
- return false
- end
- end
- end
- end
-
- def interceptor_block?(interceptor)
- interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1)
- end
-
- def interceptor_class?(interceptor)
- interceptor.respond_to?("intercept")
- end
-
- def method_exempted?(interceptor, method_name)
- case
- when self.class.included_intercepted_methods[interceptor]
- !self.class.included_intercepted_methods[interceptor].include?(method_name)
- when self.class.excluded_intercepted_methods[interceptor]
- self.class.excluded_intercepted_methods[interceptor].include?(method_name)
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol.rb
deleted file mode 100644
index 053e9cb4b..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/protocol.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-require 'action_web_service/protocol/abstract'
-require 'action_web_service/protocol/discovery'
-require 'action_web_service/protocol/soap_protocol'
-require 'action_web_service/protocol/xmlrpc_protocol'
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/abstract.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/abstract.rb
deleted file mode 100644
index fff5f622c..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/abstract.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-module ActionWebService # :nodoc:
- module Protocol # :nodoc:
- class ProtocolError < ActionWebServiceError # :nodoc:
- end
-
- class AbstractProtocol # :nodoc:
- def setup(controller)
- end
-
- def decode_action_pack_request(action_pack_request)
- end
-
- def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
- klass = options[:request_class] || SimpleActionPackRequest
- request = klass.new
- request.request_parameters['action'] = service_name.to_s
- request.env['RAW_POST_DATA'] = raw_body
- request.env['REQUEST_METHOD'] = 'POST'
- request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
- request
- end
-
- def decode_request(raw_request, service_name, protocol_options={})
- end
-
- def encode_request(method_name, params, param_types)
- end
-
- def decode_response(raw_response)
- end
-
- def encode_response(method_name, return_value, return_type, protocol_options={})
- end
-
- def protocol_client(api, protocol_name, endpoint_uri, options)
- end
-
- def register_api(api)
- end
- end
-
- class Request # :nodoc:
- attr :protocol
- attr_accessor :method_name
- attr_accessor :method_params
- attr :service_name
- attr_accessor :api
- attr_accessor :api_method
- attr :protocol_options
-
- def initialize(protocol, method_name, method_params, service_name, api=nil, api_method=nil, protocol_options=nil)
- @protocol = protocol
- @method_name = method_name
- @method_params = method_params
- @service_name = service_name
- @api = api
- @api_method = api_method
- @protocol_options = protocol_options || {}
- end
- end
-
- class Response # :nodoc:
- attr :body
- attr :content_type
- attr :return_value
-
- def initialize(body, content_type, return_value)
- @body = body
- @content_type = content_type
- @return_value = return_value
- end
- end
-
- class SimpleActionPackRequest < ActionController::AbstractRequest # :nodoc:
- def initialize
- @env = {}
- @qparams = {}
- @rparams = {}
- @cookies = {}
- reset_session
- end
-
- def query_parameters
- @qparams
- end
-
- def request_parameters
- @rparams
- end
-
- def env
- @env
- end
-
- def host
- ''
- end
-
- def cookies
- @cookies
- end
-
- def session
- @session
- end
-
- def reset_session
- @session = {}
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/discovery.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/discovery.rb
deleted file mode 100644
index 3d4e0818d..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/discovery.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module ActionWebService # :nodoc:
- module Protocol # :nodoc:
- module Discovery # :nodoc:
- def self.included(base)
- base.extend(ClassMethods)
- base.send(:include, ActionWebService::Protocol::Discovery::InstanceMethods)
- end
-
- module ClassMethods # :nodoc:
- def register_protocol(klass)
- write_inheritable_array("web_service_protocols", [klass])
- end
- end
-
- module InstanceMethods # :nodoc:
- private
- def discover_web_service_request(action_pack_request)
- (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
- protocol = protocol.create(self)
- request = protocol.decode_action_pack_request(action_pack_request)
- return request unless request.nil?
- end
- nil
- end
-
- def create_web_service_client(api, protocol_name, endpoint_uri, options)
- (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
- protocol = protocol.create(self)
- client = protocol.protocol_client(api, protocol_name, endpoint_uri, options)
- return client unless client.nil?
- end
- nil
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
deleted file mode 100644
index 1bce496a7..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
+++ /dev/null
@@ -1,176 +0,0 @@
-require 'action_web_service/protocol/soap_protocol/marshaler'
-require 'soap/streamHandler'
-require 'action_web_service/client/soap_client'
-
-module ActionWebService # :nodoc:
- module API # :nodoc:
- class Base # :nodoc:
- def self.soap_client(endpoint_uri, options={})
- ActionWebService::Client::Soap.new self, endpoint_uri, options
- end
- end
- end
-
- module Protocol # :nodoc:
- module Soap # :nodoc:
- def self.included(base)
- base.register_protocol(SoapProtocol)
- base.class_inheritable_option(:wsdl_service_name)
- base.class_inheritable_option(:wsdl_namespace)
- end
-
- class SoapProtocol < AbstractProtocol # :nodoc:
- AWSEncoding = 'UTF-8'
- XSDEncoding = 'UTF8'
-
- attr :marshaler
-
- def initialize(namespace=nil)
- namespace ||= 'urn:ActionWebService'
- @marshaler = SoapMarshaler.new namespace
- end
-
- def self.create(controller)
- SoapProtocol.new(controller.wsdl_namespace)
- end
-
- def decode_action_pack_request(action_pack_request)
- return nil unless soap_action = has_valid_soap_action?(action_pack_request)
- service_name = action_pack_request.parameters['action']
- input_encoding = parse_charset(action_pack_request.env['HTTP_CONTENT_TYPE'])
- protocol_options = {
- :soap_action => soap_action,
- :charset => input_encoding
- }
- decode_request(action_pack_request.raw_post, service_name, protocol_options)
- end
-
- def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
- request = super
- request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name]
- request
- end
-
- def decode_request(raw_request, service_name, protocol_options={})
- envelope = SOAP::Processor.unmarshal(raw_request, :charset => protocol_options[:charset])
- unless envelope
- raise ProtocolError, "Failed to parse SOAP request message"
- end
- request = envelope.body.request
- method_name = request.elename.name
- params = request.collect{ |k, v| marshaler.soap_to_ruby(request[k]) }
- Request.new(self, method_name, params, service_name, nil, nil, protocol_options)
- end
-
- def encode_request(method_name, params, param_types)
- param_types.each{ |type| marshaler.register_type(type) } if param_types
- qname = XSD::QName.new(marshaler.namespace, method_name)
- param_def = []
- if param_types
- params = param_types.zip(params).map do |type, param|
- param_def << ['in', type.name, marshaler.lookup_type(type).mapping]
- [type.name, marshaler.ruby_to_soap(param)]
- end
- else
- params = []
- end
- request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
- request.set_param(params)
- envelope = create_soap_envelope(request)
- SOAP::Processor.marshal(envelope)
- end
-
- def decode_response(raw_response)
- envelope = SOAP::Processor.unmarshal(raw_response)
- unless envelope
- raise ProtocolError, "Failed to parse SOAP request message"
- end
- method_name = envelope.body.request.elename.name
- return_value = envelope.body.response
- return_value = marshaler.soap_to_ruby(return_value) unless return_value.nil?
- [method_name, return_value]
- end
-
- def encode_response(method_name, return_value, return_type, protocol_options={})
- if return_type
- return_binding = marshaler.register_type(return_type)
- marshaler.annotate_arrays(return_binding, return_value)
- end
- qname = XSD::QName.new(marshaler.namespace, method_name)
- if return_value.nil?
- response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
- else
- if return_value.is_a?(Exception)
- detail = SOAP::Mapping::SOAPException.new(return_value)
- response = SOAP::SOAPFault.new(
- SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, 'Server']),
- SOAP::SOAPString.new(return_value.to_s),
- SOAP::SOAPString.new(self.class.name),
- marshaler.ruby_to_soap(detail))
- else
- if return_type
- param_def = [['retval', 'return', marshaler.lookup_type(return_type).mapping]]
- response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
- response.retval = marshaler.ruby_to_soap(return_value)
- else
- response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
- end
- end
- end
- envelope = create_soap_envelope(response)
-
- # FIXME: This is not thread-safe, but StringFactory_ in SOAP4R only
- # reads target encoding from the XSD::Charset.encoding variable.
- # This is required to ensure $KCODE strings are converted
- # correctly to UTF-8 for any values of $KCODE.
- previous_encoding = XSD::Charset.encoding
- XSD::Charset.encoding = XSDEncoding
- response_body = SOAP::Processor.marshal(envelope, :charset => AWSEncoding)
- XSD::Charset.encoding = previous_encoding
-
- Response.new(response_body, "text/xml; charset=#{AWSEncoding}", return_value)
- end
-
- def protocol_client(api, protocol_name, endpoint_uri, options={})
- return nil unless protocol_name == :soap
- ActionWebService::Client::Soap.new(api, endpoint_uri, options)
- end
-
- def register_api(api)
- api.api_methods.each do |name, method|
- method.expects.each{ |type| marshaler.register_type(type) } if method.expects
- method.returns.each{ |type| marshaler.register_type(type) } if method.returns
- end
- end
-
- private
- def has_valid_soap_action?(request)
- return nil unless request.method == :post
- soap_action = request.env['HTTP_SOAPACTION']
- return nil unless soap_action
- soap_action = soap_action.dup
- soap_action.gsub!(/^"/, '')
- soap_action.gsub!(/"$/, '')
- soap_action.strip!
- return nil if soap_action.empty?
- soap_action
- end
-
- def create_soap_envelope(body)
- header = SOAP::SOAPHeader.new
- body = SOAP::SOAPBody.new(body)
- SOAP::SOAPEnvelope.new(header, body)
- end
-
- def parse_charset(content_type)
- return AWSEncoding if content_type.nil?
- if /^text\/xml(?:\s*;\s*charset=([^"]+|"[^"]+"))$/i =~ content_type
- $1
- else
- AWSEncoding
- end
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb
deleted file mode 100644
index 187339627..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb
+++ /dev/null
@@ -1,235 +0,0 @@
-require 'soap/mapping'
-
-module ActionWebService
- module Protocol
- module Soap
- # Workaround for SOAP4R return values changing
- class Registry < SOAP::Mapping::Registry
- if SOAP::Version >= "1.5.4"
- def find_mapped_soap_class(obj_class)
- return @map.instance_eval { @obj2soap[obj_class][0] }
- end
-
- def find_mapped_obj_class(soap_class)
- return @map.instance_eval { @soap2obj[soap_class][0] }
- end
- end
- end
-
- class SoapMarshaler
- attr :namespace
- attr :registry
-
- def initialize(namespace=nil)
- @namespace = namespace || 'urn:ActionWebService'
- @registry = Registry.new
- @type2binding = {}
- register_static_factories
- end
-
- def soap_to_ruby(obj)
- SOAP::Mapping.soap2obj(obj, @registry)
- end
-
- def ruby_to_soap(obj)
- soap = SOAP::Mapping.obj2soap(obj, @registry)
- soap.elename = XSD::QName.new if SOAP::Version >= "1.5.5" && soap.elename == XSD::QName::EMPTY
- soap
- end
-
- def register_type(type)
- return @type2binding[type] if @type2binding.has_key?(type)
-
- if type.array?
- array_mapping = @registry.find_mapped_soap_class(Array)
- qname = XSD::QName.new(@namespace, soap_type_name(type.element_type.type_class.name) + 'Array')
- element_type_binding = register_type(type.element_type)
- @type2binding[type] = SoapBinding.new(self, qname, type, array_mapping, element_type_binding)
- elsif (mapping = @registry.find_mapped_soap_class(type.type_class) rescue nil)
- qname = mapping[2] ? mapping[2][:type] : nil
- qname ||= soap_base_type_name(mapping[0])
- @type2binding[type] = SoapBinding.new(self, qname, type, mapping)
- else
- qname = XSD::QName.new(@namespace, soap_type_name(type.type_class.name))
- @registry.add(type.type_class,
- SOAP::SOAPStruct,
- typed_struct_factory(type.type_class),
- { :type => qname })
- mapping = @registry.find_mapped_soap_class(type.type_class)
- @type2binding[type] = SoapBinding.new(self, qname, type, mapping)
- end
-
- if type.structured?
- type.each_member do |m_name, m_type|
- register_type(m_type)
- end
- end
-
- @type2binding[type]
- end
- alias :lookup_type :register_type
-
- def annotate_arrays(binding, value)
- if value.nil?
- return
- elsif binding.type.array?
- mark_typed_array(value, binding.element_binding.qname)
- if binding.element_binding.type.custom?
- value.each do |element|
- annotate_arrays(binding.element_binding, element)
- end
- end
- elsif binding.type.structured?
- binding.type.each_member do |name, type|
- member_binding = register_type(type)
- member_value = value.respond_to?('[]') ? value[name] : value.send(name)
- annotate_arrays(member_binding, member_value) if type.custom?
- end
- end
- end
-
- private
- def typed_struct_factory(type_class)
- if Object.const_defined?('ActiveRecord')
- if type_class.ancestors.include?(ActiveRecord::Base)
- qname = XSD::QName.new(@namespace, soap_type_name(type_class.name))
- type_class.instance_variable_set('@qname', qname)
- return SoapActiveRecordStructFactory.new
- end
- end
- SOAP::Mapping::Registry::TypedStructFactory
- end
-
- def mark_typed_array(array, qname)
- (class << array; self; end).class_eval do
- define_method(:arytype) do
- qname
- end
- end
- end
-
- def soap_base_type_name(type)
- xsd_type = type.ancestors.find{ |c| c.const_defined? 'Type' }
- xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
- end
-
- def soap_type_name(type_name)
- type_name.gsub(/::/, '..')
- end
-
- def register_static_factories
- @registry.add(ActionWebService::Base64, SOAP::SOAPBase64, SoapBase64Factory.new, nil)
- mapping = @registry.find_mapped_soap_class(ActionWebService::Base64)
- @type2binding[ActionWebService::Base64] =
- SoapBinding.new(self, SOAP::SOAPBase64::Type, ActionWebService::Base64, mapping)
- @registry.add(Array, SOAP::SOAPArray, SoapTypedArrayFactory.new, nil)
- @registry.add(::BigDecimal, SOAP::SOAPDouble, SOAP::Mapping::Registry::BasetypeFactory, {:derived_class => true})
- end
- end
-
- class SoapBinding
- attr :qname
- attr :type
- attr :mapping
- attr :element_binding
-
- def initialize(marshaler, qname, type, mapping, element_binding=nil)
- @marshaler = marshaler
- @qname = qname
- @type = type
- @mapping = mapping
- @element_binding = element_binding
- end
-
- def type_name
- @type.custom? ? @qname.name : nil
- end
-
- def qualified_type_name(ns=nil)
- if @type.custom?
- "#{ns ? ns : @qname.namespace}:#{@qname.name}"
- else
- ns = XSD::NS.new
- ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
- ns.assign(SOAP::EncodingNamespace, "soapenc")
- xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')}
- return ns.name(XSD::AnyTypeName) unless xsd_klass
- ns.name(xsd_klass.const_get('Type'))
- end
- end
-
- def eql?(other)
- @qname == other.qname
- end
- alias :== :eql?
-
- def hash
- @qname.hash
- end
- end
-
- class SoapActiveRecordStructFactory < SOAP::Mapping::Factory
- def obj2soap(soap_class, obj, info, map)
- unless obj.is_a?(ActiveRecord::Base)
- return nil
- end
- soap_obj = soap_class.new(obj.class.instance_variable_get('@qname'))
- obj.class.columns.each do |column|
- key = column.name.to_s
- value = obj.send(key)
- soap_obj[key] = SOAP::Mapping._obj2soap(value, map)
- end
- soap_obj
- end
-
- def soap2obj(obj_class, node, info, map)
- unless node.type == obj_class.instance_variable_get('@qname')
- return false
- end
- obj = obj_class.new
- node.each do |key, value|
- obj[key] = value.data
- end
- obj.instance_variable_set('@new_record', false)
- return true, obj
- end
- end
-
- class SoapTypedArrayFactory < SOAP::Mapping::Factory
- def obj2soap(soap_class, obj, info, map)
- unless obj.respond_to?(:arytype)
- return nil
- end
- soap_obj = soap_class.new(SOAP::ValueArrayName, 1, obj.arytype)
- mark_marshalled_obj(obj, soap_obj)
- obj.each do |item|
- child = SOAP::Mapping._obj2soap(item, map)
- soap_obj.add(child)
- end
- soap_obj
- end
-
- def soap2obj(obj_class, node, info, map)
- return false
- end
- end
-
- class SoapBase64Factory < SOAP::Mapping::Factory
- def obj2soap(soap_class, obj, info, map)
- unless obj.is_a?(ActionWebService::Base64)
- return nil
- end
- return soap_class.new(obj)
- end
-
- def soap2obj(obj_class, node, info, map)
- unless node.type == SOAP::SOAPBase64::Type
- return false
- end
- return true, obj_class.new(node.string)
- end
- end
-
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
deleted file mode 100644
index dfa4afc67..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-require 'xmlrpc/marshal'
-require 'action_web_service/client/xmlrpc_client'
-
-module XMLRPC # :nodoc:
- class FaultException # :nodoc:
- alias :message :faultString
- end
-
- class Create
- def wrong_type(value)
- if BigDecimal === value
- [true, value.to_f]
- else
- false
- end
- end
- end
-end
-
-module ActionWebService # :nodoc:
- module API # :nodoc:
- class Base # :nodoc:
- def self.xmlrpc_client(endpoint_uri, options={})
- ActionWebService::Client::XmlRpc.new self, endpoint_uri, options
- end
- end
- end
-
- module Protocol # :nodoc:
- module XmlRpc # :nodoc:
- def self.included(base)
- base.register_protocol(XmlRpcProtocol)
- end
-
- class XmlRpcProtocol < AbstractProtocol # :nodoc:
- def self.create(controller)
- XmlRpcProtocol.new
- end
-
- def decode_action_pack_request(action_pack_request)
- service_name = action_pack_request.parameters['action']
- decode_request(action_pack_request.raw_post, service_name)
- end
-
- def decode_request(raw_request, service_name)
- method_name, params = XMLRPC::Marshal.load_call(raw_request)
- Request.new(self, method_name, params, service_name)
- rescue
- return nil
- end
-
- def encode_request(method_name, params, param_types)
- if param_types
- params = params.dup
- param_types.each_with_index{ |type, i| params[i] = value_to_xmlrpc_wire_format(params[i], type) }
- end
- XMLRPC::Marshal.dump_call(method_name, *params)
- end
-
- def decode_response(raw_response)
- [nil, XMLRPC::Marshal.load_response(raw_response)]
- end
-
- def encode_response(method_name, return_value, return_type, protocol_options={})
- if return_value && return_type
- return_value = value_to_xmlrpc_wire_format(return_value, return_type)
- end
- return_value = false if return_value.nil?
- raw_response = XMLRPC::Marshal.dump_response(return_value)
- Response.new(raw_response, 'text/xml', return_value)
- end
-
- def encode_multicall_response(responses, protocol_options={})
- result = responses.map do |return_value, return_type|
- if return_value && return_type
- return_value = value_to_xmlrpc_wire_format(return_value, return_type)
- return_value = [return_value] unless return_value.nil?
- end
- return_value = false if return_value.nil?
- return_value
- end
- raw_response = XMLRPC::Marshal.dump_response(result)
- Response.new(raw_response, 'text/xml', result)
- end
-
- def protocol_client(api, protocol_name, endpoint_uri, options={})
- return nil unless protocol_name == :xmlrpc
- ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
- end
-
- def value_to_xmlrpc_wire_format(value, value_type)
- if value_type.array?
- value.map{ |val| value_to_xmlrpc_wire_format(val, value_type.element_type) }
- else
- if value.is_a?(ActionWebService::Struct)
- struct = {}
- value.class.members.each do |name, type|
- member_value = value[name]
- next if member_value.nil?
- struct[name.to_s] = value_to_xmlrpc_wire_format(member_value, type)
- end
- struct
- elsif value.is_a?(ActiveRecord::Base)
- struct = {}
- value.attributes.each do |key, member_value|
- next if member_value.nil?
- struct[key.to_s] = member_value
- end
- struct
- elsif value.is_a?(ActionWebService::Base64)
- XMLRPC::Base64.new(value)
- elsif value.is_a?(Exception) && !value.is_a?(XMLRPC::FaultException)
- XMLRPC::FaultException.new(2, value.message)
- else
- value
- end
- end
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/scaffolding.rb b/vendor/plugins/actionwebservice/lib/action_web_service/scaffolding.rb
deleted file mode 100644
index f94a7ee91..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/scaffolding.rb
+++ /dev/null
@@ -1,283 +0,0 @@
-require 'benchmark'
-require 'pathname'
-
-module ActionWebService
- module Scaffolding # :nodoc:
- class ScaffoldingError < ActionWebServiceError # :nodoc:
- end
-
- def self.included(base)
- base.extend(ClassMethods)
- end
-
- # Web service invocation scaffolding provides a way to quickly invoke web service methods in a controller. The
- # generated scaffold actions have default views to let you enter the method parameters and view the
- # results.
- #
- # Example:
- #
- # class ApiController < ActionController
- # web_service_scaffold :invoke
- # end
- #
- # This example generates an +invoke+ action in the +ApiController+ that you can navigate to from
- # your browser, select the API method, enter its parameters, and perform the invocation.
- #
- # If you want to customize the default views, create the following views in "app/views":
- #
- # * <tt>action_name/methods.erb</tt>
- # * <tt>action_name/parameters.erb</tt>
- # * <tt>action_name/result.erb</tt>
- # * <tt>action_name/layout.erb</tt>
- #
- # Where <tt>action_name</tt> is the name of the action you gave to ClassMethods#web_service_scaffold.
- #
- # You can use the default views in <tt>RAILS_DIR/lib/action_web_service/templates/scaffolds</tt> as
- # a guide.
- module ClassMethods
- # Generates web service invocation scaffolding for the current controller. The given action name
- # can then be used as the entry point for invoking API methods from a web browser.
- def web_service_scaffold(action_name)
- add_template_helper(Helpers)
- module_eval <<-"end_eval", __FILE__, __LINE__ + 1
- def #{action_name}
- if request.method == :get
- setup_invocation_assigns
- render_invocation_scaffold 'methods'
- end
- end
-
- def #{action_name}_method_params
- if request.method == :get
- setup_invocation_assigns
- render_invocation_scaffold 'parameters'
- end
- end
-
- def #{action_name}_submit
- if request.method == :post
- setup_invocation_assigns
- protocol_name = params['protocol'] ? params['protocol'].to_sym : :soap
- case protocol_name
- when :soap
- @protocol = Protocol::Soap::SoapProtocol.create(self)
- when :xmlrpc
- @protocol = Protocol::XmlRpc::XmlRpcProtocol.create(self)
- end
- bm = Benchmark.measure do
- @protocol.register_api(@scaffold_service.api)
- post_params = params['method_params'] ? params['method_params'].dup : nil
- params = []
- @scaffold_method.expects.each_with_index do |spec, i|
- params << post_params[i.to_s]
- end if @scaffold_method.expects
- params = @scaffold_method.cast_expects(params)
- method_name = public_method_name(@scaffold_service.name, @scaffold_method.public_name)
- @method_request_xml = @protocol.encode_request(method_name, params, @scaffold_method.expects)
- new_request = @protocol.encode_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml)
- prepare_request(new_request, @scaffold_service.name, @scaffold_method.public_name)
- self.request = new_request
- if @scaffold_container.dispatching_mode != :direct
- request.parameters['action'] = @scaffold_service.name
- end
- dispatch_web_service_request
- @method_response_xml = response.body
- method_name, obj = @protocol.decode_response(@method_response_xml)
- return if handle_invocation_exception(obj)
- @method_return_value = @scaffold_method.cast_returns(obj)
- end
- @method_elapsed = bm.real
- add_instance_variables_to_assigns
- reset_invocation_response
- render_invocation_scaffold 'result'
- end
- end
-
- private
- def setup_invocation_assigns
- @scaffold_class = self.class
- @scaffold_action_name = "#{action_name}"
- @scaffold_container = WebServiceModel::Container.new(self)
- if params['service'] && params['method']
- @scaffold_service = @scaffold_container.services.find{ |x| x.name == params['service'] }
- @scaffold_method = @scaffold_service.api_methods[params['method']]
- end
- add_instance_variables_to_assigns
- end
-
- def render_invocation_scaffold(action)
- customized_template = "\#{self.class.controller_path}/#{action_name}/\#{action}"
- default_template = scaffold_path(action)
- if template_exists?(customized_template)
- content = @template.render :file => customized_template
- else
- content = @template.render :file => default_template
- end
- @template.instance_variable_set("@content_for_layout", content)
- if self.active_layout.nil?
- render :file => scaffold_path("layout")
- else
- render :file => self.active_layout
- end
- end
-
- def scaffold_path(template_name)
- File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".erb"
- end
-
- def reset_invocation_response
- erase_render_results
- response.headers = ::ActionController::AbstractResponse::DEFAULT_HEADERS.merge("cookie" => [])
- end
-
- def public_method_name(service_name, method_name)
- if web_service_dispatching_mode == :layered && @protocol.is_a?(ActionWebService::Protocol::XmlRpc::XmlRpcProtocol)
- service_name + '.' + method_name
- else
- method_name
- end
- end
-
- def prepare_request(new_request, service_name, method_name)
- new_request.parameters.update(request.parameters)
- request.env.each{ |k, v| new_request.env[k] = v unless new_request.env.has_key?(k) }
- if web_service_dispatching_mode == :layered && @protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol)
- new_request.env['HTTP_SOAPACTION'] = "/\#{controller_name()}/\#{service_name}/\#{method_name}"
- end
- end
-
- def handle_invocation_exception(obj)
- exception = nil
- if obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && obj.detail.cause.is_a?(Exception)
- exception = obj.detail.cause
- elsif obj.is_a?(XMLRPC::FaultException)
- exception = obj
- end
- return unless exception
- reset_invocation_response
- rescue_action(exception)
- true
- end
- end_eval
- end
- end
-
- module Helpers # :nodoc:
- def method_parameter_input_fields(method, type, field_name_base, idx, was_structured=false)
- if type.array?
- return content_tag('em', "Typed array input fields not supported yet (#{type.name})")
- end
- if type.structured?
- return content_tag('em', "Nested structural types not supported yet (#{type.name})") if was_structured
- parameters = ""
- type.each_member do |member_name, member_type|
- label = method_parameter_label(member_name, member_type)
- nested_content = method_parameter_input_fields(
- method,
- member_type,
- "#{field_name_base}[#{idx}][#{member_name}]",
- idx,
- true)
- if member_type.custom?
- parameters << content_tag('li', label)
- parameters << content_tag('ul', nested_content)
- else
- parameters << content_tag('li', label + ' ' + nested_content)
- end
- end
- content_tag('ul', parameters)
- else
- # If the data source was structured previously we already have the index set
- field_name_base = "#{field_name_base}[#{idx}]" unless was_structured
-
- case type.type
- when :int
- text_field_tag "#{field_name_base}"
- when :string
- text_field_tag "#{field_name_base}"
- when :base64
- text_area_tag "#{field_name_base}", nil, :size => "40x5"
- when :bool
- radio_button_tag("#{field_name_base}", "true") + " True" +
- radio_button_tag("#{field_name_base}", "false") + "False"
- when :float
- text_field_tag "#{field_name_base}"
- when :time, :datetime
- time = Time.now
- i = 0
- %w|year month day hour minute second|.map do |name|
- i += 1
- send("select_#{name}", time, :prefix => "#{field_name_base}[#{i}]", :discard_type => true)
- end.join
- when :date
- date = Date.today
- i = 0
- %w|year month day|.map do |name|
- i += 1
- send("select_#{name}", date, :prefix => "#{field_name_base}[#{i}]", :discard_type => true)
- end.join
- end
- end
- end
-
- def method_parameter_label(name, type)
- name.to_s.capitalize + ' (' + type.human_name(false) + ')'
- end
-
- def service_method_list(service)
- action = @scaffold_action_name + '_method_params'
- methods = service.api_methods_full.map do |desc, name|
- content_tag("li", link_to(desc, :action => action, :service => service.name, :method => name))
- end
- content_tag("ul", methods.join("\n"))
- end
- end
-
- module WebServiceModel # :nodoc:
- class Container # :nodoc:
- attr :services
- attr :dispatching_mode
-
- def initialize(real_container)
- @real_container = real_container
- @dispatching_mode = @real_container.class.web_service_dispatching_mode
- @services = []
- if @dispatching_mode == :direct
- @services << Service.new(@real_container.controller_name, @real_container)
- else
- @real_container.class.web_services.each do |name, obj|
- @services << Service.new(name, @real_container.instance_eval{ web_service_object(name) })
- end
- end
- end
- end
-
- class Service # :nodoc:
- attr :name
- attr :object
- attr :api
- attr :api_methods
- attr :api_methods_full
-
- def initialize(name, real_service)
- @name = name.to_s
- @object = real_service
- @api = @object.class.web_service_api
- if @api.nil?
- raise ScaffoldingError, "No web service API attached to #{object.class}"
- end
- @api_methods = {}
- @api_methods_full = []
- @api.api_methods.each do |name, method|
- @api_methods[method.public_name.to_s] = method
- @api_methods_full << [method.to_s, method.public_name.to_s]
- end
- end
-
- def to_s
- self.name.camelize
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/struct.rb b/vendor/plugins/actionwebservice/lib/action_web_service/struct.rb
deleted file mode 100644
index 00eafc169..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/struct.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-module ActionWebService
- # To send structured types across the wire, derive from ActionWebService::Struct,
- # and use +member+ to declare structure members.
- #
- # ActionWebService::Struct should be used in method signatures when you want to accept or return
- # structured types that have no Active Record model class representations, or you don't
- # want to expose your entire Active Record model to remote callers.
- #
- # === Example
- #
- # class Person < ActionWebService::Struct
- # member :id, :int
- # member :firstnames, [:string]
- # member :lastname, :string
- # member :email, :string
- # end
- # person = Person.new(:id => 5, :firstname => 'john', :lastname => 'doe')
- #
- # Active Record model classes are already implicitly supported in method
- # signatures.
- class Struct
- # If a Hash is given as argument to an ActionWebService::Struct constructor,
- # it can contain initial values for the structure member.
- def initialize(values={})
- if values.is_a?(Hash)
- values.map{|k,v| __send__('%s=' % k.to_s, v)}
- end
- end
-
- # The member with the given name
- def [](name)
- send(name.to_s)
- end
-
- # Iterates through each member
- def each_pair(&block)
- self.class.members.each do |name, type|
- yield name, self.__send__(name)
- end
- end
-
- class << self
- # Creates a structure member with the specified +name+ and +type+. Generates
- # accessor methods for reading and writing the member value.
- def member(name, type)
- name = name.to_sym
- type = ActionWebService::SignatureTypes.canonical_signature_entry({ name => type }, 0)
- write_inheritable_hash("struct_members", name => type)
- class_eval <<-END
- def #{name}; @#{name}; end
- def #{name}=(value); @#{name} = value; end
- END
- end
-
- def members # :nodoc:
- read_inheritable_attribute("struct_members") || {}
- end
-
- def member_type(name) # :nodoc:
- members[name.to_sym]
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/support/class_inheritable_options.rb b/vendor/plugins/actionwebservice/lib/action_web_service/support/class_inheritable_options.rb
deleted file mode 100644
index 4d1c2ed47..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/support/class_inheritable_options.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-class Class # :nodoc:
- def class_inheritable_option(sym, default_value=nil)
- write_inheritable_attribute sym, default_value
- class_eval <<-EOS
- def self.#{sym}(value=nil)
- if !value.nil?
- write_inheritable_attribute(:#{sym}, value)
- else
- read_inheritable_attribute(:#{sym})
- end
- end
-
- def self.#{sym}=(value)
- write_inheritable_attribute(:#{sym}, value)
- end
-
- def #{sym}
- self.class.#{sym}
- end
-
- def #{sym}=(value)
- self.class.#{sym} = value
- end
- EOS
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/support/signature_types.rb b/vendor/plugins/actionwebservice/lib/action_web_service/support/signature_types.rb
deleted file mode 100644
index 66c86bf6d..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/support/signature_types.rb
+++ /dev/null
@@ -1,226 +0,0 @@
-module ActionWebService # :nodoc:
- # Action Web Service supports the following base types in a signature:
- #
- # [<tt>:int</tt>] Represents an integer value, will be cast to an integer using <tt>Integer(value)</tt>
- # [<tt>:string</tt>] Represents a string value, will be cast to an string using the <tt>to_s</tt> method on an object
- # [<tt>:base64</tt>] Represents a Base 64 value, will contain the binary bytes of a Base 64 value sent by the caller
- # [<tt>:bool</tt>] Represents a boolean value, whatever is passed will be cast to boolean (<tt>true</tt>, '1', 'true', 'y', 'yes' are taken to represent true; <tt>false</tt>, '0', 'false', 'n', 'no' and <tt>nil</tt> represent false)
- # [<tt>:float</tt>] Represents a floating point value, will be cast to a float using <tt>Float(value)</tt>
- # [<tt>:time</tt>] Represents a timestamp, will be cast to a <tt>Time</tt> object
- # [<tt>:datetime</tt>] Represents a timestamp, will be cast to a <tt>DateTime</tt> object
- # [<tt>:date</tt>] Represents a date, will be cast to a <tt>Date</tt> object
- #
- # For structured types, you'll need to pass in the Class objects of
- # ActionWebService::Struct and ActiveRecord::Base derivatives.
- module SignatureTypes
- def canonical_signature(signature) # :nodoc:
- return nil if signature.nil?
- unless signature.is_a?(Array)
- raise(ActionWebServiceError, "Expected signature to be an Array")
- end
- i = -1
- signature.map{ |spec| canonical_signature_entry(spec, i += 1) }
- end
-
- def canonical_signature_entry(spec, i) # :nodoc:
- orig_spec = spec
- name = "param#{i}"
- if spec.is_a?(Hash)
- name, spec = spec.keys.first, spec.values.first
- end
- type = spec
- if spec.is_a?(Array)
- ArrayType.new(orig_spec, canonical_signature_entry(spec[0], 0), name)
- else
- type = canonical_type(type)
- if type.is_a?(Symbol)
- BaseType.new(orig_spec, type, name)
- else
- StructuredType.new(orig_spec, type, name)
- end
- end
- end
-
- def canonical_type(type) # :nodoc:
- type_name = symbol_name(type) || class_to_type_name(type)
- type = type_name || type
- return canonical_type_name(type) if type.is_a?(Symbol)
- type
- end
-
- def canonical_type_name(name) # :nodoc:
- name = name.to_sym
- case name
- when :int, :integer, :fixnum, :bignum
- :int
- when :string, :text
- :string
- when :base64, :binary
- :base64
- when :bool, :boolean
- :bool
- when :float, :double
- :float
- when :decimal
- :decimal
- when :time, :timestamp
- :time
- when :datetime
- :datetime
- when :date
- :date
- else
- raise(TypeError, "#{name} is not a valid base type")
- end
- end
-
- def canonical_type_class(type) # :nodoc:
- type = canonical_type(type)
- type.is_a?(Symbol) ? type_name_to_class(type) : type
- end
-
- def symbol_name(name) # :nodoc:
- return name.to_sym if name.is_a?(Symbol) || name.is_a?(String)
- nil
- end
-
- def class_to_type_name(klass) # :nodoc:
- klass = klass.class unless klass.is_a?(Class)
- if derived_from?(Integer, klass) || derived_from?(Fixnum, klass) || derived_from?(Bignum, klass)
- :int
- elsif klass == String
- :string
- elsif klass == Base64
- :base64
- elsif klass == TrueClass || klass == FalseClass
- :bool
- elsif derived_from?(Float, klass) || derived_from?(Precision, klass) || derived_from?(Numeric, klass)
- :float
- elsif klass == Time
- :time
- elsif klass == DateTime
- :datetime
- elsif klass == Date
- :date
- else
- nil
- end
- end
-
- def type_name_to_class(name) # :nodoc:
- case canonical_type_name(name)
- when :int
- Integer
- when :string
- String
- when :base64
- Base64
- when :bool
- TrueClass
- when :float
- Float
- when :decimal
- BigDecimal
- when :time
- Time
- when :date
- Date
- when :datetime
- DateTime
- else
- nil
- end
- end
-
- def derived_from?(ancestor, child) # :nodoc:
- child.ancestors.include?(ancestor)
- end
-
- module_function :type_name_to_class
- module_function :class_to_type_name
- module_function :symbol_name
- module_function :canonical_type_class
- module_function :canonical_type_name
- module_function :canonical_type
- module_function :canonical_signature_entry
- module_function :canonical_signature
- module_function :derived_from?
- end
-
- class BaseType # :nodoc:
- include SignatureTypes
-
- attr :spec
- attr :type
- attr :type_class
- attr :name
-
- def initialize(spec, type, name)
- @spec = spec
- @type = canonical_type(type)
- @type_class = canonical_type_class(@type)
- @name = name
- end
-
- def custom?
- false
- end
-
- def array?
- false
- end
-
- def structured?
- false
- end
-
- def human_name(show_name=true)
- type_type = array? ? element_type.type.to_s : self.type.to_s
- str = array? ? (type_type + '[]') : type_type
- show_name ? (str + " " + name.to_s) : str
- end
- end
-
- class ArrayType < BaseType # :nodoc:
- attr :element_type
-
- def initialize(spec, element_type, name)
- super(spec, Array, name)
- @element_type = element_type
- end
-
- def custom?
- true
- end
-
- def array?
- true
- end
- end
-
- class StructuredType < BaseType # :nodoc:
- def each_member
- if @type_class.respond_to?(:members)
- @type_class.members.each do |name, type|
- yield name, type
- end
- elsif @type_class.respond_to?(:columns)
- i = -1
- @type_class.columns.each do |column|
- yield column.name, canonical_signature_entry(column.type, i += 1)
- end
- end
- end
-
- def custom?
- true
- end
-
- def structured?
- true
- end
- end
-
- class Base64 < String # :nodoc:
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.erb b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.erb
deleted file mode 100644
index 167613f68..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.erb
+++ /dev/null
@@ -1,65 +0,0 @@
-<html>
-<head>
- <title><%= @scaffold_class.wsdl_service_name %> Web Service</title>
- <style>
- body { background-color: #fff; color: #333; }
-
- body, p, ol, ul, td {
- font-family: verdana, arial, helvetica, sans-serif;
- font-size: 13px;
- line-height: 18px;
- }
-
- pre {
- background-color: #eee;
- padding: 10px;
- font-size: 11px;
- }
-
- a { color: #000; }
- a:visited { color: #666; }
- a:hover { color: #fff; background-color:#000; }
-
- .fieldWithErrors {
- padding: 2px;
- background-color: red;
- display: table;
- }
-
- #errorExplanation {
- width: 400px;
- border: 2px solid red;
- padding: 7px;
- padding-bottom: 12px;
- margin-bottom: 20px;
- background-color: #f0f0f0;
- }
-
- #errorExplanation h2 {
- text-align: left;
- font-weight: bold;
- padding: 5px 5px 5px 15px;
- font-size: 12px;
- margin: -7px;
- background-color: #c00;
- color: #fff;
- }
-
- #errorExplanation p {
- color: #333;
- margin-bottom: 0;
- padding: 5px;
- }
-
- #errorExplanation ul li {
- font-size: 12px;
- list-style: square;
- }
- </style>
-</head>
-<body>
-
-<%= @content_for_layout %>
-
-</body>
-</html>
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml
deleted file mode 100644
index e69de29bb..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml
+++ /dev/null
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.erb b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.erb
deleted file mode 100644
index 60dfe23f0..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-<% @scaffold_container.services.each do |service| %>
-
- <h4>API Methods for <%= service %></h4>
- <%= service_method_list(service) %>
-
-<% end %>
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml
deleted file mode 100644
index e69de29bb..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml
+++ /dev/null
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.erb b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.erb
deleted file mode 100644
index 767284e0d..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.erb
+++ /dev/null
@@ -1,29 +0,0 @@
-<h4>Method Invocation Details for <em><%= @scaffold_service %>#<%= @scaffold_method.public_name %></em></h4>
-
-<% form_tag(:action => @scaffold_action_name + '_submit') do -%>
-<%= hidden_field_tag "service", @scaffold_service.name %>
-<%= hidden_field_tag "method", @scaffold_method.public_name %>
-
-<p>
-<label for="protocol">Protocol:</label><br />
-<%= select_tag 'protocol', options_for_select([['SOAP', 'soap'], ['XML-RPC', 'xmlrpc']], params['protocol']) %>
-</p>
-
-<% if @scaffold_method.expects %>
-
-<strong>Method Parameters:</strong><br />
-<% @scaffold_method.expects.each_with_index do |type, i| %>
- <p>
- <label for="method_params[<%= i %>]"><%= method_parameter_label(type.name, type) %> </label><br />
- <%= method_parameter_input_fields(@scaffold_method, type, "method_params", i) %>
- </p>
-<% end %>
-
-<% end %>
-
-<%= submit_tag "Invoke" %>
-<% end -%>
-
-<p>
-<%= link_to "Back", :action => @scaffold_action_name %>
-</p>
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml
deleted file mode 100644
index e69de29bb..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml
+++ /dev/null
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.erb b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.erb
deleted file mode 100644
index 5317688fc..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.erb
+++ /dev/null
@@ -1,30 +0,0 @@
-<h4>Method Invocation Result for <em><%= @scaffold_service %>#<%= @scaffold_method.public_name %></em></h4>
-
-<p>
-Invocation took <tt><%= '%f' % @method_elapsed %></tt> seconds
-</p>
-
-<p>
-<strong>Return Value:</strong><br />
-<pre>
-<%= h @method_return_value.inspect %>
-</pre>
-</p>
-
-<p>
-<strong>Request XML:</strong><br />
-<pre>
-<%= h @method_request_xml %>
-</pre>
-</p>
-
-<p>
-<strong>Response XML:</strong><br />
-<pre>
-<%= h @method_response_xml %>
-</pre>
-</p>
-
-<p>
-<%= link_to "Back", :action => @scaffold_action_name + '_method_params', :method => @scaffold_method.public_name, :service => @scaffold_service.name %>
-</p>
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml
deleted file mode 100644
index e69de29bb..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml
+++ /dev/null
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/test_invoke.rb b/vendor/plugins/actionwebservice/lib/action_web_service/test_invoke.rb
deleted file mode 100644
index 7e714c941..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/test_invoke.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-require 'test/unit'
-
-module Test # :nodoc:
- module Unit # :nodoc:
- class TestCase # :nodoc:
- private
- # invoke the specified API method
- def invoke_direct(method_name, *args)
- prepare_request('api', 'api', method_name, *args)
- @controller.process(@request, @response)
- decode_rpc_response
- end
- alias_method :invoke, :invoke_direct
-
- # invoke the specified API method on the specified service
- def invoke_delegated(service_name, method_name, *args)
- prepare_request(service_name.to_s, service_name, method_name, *args)
- @controller.process(@request, @response)
- decode_rpc_response
- end
-
- # invoke the specified layered API method on the correct service
- def invoke_layered(service_name, method_name, *args)
- prepare_request('api', service_name, method_name, *args)
- @controller.process(@request, @response)
- decode_rpc_response
- end
-
- # ---------------------- internal ---------------------------
-
- def prepare_request(action, service_name, api_method_name, *args)
- @request.recycle!
- @request.request_parameters['action'] = action
- @request.env['REQUEST_METHOD'] = 'POST'
- @request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
- @request.env['RAW_POST_DATA'] = encode_rpc_call(service_name, api_method_name, *args)
- case protocol
- when ActionWebService::Protocol::Soap::SoapProtocol
- soap_action = "/#{@controller.controller_name}/#{service_name}/#{public_method_name(service_name, api_method_name)}"
- @request.env['HTTP_SOAPACTION'] = soap_action
- when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
- @request.env.delete('HTTP_SOAPACTION')
- end
- end
-
- def encode_rpc_call(service_name, api_method_name, *args)
- case @controller.web_service_dispatching_mode
- when :direct
- api = @controller.class.web_service_api
- when :delegated, :layered
- api = @controller.web_service_object(service_name.to_sym).class.web_service_api
- end
- protocol.register_api(api)
- method = api.api_methods[api_method_name.to_sym]
- raise ArgumentError, "wrong number of arguments for rpc call (#{args.length} for #{method.expects.length})" if method && method.expects && args.length != method.expects.length
- protocol.encode_request(public_method_name(service_name, api_method_name), args.dup, method.expects)
- end
-
- def decode_rpc_response
- public_method_name, return_value = protocol.decode_response(@response.body)
- exception = is_exception?(return_value)
- raise exception if exception
- return_value
- end
-
- def public_method_name(service_name, api_method_name)
- public_name = service_api(service_name).public_api_method_name(api_method_name)
- if @controller.web_service_dispatching_mode == :layered && protocol.is_a?(ActionWebService::Protocol::XmlRpc::XmlRpcProtocol)
- '%s.%s' % [service_name.to_s, public_name]
- else
- public_name
- end
- end
-
- def service_api(service_name)
- case @controller.web_service_dispatching_mode
- when :direct
- @controller.class.web_service_api
- when :delegated, :layered
- @controller.web_service_object(service_name.to_sym).class.web_service_api
- end
- end
-
- def protocol
- if @protocol.nil?
- @protocol ||= ActionWebService::Protocol::Soap::SoapProtocol.create(@controller)
- else
- case @protocol
- when :xmlrpc
- @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.create(@controller)
- when :soap
- @protocol = ActionWebService::Protocol::Soap::SoapProtocol.create(@controller)
- else
- @protocol
- end
- end
- end
-
- def is_exception?(obj)
- case protocol
- when :soap, ActionWebService::Protocol::Soap::SoapProtocol
- (obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && \
- obj.detail.cause.is_a?(Exception)) ? obj.detail.cause : nil
- when :xmlrpc, ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
- obj.is_a?(XMLRPC::FaultException) ? obj : nil
- end
- end
- end
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/version.rb b/vendor/plugins/actionwebservice/lib/action_web_service/version.rb
deleted file mode 100644
index a1b3d5929..000000000
--- a/vendor/plugins/actionwebservice/lib/action_web_service/version.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module ActionWebService
- module VERSION #:nodoc:
- MAJOR = 1
- MINOR = 2
- TINY = 5
-
- STRING = [MAJOR, MINOR, TINY].join('.')
- end
-end
diff --git a/vendor/plugins/actionwebservice/lib/actionwebservice.rb b/vendor/plugins/actionwebservice/lib/actionwebservice.rb
deleted file mode 100644
index 25e3aa8e8..000000000
--- a/vendor/plugins/actionwebservice/lib/actionwebservice.rb
+++ /dev/null
@@ -1 +0,0 @@
-require 'action_web_service'
diff --git a/vendor/plugins/actionwebservice/setup.rb b/vendor/plugins/actionwebservice/setup.rb
deleted file mode 100644
index aeef0d106..000000000
--- a/vendor/plugins/actionwebservice/setup.rb
+++ /dev/null
@@ -1,1379 +0,0 @@
-#
-# setup.rb
-#
-# Copyright (c) 2000-2004 Minero Aoki
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
-# with permission of Minero Aoki.
-
-#
-
-unless Enumerable.method_defined?(:map) # Ruby 1.4.6
- module Enumerable
- alias map collect
- end
-end
-
-unless File.respond_to?(:read) # Ruby 1.6
- def File.read(fname)
- open(fname) {|f|
- return f.read
- }
- end
-end
-
-def File.binread(fname)
- open(fname, 'rb') {|f|
- return f.read
- }
-end
-
-# for corrupted windows stat(2)
-def File.dir?(path)
- File.directory?((path[-1,1] == '/') ? path : path + '/')
-end
-
-
-class SetupError < StandardError; end
-
-def setup_rb_error(msg)
- raise SetupError, msg
-end
-
-#
-# Config
-#
-
-if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
- ARGV.delete(arg)
- require arg.split(/=/, 2)[1]
- $".push 'rbconfig.rb'
-else
- require 'rbconfig'
-end
-
-def multipackage_install?
- FileTest.directory?(File.dirname($0) + '/packages')
-end
-
-
-class ConfigItem
- def initialize(name, template, default, desc)
- @name = name.freeze
- @template = template
- @value = default
- @default = default.dup.freeze
- @description = desc
- end
-
- attr_reader :name
- attr_reader :description
-
- attr_accessor :default
- alias help_default default
-
- def help_opt
- "--#{@name}=#{@template}"
- end
-
- def value
- @value
- end
-
- def eval(table)
- @value.gsub(%r<\$([^/]+)>) { table[$1] }
- end
-
- def set(val)
- @value = check(val)
- end
-
- private
-
- def check(val)
- setup_rb_error "config: --#{name} requires argument" unless val
- val
- end
-end
-
-class BoolItem < ConfigItem
- def config_type
- 'bool'
- end
-
- def help_opt
- "--#{@name}"
- end
-
- private
-
- def check(val)
- return 'yes' unless val
- unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val
- setup_rb_error "config: --#{@name} accepts only yes/no for argument"
- end
- (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
- end
-end
-
-class PathItem < ConfigItem
- def config_type
- 'path'
- end
-
- private
-
- def check(path)
- setup_rb_error "config: --#{@name} requires argument" unless path
- path[0,1] == '$' ? path : File.expand_path(path)
- end
-end
-
-class ProgramItem < ConfigItem
- def config_type
- 'program'
- end
-end
-
-class SelectItem < ConfigItem
- def initialize(name, template, default, desc)
- super
- @ok = template.split('/')
- end
-
- def config_type
- 'select'
- end
-
- private
-
- def check(val)
- unless @ok.include?(val.strip)
- setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
- end
- val.strip
- end
-end
-
-class PackageSelectionItem < ConfigItem
- def initialize(name, template, default, help_default, desc)
- super name, template, default, desc
- @help_default = help_default
- end
-
- attr_reader :help_default
-
- def config_type
- 'package'
- end
-
- private
-
- def check(val)
- unless File.dir?("packages/#{val}")
- setup_rb_error "config: no such package: #{val}"
- end
- val
- end
-end
-
-class ConfigTable_class
-
- def initialize(items)
- @items = items
- @table = {}
- items.each do |i|
- @table[i.name] = i
- end
- ALIASES.each do |ali, name|
- @table[ali] = @table[name]
- end
- end
-
- include Enumerable
-
- def each(&block)
- @items.each(&block)
- end
-
- def key?(name)
- @table.key?(name)
- end
-
- def lookup(name)
- @table[name] or raise ArgumentError, "no such config item: #{name}"
- end
-
- def add(item)
- @items.push item
- @table[item.name] = item
- end
-
- def remove(name)
- item = lookup(name)
- @items.delete_if {|i| i.name == name }
- @table.delete_if {|name, i| i.name == name }
- item
- end
-
- def new
- dup()
- end
-
- def savefile
- '.config'
- end
-
- def load
- begin
- t = dup()
- File.foreach(savefile()) do |line|
- k, v = *line.split(/=/, 2)
- t[k] = v.strip
- end
- t
- rescue Errno::ENOENT
- setup_rb_error $!.message + "#{File.basename($0)} config first"
- end
- end
-
- def save
- @items.each {|i| i.value }
- File.open(savefile(), 'w') {|f|
- @items.each do |i|
- f.printf "%s=%s\n", i.name, i.value if i.value
- end
- }
- end
-
- def [](key)
- lookup(key).eval(self)
- end
-
- def []=(key, val)
- lookup(key).set val
- end
-
-end
-
-c = ::Config::CONFIG
-
-rubypath = c['bindir'] + '/' + c['ruby_install_name']
-
-major = c['MAJOR'].to_i
-minor = c['MINOR'].to_i
-teeny = c['TEENY'].to_i
-version = "#{major}.#{minor}"
-
-# ruby ver. >= 1.4.4?
-newpath_p = ((major >= 2) or
- ((major == 1) and
- ((minor >= 5) or
- ((minor == 4) and (teeny >= 4)))))
-
-if c['rubylibdir']
- # V < 1.6.3
- _stdruby = c['rubylibdir']
- _siteruby = c['sitedir']
- _siterubyver = c['sitelibdir']
- _siterubyverarch = c['sitearchdir']
-elsif newpath_p
- # 1.4.4 <= V <= 1.6.3
- _stdruby = "$prefix/lib/ruby/#{version}"
- _siteruby = c['sitedir']
- _siterubyver = "$siteruby/#{version}"
- _siterubyverarch = "$siterubyver/#{c['arch']}"
-else
- # V < 1.4.4
- _stdruby = "$prefix/lib/ruby/#{version}"
- _siteruby = "$prefix/lib/ruby/#{version}/site_ruby"
- _siterubyver = _siteruby
- _siterubyverarch = "$siterubyver/#{c['arch']}"
-end
-libdir = '-* dummy libdir *-'
-stdruby = '-* dummy rubylibdir *-'
-siteruby = '-* dummy site_ruby *-'
-siterubyver = '-* dummy site_ruby version *-'
-parameterize = lambda {|path|
- path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\
- .sub(/\A#{Regexp.quote(libdir)}/, '$libdir')\
- .sub(/\A#{Regexp.quote(stdruby)}/, '$stdruby')\
- .sub(/\A#{Regexp.quote(siteruby)}/, '$siteruby')\
- .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver')
-}
-libdir = parameterize.call(c['libdir'])
-stdruby = parameterize.call(_stdruby)
-siteruby = parameterize.call(_siteruby)
-siterubyver = parameterize.call(_siterubyver)
-siterubyverarch = parameterize.call(_siterubyverarch)
-
-if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
- makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
-else
- makeprog = 'make'
-end
-
-common_conf = [
- PathItem.new('prefix', 'path', c['prefix'],
- 'path prefix of target environment'),
- PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
- 'the directory for commands'),
- PathItem.new('libdir', 'path', libdir,
- 'the directory for libraries'),
- PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
- 'the directory for shared data'),
- PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
- 'the directory for man pages'),
- PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
- 'the directory for man pages'),
- PathItem.new('stdruby', 'path', stdruby,
- 'the directory for standard ruby libraries'),
- PathItem.new('siteruby', 'path', siteruby,
- 'the directory for version-independent aux ruby libraries'),
- PathItem.new('siterubyver', 'path', siterubyver,
- 'the directory for aux ruby libraries'),
- PathItem.new('siterubyverarch', 'path', siterubyverarch,
- 'the directory for aux ruby binaries'),
- PathItem.new('rbdir', 'path', '$siterubyver',
- 'the directory for ruby scripts'),
- PathItem.new('sodir', 'path', '$siterubyverarch',
- 'the directory for ruby extentions'),
- PathItem.new('rubypath', 'path', rubypath,
- 'the path to set to #! line'),
- ProgramItem.new('rubyprog', 'name', rubypath,
- 'the ruby program using for installation'),
- ProgramItem.new('makeprog', 'name', makeprog,
- 'the make program to compile ruby extentions'),
- SelectItem.new('shebang', 'all/ruby/never', 'ruby',
- 'shebang line (#!) editing mode'),
- BoolItem.new('without-ext', 'yes/no', 'no',
- 'does not compile/install ruby extentions')
-]
-class ConfigTable_class # open again
- ALIASES = {
- 'std-ruby' => 'stdruby',
- 'site-ruby-common' => 'siteruby', # For backward compatibility
- 'site-ruby' => 'siterubyver', # For backward compatibility
- 'bin-dir' => 'bindir',
- 'bin-dir' => 'bindir',
- 'rb-dir' => 'rbdir',
- 'so-dir' => 'sodir',
- 'data-dir' => 'datadir',
- 'ruby-path' => 'rubypath',
- 'ruby-prog' => 'rubyprog',
- 'ruby' => 'rubyprog',
- 'make-prog' => 'makeprog',
- 'make' => 'makeprog'
- }
-end
-multipackage_conf = [
- PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
- 'package names that you want to install'),
- PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
- 'package names that you do not want to install')
-]
-if multipackage_install?
- ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf)
-else
- ConfigTable = ConfigTable_class.new(common_conf)
-end
-
-
-module MetaConfigAPI
-
- def eval_file_ifexist(fname)
- instance_eval File.read(fname), fname, 1 if File.file?(fname)
- end
-
- def config_names
- ConfigTable.map {|i| i.name }
- end
-
- def config?(name)
- ConfigTable.key?(name)
- end
-
- def bool_config?(name)
- ConfigTable.lookup(name).config_type == 'bool'
- end
-
- def path_config?(name)
- ConfigTable.lookup(name).config_type == 'path'
- end
-
- def value_config?(name)
- case ConfigTable.lookup(name).config_type
- when 'bool', 'path'
- true
- else
- false
- end
- end
-
- def add_config(item)
- ConfigTable.add item
- end
-
- def add_bool_config(name, default, desc)
- ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
- end
-
- def add_path_config(name, default, desc)
- ConfigTable.add PathItem.new(name, 'path', default, desc)
- end
-
- def set_config_default(name, default)
- ConfigTable.lookup(name).default = default
- end
-
- def remove_config(name)
- ConfigTable.remove(name)
- end
-
-end
-
-
-#
-# File Operations
-#
-
-module FileOperations
-
- def mkdir_p(dirname, prefix = nil)
- dirname = prefix + File.expand_path(dirname) if prefix
- $stderr.puts "mkdir -p #{dirname}" if verbose?
- return if no_harm?
-
- # does not check '/'... it's too abnormal case
- dirs = File.expand_path(dirname).split(%r<(?=/)>)
- if /\A[a-z]:\z/i =~ dirs[0]
- disk = dirs.shift
- dirs[0] = disk + dirs[0]
- end
- dirs.each_index do |idx|
- path = dirs[0..idx].join('')
- Dir.mkdir path unless File.dir?(path)
- end
- end
-
- def rm_f(fname)
- $stderr.puts "rm -f #{fname}" if verbose?
- return if no_harm?
-
- if File.exist?(fname) or File.symlink?(fname)
- File.chmod 0777, fname
- File.unlink fname
- end
- end
-
- def rm_rf(dn)
- $stderr.puts "rm -rf #{dn}" if verbose?
- return if no_harm?
-
- Dir.chdir dn
- Dir.foreach('.') do |fn|
- next if fn == '.'
- next if fn == '..'
- if File.dir?(fn)
- verbose_off {
- rm_rf fn
- }
- else
- verbose_off {
- rm_f fn
- }
- end
- end
- Dir.chdir '..'
- Dir.rmdir dn
- end
-
- def move_file(src, dest)
- File.unlink dest if File.exist?(dest)
- begin
- File.rename src, dest
- rescue
- File.open(dest, 'wb') {|f| f.write File.binread(src) }
- File.chmod File.stat(src).mode, dest
- File.unlink src
- end
- end
-
- def install(from, dest, mode, prefix = nil)
- $stderr.puts "install #{from} #{dest}" if verbose?
- return if no_harm?
-
- realdest = prefix ? prefix + File.expand_path(dest) : dest
- realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
- str = File.binread(from)
- if diff?(str, realdest)
- verbose_off {
- rm_f realdest if File.exist?(realdest)
- }
- File.open(realdest, 'wb') {|f|
- f.write str
- }
- File.chmod mode, realdest
-
- File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
- if prefix
- f.puts realdest.sub(prefix, '')
- else
- f.puts realdest
- end
- }
- end
- end
-
- def diff?(new_content, path)
- return true unless File.exist?(path)
- new_content != File.binread(path)
- end
-
- def command(str)
- $stderr.puts str if verbose?
- system str or raise RuntimeError, "'system #{str}' failed"
- end
-
- def ruby(str)
- command config('rubyprog') + ' ' + str
- end
-
- def make(task = '')
- command config('makeprog') + ' ' + task
- end
-
- def extdir?(dir)
- File.exist?(dir + '/MANIFEST')
- end
-
- def all_files_in(dirname)
- Dir.open(dirname) {|d|
- return d.select {|ent| File.file?("#{dirname}/#{ent}") }
- }
- end
-
- REJECT_DIRS = %w(
- CVS SCCS RCS CVS.adm .svn
- )
-
- def all_dirs_in(dirname)
- Dir.open(dirname) {|d|
- return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
- }
- end
-
-end
-
-
-#
-# Main Installer
-#
-
-module HookUtils
-
- def run_hook(name)
- try_run_hook "#{curr_srcdir()}/#{name}" or
- try_run_hook "#{curr_srcdir()}/#{name}.rb"
- end
-
- def try_run_hook(fname)
- return false unless File.file?(fname)
- begin
- instance_eval File.read(fname), fname, 1
- rescue
- setup_rb_error "hook #{fname} failed:\n" + $!.message
- end
- true
- end
-
-end
-
-
-module HookScriptAPI
-
- def get_config(key)
- @config[key]
- end
-
- alias config get_config
-
- def set_config(key, val)
- @config[key] = val
- end
-
- #
- # srcdir/objdir (works only in the package directory)
- #
-
- #abstract srcdir_root
- #abstract objdir_root
- #abstract relpath
-
- def curr_srcdir
- "#{srcdir_root()}/#{relpath()}"
- end
-
- def curr_objdir
- "#{objdir_root()}/#{relpath()}"
- end
-
- def srcfile(path)
- "#{curr_srcdir()}/#{path}"
- end
-
- def srcexist?(path)
- File.exist?(srcfile(path))
- end
-
- def srcdirectory?(path)
- File.dir?(srcfile(path))
- end
-
- def srcfile?(path)
- File.file? srcfile(path)
- end
-
- def srcentries(path = '.')
- Dir.open("#{curr_srcdir()}/#{path}") {|d|
- return d.to_a - %w(. ..)
- }
- end
-
- def srcfiles(path = '.')
- srcentries(path).select {|fname|
- File.file?(File.join(curr_srcdir(), path, fname))
- }
- end
-
- def srcdirectories(path = '.')
- srcentries(path).select {|fname|
- File.dir?(File.join(curr_srcdir(), path, fname))
- }
- end
-
-end
-
-
-class ToplevelInstaller
-
- Version = '3.3.1'
- Copyright = 'Copyright (c) 2000-2004 Minero Aoki'
-
- TASKS = [
- [ 'all', 'do config, setup, then install' ],
- [ 'config', 'saves your configurations' ],
- [ 'show', 'shows current configuration' ],
- [ 'setup', 'compiles ruby extentions and others' ],
- [ 'install', 'installs files' ],
- [ 'clean', "does `make clean' for each extention" ],
- [ 'distclean',"does `make distclean' for each extention" ]
- ]
-
- def ToplevelInstaller.invoke
- instance().invoke
- end
-
- @singleton = nil
-
- def ToplevelInstaller.instance
- @singleton ||= new(File.dirname($0))
- @singleton
- end
-
- include MetaConfigAPI
-
- def initialize(ardir_root)
- @config = nil
- @options = { 'verbose' => true }
- @ardir = File.expand_path(ardir_root)
- end
-
- def inspect
- "#<#{self.class} #{__id__()}>"
- end
-
- def invoke
- run_metaconfigs
- case task = parsearg_global()
- when nil, 'all'
- @config = load_config('config')
- parsearg_config
- init_installers
- exec_config
- exec_setup
- exec_install
- else
- @config = load_config(task)
- __send__ "parsearg_#{task}"
- init_installers
- __send__ "exec_#{task}"
- end
- end
-
- def run_metaconfigs
- eval_file_ifexist "#{@ardir}/metaconfig"
- end
-
- def load_config(task)
- case task
- when 'config'
- ConfigTable.new
- when 'clean', 'distclean'
- if File.exist?(ConfigTable.savefile)
- then ConfigTable.load
- else ConfigTable.new
- end
- else
- ConfigTable.load
- end
- end
-
- def init_installers
- @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
- end
-
- #
- # Hook Script API bases
- #
-
- def srcdir_root
- @ardir
- end
-
- def objdir_root
- '.'
- end
-
- def relpath
- '.'
- end
-
- #
- # Option Parsing
- #
-
- def parsearg_global
- valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/
-
- while arg = ARGV.shift
- case arg
- when /\A\w+\z/
- setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg
- return arg
-
- when '-q', '--quiet'
- @options['verbose'] = false
-
- when '--verbose'
- @options['verbose'] = true
-
- when '-h', '--help'
- print_usage $stdout
- exit 0
-
- when '-v', '--version'
- puts "#{File.basename($0)} version #{Version}"
- exit 0
-
- when '--copyright'
- puts Copyright
- exit 0
-
- else
- setup_rb_error "unknown global option '#{arg}'"
- end
- end
-
- nil
- end
-
-
- def parsearg_no_options
- unless ARGV.empty?
- setup_rb_error "#{task}: unknown options: #{ARGV.join ' '}"
- end
- end
-
- alias parsearg_show parsearg_no_options
- alias parsearg_setup parsearg_no_options
- alias parsearg_clean parsearg_no_options
- alias parsearg_distclean parsearg_no_options
-
- def parsearg_config
- re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/
- @options['config-opt'] = []
-
- while i = ARGV.shift
- if /\A--?\z/ =~ i
- @options['config-opt'] = ARGV.dup
- break
- end
- m = re.match(i) or setup_rb_error "config: unknown option #{i}"
- name, value = *m.to_a[1,2]
- @config[name] = value
- end
- end
-
- def parsearg_install
- @options['no-harm'] = false
- @options['install-prefix'] = ''
- while a = ARGV.shift
- case a
- when /\A--no-harm\z/
- @options['no-harm'] = true
- when /\A--prefix=(.*)\z/
- path = $1
- path = File.expand_path(path) unless path[0,1] == '/'
- @options['install-prefix'] = path
- else
- setup_rb_error "install: unknown option #{a}"
- end
- end
- end
-
- def print_usage(out)
- out.puts 'Typical Installation Procedure:'
- out.puts " $ ruby #{File.basename $0} config"
- out.puts " $ ruby #{File.basename $0} setup"
- out.puts " # ruby #{File.basename $0} install (may require root privilege)"
- out.puts
- out.puts 'Detailed Usage:'
- out.puts " ruby #{File.basename $0} <global option>"
- out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
-
- fmt = " %-24s %s\n"
- out.puts
- out.puts 'Global options:'
- out.printf fmt, '-q,--quiet', 'suppress message outputs'
- out.printf fmt, ' --verbose', 'output messages verbosely'
- out.printf fmt, '-h,--help', 'print this message'
- out.printf fmt, '-v,--version', 'print version and quit'
- out.printf fmt, ' --copyright', 'print copyright and quit'
- out.puts
- out.puts 'Tasks:'
- TASKS.each do |name, desc|
- out.printf fmt, name, desc
- end
-
- fmt = " %-24s %s [%s]\n"
- out.puts
- out.puts 'Options for CONFIG or ALL:'
- ConfigTable.each do |item|
- out.printf fmt, item.help_opt, item.description, item.help_default
- end
- out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
- out.puts
- out.puts 'Options for INSTALL:'
- out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
- out.printf fmt, '--prefix=path', 'install path prefix', '$prefix'
- out.puts
- end
-
- #
- # Task Handlers
- #
-
- def exec_config
- @installer.exec_config
- @config.save # must be final
- end
-
- def exec_setup
- @installer.exec_setup
- end
-
- def exec_install
- @installer.exec_install
- end
-
- def exec_show
- ConfigTable.each do |i|
- printf "%-20s %s\n", i.name, i.value
- end
- end
-
- def exec_clean
- @installer.exec_clean
- end
-
- def exec_distclean
- @installer.exec_distclean
- end
-
-end
-
-
-class ToplevelInstallerMulti < ToplevelInstaller
-
- include HookUtils
- include HookScriptAPI
- include FileOperations
-
- def initialize(ardir)
- super
- @packages = all_dirs_in("#{@ardir}/packages")
- raise 'no package exists' if @packages.empty?
- end
-
- def run_metaconfigs
- eval_file_ifexist "#{@ardir}/metaconfig"
- @packages.each do |name|
- eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
- end
- end
-
- def init_installers
- @installers = {}
- @packages.each do |pack|
- @installers[pack] = Installer.new(@config, @options,
- "#{@ardir}/packages/#{pack}",
- "packages/#{pack}")
- end
-
- with = extract_selection(config('with'))
- without = extract_selection(config('without'))
- @selected = @installers.keys.select {|name|
- (with.empty? or with.include?(name)) \
- and not without.include?(name)
- }
- end
-
- def extract_selection(list)
- a = list.split(/,/)
- a.each do |name|
- setup_rb_error "no such package: #{name}" unless @installers.key?(name)
- end
- a
- end
-
- def print_usage(f)
- super
- f.puts 'Included packages:'
- f.puts ' ' + @packages.sort.join(' ')
- f.puts
- end
-
- #
- # multi-package metaconfig API
- #
-
- attr_reader :packages
-
- def declare_packages(list)
- raise 'package list is empty' if list.empty?
- list.each do |name|
- raise "directory packages/#{name} does not exist"\
- unless File.dir?("#{@ardir}/packages/#{name}")
- end
- @packages = list
- end
-
- #
- # Task Handlers
- #
-
- def exec_config
- run_hook 'pre-config'
- each_selected_installers {|inst| inst.exec_config }
- run_hook 'post-config'
- @config.save # must be final
- end
-
- def exec_setup
- run_hook 'pre-setup'
- each_selected_installers {|inst| inst.exec_setup }
- run_hook 'post-setup'
- end
-
- def exec_install
- run_hook 'pre-install'
- each_selected_installers {|inst| inst.exec_install }
- run_hook 'post-install'
- end
-
- def exec_clean
- rm_f ConfigTable.savefile
- run_hook 'pre-clean'
- each_selected_installers {|inst| inst.exec_clean }
- run_hook 'post-clean'
- end
-
- def exec_distclean
- rm_f ConfigTable.savefile
- run_hook 'pre-distclean'
- each_selected_installers {|inst| inst.exec_distclean }
- run_hook 'post-distclean'
- end
-
- #
- # lib
- #
-
- def each_selected_installers
- Dir.mkdir 'packages' unless File.dir?('packages')
- @selected.each do |pack|
- $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
- Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
- Dir.chdir "packages/#{pack}"
- yield @installers[pack]
- Dir.chdir '../..'
- end
- end
-
- def verbose?
- @options['verbose']
- end
-
- def no_harm?
- @options['no-harm']
- end
-
-end
-
-
-class Installer
-
- FILETYPES = %w( bin lib ext data )
-
- include HookScriptAPI
- include HookUtils
- include FileOperations
-
- def initialize(config, opt, srcroot, objroot)
- @config = config
- @options = opt
- @srcdir = File.expand_path(srcroot)
- @objdir = File.expand_path(objroot)
- @currdir = '.'
- end
-
- def inspect
- "#<#{self.class} #{File.basename(@srcdir)}>"
- end
-
- #
- # Hook Script API base methods
- #
-
- def srcdir_root
- @srcdir
- end
-
- def objdir_root
- @objdir
- end
-
- def relpath
- @currdir
- end
-
- #
- # configs/options
- #
-
- def no_harm?
- @options['no-harm']
- end
-
- def verbose?
- @options['verbose']
- end
-
- def verbose_off
- begin
- save, @options['verbose'] = @options['verbose'], false
- yield
- ensure
- @options['verbose'] = save
- end
- end
-
- #
- # TASK config
- #
-
- def exec_config
- exec_task_traverse 'config'
- end
-
- def config_dir_bin(rel)
- end
-
- def config_dir_lib(rel)
- end
-
- def config_dir_ext(rel)
- extconf if extdir?(curr_srcdir())
- end
-
- def extconf
- opt = @options['config-opt'].join(' ')
- command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}"
- end
-
- def config_dir_data(rel)
- end
-
- #
- # TASK setup
- #
-
- def exec_setup
- exec_task_traverse 'setup'
- end
-
- def setup_dir_bin(rel)
- all_files_in(curr_srcdir()).each do |fname|
- adjust_shebang "#{curr_srcdir()}/#{fname}"
- end
- end
-
- def adjust_shebang(path)
- return if no_harm?
- tmpfile = File.basename(path) + '.tmp'
- begin
- File.open(path, 'rb') {|r|
- first = r.gets
- return unless File.basename(config('rubypath')) == 'ruby'
- return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby'
- $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose?
- File.open(tmpfile, 'wb') {|w|
- w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath'))
- w.write r.read
- }
- move_file tmpfile, File.basename(path)
- }
- ensure
- File.unlink tmpfile if File.exist?(tmpfile)
- end
- end
-
- def setup_dir_lib(rel)
- end
-
- def setup_dir_ext(rel)
- make if extdir?(curr_srcdir())
- end
-
- def setup_dir_data(rel)
- end
-
- #
- # TASK install
- #
-
- def exec_install
- rm_f 'InstalledFiles'
- exec_task_traverse 'install'
- end
-
- def install_dir_bin(rel)
- install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755
- end
-
- def install_dir_lib(rel)
- install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644
- end
-
- def install_dir_ext(rel)
- return unless extdir?(curr_srcdir())
- install_files ruby_extentions('.'),
- "#{config('sodir')}/#{File.dirname(rel)}",
- 0555
- end
-
- def install_dir_data(rel)
- install_files collect_filenames_auto(), "#{config('datadir')}/#{rel}", 0644
- end
-
- def install_files(list, dest, mode)
- mkdir_p dest, @options['install-prefix']
- list.each do |fname|
- install fname, dest, mode, @options['install-prefix']
- end
- end
-
- def ruby_scripts
- collect_filenames_auto().select {|n| /\.rb\z/ =~ n }
- end
-
- # picked up many entries from cvs-1.11.1/src/ignore.c
- reject_patterns = %w(
- core RCSLOG tags TAGS .make.state
- .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
- *~ *.old *.bak *.BAK *.orig *.rej _$* *$
-
- *.org *.in .*
- )
- mapping = {
- '.' => '\.',
- '$' => '\$',
- '#' => '\#',
- '*' => '.*'
- }
- REJECT_PATTERNS = Regexp.new('\A(?:' +
- reject_patterns.map {|pat|
- pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] }
- }.join('|') +
- ')\z')
-
- def collect_filenames_auto
- mapdir((existfiles() - hookfiles()).reject {|fname|
- REJECT_PATTERNS =~ fname
- })
- end
-
- def existfiles
- all_files_in(curr_srcdir()) | all_files_in('.')
- end
-
- def hookfiles
- %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
- %w( config setup install clean ).map {|t| sprintf(fmt, t) }
- }.flatten
- end
-
- def mapdir(filelist)
- filelist.map {|fname|
- if File.exist?(fname) # objdir
- fname
- else # srcdir
- File.join(curr_srcdir(), fname)
- end
- }
- end
-
- def ruby_extentions(dir)
- Dir.open(dir) {|d|
- ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname }
- if ents.empty?
- setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
- end
- return ents
- }
- end
-
- #
- # TASK clean
- #
-
- def exec_clean
- exec_task_traverse 'clean'
- rm_f ConfigTable.savefile
- rm_f 'InstalledFiles'
- end
-
- def clean_dir_bin(rel)
- end
-
- def clean_dir_lib(rel)
- end
-
- def clean_dir_ext(rel)
- return unless extdir?(curr_srcdir())
- make 'clean' if File.file?('Makefile')
- end
-
- def clean_dir_data(rel)
- end
-
- #
- # TASK distclean
- #
-
- def exec_distclean
- exec_task_traverse 'distclean'
- rm_f ConfigTable.savefile
- rm_f 'InstalledFiles'
- end
-
- def distclean_dir_bin(rel)
- end
-
- def distclean_dir_lib(rel)
- end
-
- def distclean_dir_ext(rel)
- return unless extdir?(curr_srcdir())
- make 'distclean' if File.file?('Makefile')
- end
-
- #
- # lib
- #
-
- def exec_task_traverse(task)
- run_hook "pre-#{task}"
- FILETYPES.each do |type|
- if config('without-ext') == 'yes' and type == 'ext'
- $stderr.puts 'skipping ext/* by user option' if verbose?
- next
- end
- traverse task, type, "#{task}_dir_#{type}"
- end
- run_hook "post-#{task}"
- end
-
- def traverse(task, rel, mid)
- dive_into(rel) {
- run_hook "pre-#{task}"
- __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
- all_dirs_in(curr_srcdir()).each do |d|
- traverse task, "#{rel}/#{d}", mid
- end
- run_hook "post-#{task}"
- }
- end
-
- def dive_into(rel)
- return unless File.dir?("#{@srcdir}/#{rel}")
-
- dir = File.basename(rel)
- Dir.mkdir dir unless File.dir?(dir)
- prevdir = Dir.pwd
- Dir.chdir dir
- $stderr.puts '---> ' + rel if verbose?
- @currdir = rel
- yield
- Dir.chdir prevdir
- $stderr.puts '<--- ' + rel if verbose?
- @currdir = File.dirname(rel)
- end
-
-end
-
-
-if $0 == __FILE__
- begin
- if multipackage_install?
- ToplevelInstallerMulti.invoke
- else
- ToplevelInstaller.invoke
- end
- rescue SetupError
- raise if $DEBUG
- $stderr.puts $!.message
- $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
- exit 1
- end
-end
diff --git a/vendor/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb b/vendor/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb
index 7c4fac8b1..bbbf5814b 100644
--- a/vendor/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb
+++ b/vendor/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb
@@ -29,7 +29,7 @@ module Redmine
send :include, Redmine::Acts::ActivityProvider::InstanceMethods
end
- options.assert_valid_keys(:type, :permission, :timestamp, :find_options)
+ options.assert_valid_keys(:type, :permission, :timestamp, :author_key, :find_options)
self.activity_provider_options ||= {}
# One model can provide different event types
@@ -39,6 +39,7 @@ module Redmine
options[:permission] = "view_#{self.name.underscore.pluralize}".to_sym unless options.has_key?(:permission)
options[:timestamp] ||= "#{table_name}.created_on"
options[:find_options] ||= {}
+ options[:author_key] = "#{table_name}.#{options[:author_key]}" if options[:author_key].is_a?(Symbol)
self.activity_provider_options[event_type] = options
end
end
@@ -54,11 +55,25 @@ module Redmine
provider_options = activity_provider_options[event_type]
raise "#{self.name} can not provide #{event_type} events." if provider_options.nil?
- cond = ARCondition.new(["#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to])
+ scope_options = {}
+ cond = ARCondition.new
+ if from && to
+ cond.add(["#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to])
+ end
+ if options[:author]
+ return [] if provider_options[:author_key].nil?
+ cond.add(["#{provider_options[:author_key]} = ?", options[:author].id])
+ end
cond.add(Project.allowed_to_condition(user, provider_options[:permission], options)) if provider_options[:permission]
+ scope_options[:conditions] = cond.conditions
+ if options[:limit]
+ # id and creation time should be in same order in most cases
+ scope_options[:order] = "#{table_name}.id DESC"
+ scope_options[:limit] = options[:limit]
+ end
- with_scope(:find => { :conditions => cond.conditions }) do
- find(:all, provider_options[:find_options])
+ with_scope(:find => scope_options) do
+ find(:all, provider_options[:find_options].dup)
end
end
end
diff --git a/vendor/plugins/acts_as_attachable/init.rb b/vendor/plugins/acts_as_attachable/init.rb
new file mode 100644
index 000000000..213e1d4b1
--- /dev/null
+++ b/vendor/plugins/acts_as_attachable/init.rb
@@ -0,0 +1,2 @@
+require File.dirname(__FILE__) + '/lib/acts_as_attachable'
+ActiveRecord::Base.send(:include, Redmine::Acts::Attachable)
diff --git a/vendor/plugins/acts_as_attachable/lib/acts_as_attachable.rb b/vendor/plugins/acts_as_attachable/lib/acts_as_attachable.rb
new file mode 100644
index 000000000..78d42c215
--- /dev/null
+++ b/vendor/plugins/acts_as_attachable/lib/acts_as_attachable.rb
@@ -0,0 +1,57 @@
+# 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 Acts
+ module Attachable
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ def acts_as_attachable(options = {})
+ cattr_accessor :attachable_options
+ self.attachable_options = {}
+ attachable_options[:view_permission] = options.delete(:view_permission) || "view_#{self.name.pluralize.underscore}".to_sym
+ attachable_options[:delete_permission] = options.delete(:delete_permission) || "edit_#{self.name.pluralize.underscore}".to_sym
+
+ has_many :attachments, options.merge(:as => :container,
+ :order => "#{Attachment.table_name}.created_on",
+ :dependent => :destroy)
+ send :include, Redmine::Acts::Attachable::InstanceMethods
+ end
+ end
+
+ module InstanceMethods
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ def attachments_visible?(user=User.current)
+ user.allowed_to?(self.class.attachable_options[:view_permission], self.project)
+ end
+
+ def attachments_deletable?(user=User.current)
+ user.allowed_to?(self.class.attachable_options[:delete_permission], self.project)
+ end
+
+ module ClassMethods
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/plugins/acts_as_watchable/lib/acts_as_watchable.rb b/vendor/plugins/acts_as_watchable/lib/acts_as_watchable.rb
index 2cb122795..3d0de0dca 100644
--- a/vendor/plugins/acts_as_watchable/lib/acts_as_watchable.rb
+++ b/vendor/plugins/acts_as_watchable/lib/acts_as_watchable.rb
@@ -14,6 +14,8 @@ module Redmine
class_eval do
has_many :watchers, :as => :watchable, :dependent => :delete_all
has_many :watcher_users, :through => :watchers, :source => :user
+
+ attr_protected :watcher_ids, :watcher_user_ids
end
end
end
diff --git a/vendor/plugins/actionwebservice/MIT-LICENSE b/vendor/plugins/awesome_nested_set/MIT-LICENSE
index 528941e84..570ecf870 100644
--- a/vendor/plugins/actionwebservice/MIT-LICENSE
+++ b/vendor/plugins/awesome_nested_set/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (C) 2005 Leon Breedt
+Copyright (c) 2007 [name of plugin creator]
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -18,4 +18,3 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
diff --git a/vendor/plugins/awesome_nested_set/README.rdoc b/vendor/plugins/awesome_nested_set/README.rdoc
new file mode 100644
index 000000000..c093f751d
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/README.rdoc
@@ -0,0 +1,64 @@
+= AwesomeNestedSet
+
+Awesome Nested Set is an implementation of the nested set pattern for ActiveRecord models. It is replacement for acts_as_nested_set and BetterNestedSet, but awesomer.
+
+== What makes this so awesome?
+
+This is a new implementation of nested set based off of BetterNestedSet that fixes some bugs, removes tons of duplication, adds a few useful methods, and adds STI support.
+
+== Installation
+
+If you are on Rails 2.1 or later:
+
+ script/plugin install git://github.com/collectiveidea/awesome_nested_set.git
+
+== Usage
+
+To make use of awesome_nested_set, your model needs to have 3 fields: lft, rgt, and parent_id:
+
+ class CreateCategories < ActiveRecord::Migration
+ def self.up
+ create_table :categories do |t|
+ t.string :name
+ t.integer :parent_id
+ t.integer :lft
+ t.integer :rgt
+ end
+ end
+
+ def self.down
+ drop_table :categories
+ end
+ end
+
+Enable the nested set functionality by declaring acts_as_nested_set on your model
+
+ class Category < ActiveRecord::Base
+ acts_as_nested_set
+ end
+
+Run `rake rdoc` to generate the API docs and see CollectiveIdea::Acts::NestedSet::SingletonMethods for more info.
+
+== View Helper
+
+The view helper is called #nested_set_options.
+
+Example usage:
+
+ <%= f.select :parent_id, nested_set_options(Category, @category) {|i| "#{'-' * i.level} #{i.name}" } %>
+
+ <%= select_tag 'parent_id', options_for_select(nested_set_options(Category) {|i| "#{'-' * i.level} #{i.name}" } ) %>
+
+See CollectiveIdea::Acts::NestedSet::Helper for more information about the helpers.
+
+== References
+
+You can learn more about nested sets at:
+
+ http://www.dbmsmag.com/9603d06.html
+ http://threebit.net/tutorials/nestedset/tutorial1.html
+ http://api.rubyonrails.com/classes/ActiveRecord/Acts/NestedSet/ClassMethods.html
+ http://opensource.symetrie.com/trac/better_nested_set/
+
+
+Copyright (c) 2008 Collective Idea, released under the MIT license \ No newline at end of file
diff --git a/vendor/plugins/awesome_nested_set/Rakefile b/vendor/plugins/awesome_nested_set/Rakefile
new file mode 100644
index 000000000..53906f68b
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/Rakefile
@@ -0,0 +1,46 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+require 'rake/gempackagetask'
+require 'rcov/rcovtask'
+require "load_multi_rails_rake_tasks"
+
+spec = eval(File.read("#{File.dirname(__FILE__)}/awesome_nested_set.gemspec"))
+PKG_NAME = spec.name
+PKG_VERSION = spec.version
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.need_zip = true
+ pkg.need_tar = true
+end
+
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the awesome_nested_set plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the awesome_nested_set plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'AwesomeNestedSet'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README.rdoc')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
+namespace :test do
+ desc "just rcov minus html output"
+ Rcov::RcovTask.new(:coverage) do |t|
+ # t.libs << 'test'
+ t.test_files = FileList['test/**/*_test.rb']
+ t.output_dir = 'coverage'
+ t.verbose = true
+ t.rcov_opts = %w(--exclude test,/usr/lib/ruby,/Library/Ruby,lib/awesome_nested_set/named_scope.rb --sort coverage)
+ end
+end \ No newline at end of file
diff --git a/vendor/plugins/awesome_nested_set/awesome_nested_set.gemspec b/vendor/plugins/awesome_nested_set/awesome_nested_set.gemspec
new file mode 100644
index 000000000..c5a1d49e5
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/awesome_nested_set.gemspec
@@ -0,0 +1,20 @@
+Gem::Specification.new do |s|
+ s.name = "awesome_nested_set"
+ s.version = "1.1.1"
+ s.summary = "An awesome replacement for acts_as_nested_set and better_nested_set."
+ s.description = s.summary
+
+ s.files = %w(init.rb MIT-LICENSE Rakefile README.rdoc lib/awesome_nested_set.rb lib/awesome_nested_set/compatability.rb lib/awesome_nested_set/helper.rb lib/awesome_nested_set/named_scope.rb rails/init.rb test/awesome_nested_set_test.rb test/test_helper.rb test/awesome_nested_set/helper_test.rb test/db/database.yml test/db/schema.rb test/fixtures/categories.yml test/fixtures/category.rb test/fixtures/departments.yml test/fixtures/notes.yml)
+
+ s.add_dependency "activerecord", ['>= 1.1']
+
+ s.has_rdoc = true
+ s.extra_rdoc_files = [ "README.rdoc"]
+ s.rdoc_options = ["--main", "README.rdoc", "--inline-source", "--line-numbers"]
+
+ s.test_files = %w(test/awesome_nested_set_test.rb test/test_helper.rb test/awesome_nested_set/helper_test.rb test/db/database.yml test/db/schema.rb test/fixtures/categories.yml test/fixtures/category.rb test/fixtures/departments.yml test/fixtures/notes.yml)
+ s.require_path = 'lib'
+ s.author = "Collective Idea"
+ s.email = "info@collectiveidea.com"
+ s.homepage = "http://collectiveidea.com"
+end
diff --git a/vendor/plugins/awesome_nested_set/init.rb b/vendor/plugins/awesome_nested_set/init.rb
new file mode 100644
index 000000000..43dc7c274
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/init.rb
@@ -0,0 +1 @@
+require File.dirname(__FILE__) + "/rails/init"
diff --git a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set.rb b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set.rb
new file mode 100644
index 000000000..fb67341b6
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set.rb
@@ -0,0 +1,547 @@
+module CollectiveIdea #:nodoc:
+ module Acts #:nodoc:
+ module NestedSet #:nodoc:
+ def self.included(base)
+ base.extend(SingletonMethods)
+ end
+
+ # This acts provides Nested Set functionality. Nested Set is a smart way to implement
+ # an _ordered_ tree, with the added feature that you can select the children and all of their
+ # descendants with a single query. The drawback is that insertion or move need some complex
+ # sql queries. But everything is done here by this module!
+ #
+ # Nested sets are appropriate each time you want either an orderd tree (menus,
+ # commercial categories) or an efficient way of querying big trees (threaded posts).
+ #
+ # == API
+ #
+ # Methods names are aligned with acts_as_tree as much as possible, to make replacment from one
+ # by another easier, except for the creation:
+ #
+ # in acts_as_tree:
+ # item.children.create(:name => "child1")
+ #
+ # in acts_as_nested_set:
+ # # adds a new item at the "end" of the tree, i.e. with child.left = max(tree.right)+1
+ # child = MyClass.new(:name => "child1")
+ # child.save
+ # # now move the item to its right place
+ # child.move_to_child_of my_item
+ #
+ # You can pass an id or an object to:
+ # * <tt>#move_to_child_of</tt>
+ # * <tt>#move_to_right_of</tt>
+ # * <tt>#move_to_left_of</tt>
+ #
+ module SingletonMethods
+ # Configuration options are:
+ #
+ # * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
+ # * +:left_column+ - column name for left boundry data, default "lft"
+ # * +:right_column+ - column name for right boundry data, default "rgt"
+ # * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
+ # (if it hasn't been already) and use that as the foreign key restriction. You
+ # can also pass an array to scope by multiple attributes.
+ # Example: <tt>acts_as_nested_set :scope => [:notable_id, :notable_type]</tt>
+ # * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the
+ # child objects are destroyed alongside this object by calling their destroy
+ # method. If set to :delete_all (default), all the child objects are deleted
+ # without calling their destroy method.
+ #
+ # See CollectiveIdea::Acts::NestedSet::ClassMethods for a list of class methods and
+ # CollectiveIdea::Acts::NestedSet::InstanceMethods for a list of instance methods added
+ # to acts_as_nested_set models
+ def acts_as_nested_set(options = {})
+ options = {
+ :parent_column => 'parent_id',
+ :left_column => 'lft',
+ :right_column => 'rgt',
+ :order => 'id',
+ :dependent => :delete_all, # or :destroy
+ }.merge(options)
+
+ if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
+ options[:scope] = "#{options[:scope]}_id".intern
+ end
+
+ write_inheritable_attribute :acts_as_nested_set_options, options
+ class_inheritable_reader :acts_as_nested_set_options
+
+ include Comparable
+ include Columns
+ include InstanceMethods
+ extend Columns
+ extend ClassMethods
+
+ # no bulk assignment
+ attr_protected left_column_name.intern,
+ right_column_name.intern,
+ parent_column_name.intern
+
+ before_create :set_default_left_and_right
+ before_destroy :prune_from_tree
+
+ # no assignment to structure fields
+ [left_column_name, right_column_name, parent_column_name].each do |column|
+ module_eval <<-"end_eval", __FILE__, __LINE__
+ def #{column}=(x)
+ raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
+ end
+ end_eval
+ end
+
+ named_scope :roots, :conditions => {parent_column_name => nil}, :order => quoted_left_column_name
+ named_scope :leaves, :conditions => "#{quoted_right_column_name} - #{quoted_left_column_name} = 1", :order => quoted_left_column_name
+ if self.respond_to?(:define_callbacks)
+ define_callbacks("before_move", "after_move")
+ end
+
+
+ end
+
+ end
+
+ module ClassMethods
+
+ # Returns the first root
+ def root
+ roots.find(:first)
+ end
+
+ def valid?
+ left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid?
+ end
+
+ def left_and_rights_valid?
+ count(
+ :joins => "LEFT OUTER JOIN #{quoted_table_name} AS parent ON " +
+ "#{quoted_table_name}.#{quoted_parent_column_name} = parent.#{primary_key}",
+ :conditions =>
+ "#{quoted_table_name}.#{quoted_left_column_name} IS NULL OR " +
+ "#{quoted_table_name}.#{quoted_right_column_name} IS NULL OR " +
+ "#{quoted_table_name}.#{quoted_left_column_name} >= " +
+ "#{quoted_table_name}.#{quoted_right_column_name} OR " +
+ "(#{quoted_table_name}.#{quoted_parent_column_name} IS NOT NULL AND " +
+ "(#{quoted_table_name}.#{quoted_left_column_name} <= parent.#{quoted_left_column_name} OR " +
+ "#{quoted_table_name}.#{quoted_right_column_name} >= parent.#{quoted_right_column_name}))"
+ ) == 0
+ end
+
+ def no_duplicates_for_columns?
+ scope_string = Array(acts_as_nested_set_options[:scope]).map do |c|
+ connection.quote_column_name(c)
+ end.push(nil).join(", ")
+ [quoted_left_column_name, quoted_right_column_name].all? do |column|
+ # No duplicates
+ find(:first,
+ :select => "#{scope_string}#{column}, COUNT(#{column})",
+ :group => "#{scope_string}#{column}
+ HAVING COUNT(#{column}) > 1").nil?
+ end
+ end
+
+ # Wrapper for each_root_valid? that can deal with scope.
+ def all_roots_valid?
+ if acts_as_nested_set_options[:scope]
+ roots(:group => scope_column_names).group_by{|record| scope_column_names.collect{|col| record.send(col.to_sym)}}.all? do |scope, grouped_roots|
+ each_root_valid?(grouped_roots)
+ end
+ else
+ each_root_valid?(roots)
+ end
+ end
+
+ def each_root_valid?(roots_to_validate)
+ left = right = 0
+ roots_to_validate.all? do |root|
+ returning(root.left > left && root.right > right) do
+ left = root.left
+ right = root.right
+ end
+ end
+ end
+
+ # Rebuilds the left & rights if unset or invalid. Also very useful for converting from acts_as_tree.
+ def rebuild!
+ # Don't rebuild a valid tree.
+ return true if valid?
+
+ scope = lambda{}
+ if acts_as_nested_set_options[:scope]
+ scope = lambda{|node|
+ scope_column_names.inject(""){|str, column_name|
+ str << "AND #{connection.quote_column_name(column_name)} = #{connection.quote(node.send(column_name.to_sym))} "
+ }
+ }
+ end
+ indices = {}
+
+ set_left_and_rights = lambda do |node|
+ # set left
+ node[left_column_name] = indices[scope.call(node)] += 1
+ # find
+ find(:all, :conditions => ["#{quoted_parent_column_name} = ? #{scope.call(node)}", node], :order => "#{quoted_left_column_name}, #{quoted_right_column_name}, #{acts_as_nested_set_options[:order]}").each{|n| set_left_and_rights.call(n) }
+ # set right
+ node[right_column_name] = indices[scope.call(node)] += 1
+ node.save!
+ end
+
+ # Find root node(s)
+ root_nodes = find(:all, :conditions => "#{quoted_parent_column_name} IS NULL", :order => "#{quoted_left_column_name}, #{quoted_right_column_name}, #{acts_as_nested_set_options[:order]}").each do |root_node|
+ # setup index for this scope
+ indices[scope.call(root_node)] ||= 0
+ set_left_and_rights.call(root_node)
+ end
+ end
+ end
+
+ # Mixed into both classes and instances to provide easy access to the column names
+ module Columns
+ def left_column_name
+ acts_as_nested_set_options[:left_column]
+ end
+
+ def right_column_name
+ acts_as_nested_set_options[:right_column]
+ end
+
+ def parent_column_name
+ acts_as_nested_set_options[:parent_column]
+ end
+
+ def scope_column_names
+ Array(acts_as_nested_set_options[:scope])
+ end
+
+ def quoted_left_column_name
+ connection.quote_column_name(left_column_name)
+ end
+
+ def quoted_right_column_name
+ connection.quote_column_name(right_column_name)
+ end
+
+ def quoted_parent_column_name
+ connection.quote_column_name(parent_column_name)
+ end
+
+ def quoted_scope_column_names
+ scope_column_names.collect {|column_name| connection.quote_column_name(column_name) }
+ end
+ end
+
+ # Any instance method that returns a collection makes use of Rails 2.1's named_scope (which is bundled for Rails 2.0), so it can be treated as a finder.
+ #
+ # category.self_and_descendants.count
+ # category.ancestors.find(:all, :conditions => "name like '%foo%'")
+ module InstanceMethods
+ # Value of the parent column
+ def parent_id
+ self[parent_column_name]
+ end
+
+ # Value of the left column
+ def left
+ self[left_column_name]
+ end
+
+ # Value of the right column
+ def right
+ self[right_column_name]
+ end
+
+ # Returns true if this is a root node.
+ def root?
+ parent_id.nil?
+ end
+
+ def leaf?
+ right - left == 1
+ end
+
+ # Returns true is this is a child node
+ def child?
+ !parent_id.nil?
+ end
+
+ # order by left column
+ def <=>(x)
+ left <=> x.left
+ end
+
+ # Redefine to act like active record
+ def ==(comparison_object)
+ comparison_object.equal?(self) ||
+ (comparison_object.instance_of?(self.class) &&
+ comparison_object.id == id &&
+ !comparison_object.new_record?)
+ end
+
+ # Returns root
+ def root
+ self_and_ancestors.find(:first)
+ end
+
+ # Returns the immediate parent
+ def parent
+ nested_set_scope.find_by_id(parent_id) if parent_id
+ end
+
+ # Returns the array of all parents and self
+ def self_and_ancestors
+ nested_set_scope.scoped :conditions => [
+ "#{self.class.table_name}.#{quoted_left_column_name} <= ? AND #{self.class.table_name}.#{quoted_right_column_name} >= ?", left, right
+ ]
+ end
+
+ # Returns an array of all parents
+ def ancestors
+ without_self self_and_ancestors
+ end
+
+ # Returns the array of all children of the parent, including self
+ def self_and_siblings
+ nested_set_scope.scoped :conditions => {parent_column_name => parent_id}
+ end
+
+ # Returns the array of all children of the parent, except self
+ def siblings
+ without_self self_and_siblings
+ end
+
+ # Returns a set of all of its nested children which do not have children
+ def leaves
+ descendants.scoped :conditions => "#{self.class.table_name}.#{quoted_right_column_name} - #{self.class.table_name}.#{quoted_left_column_name} = 1"
+ end
+
+ # Returns the level of this object in the tree
+ # root level is 0
+ def level
+ parent_id.nil? ? 0 : ancestors.count
+ end
+
+ # Returns a set of itself and all of its nested children
+ def self_and_descendants
+ nested_set_scope.scoped :conditions => [
+ "#{self.class.table_name}.#{quoted_left_column_name} >= ? AND #{self.class.table_name}.#{quoted_right_column_name} <= ?", left, right
+ ]
+ end
+
+ # Returns a set of all of its children and nested children
+ def descendants
+ without_self self_and_descendants
+ end
+
+ # Returns a set of only this entry's immediate children
+ def children
+ nested_set_scope.scoped :conditions => {parent_column_name => self}
+ end
+
+ def is_descendant_of?(other)
+ other.left < self.left && self.left < other.right && same_scope?(other)
+ end
+
+ def is_or_is_descendant_of?(other)
+ other.left <= self.left && self.left < other.right && same_scope?(other)
+ end
+
+ def is_ancestor_of?(other)
+ self.left < other.left && other.left < self.right && same_scope?(other)
+ end
+
+ def is_or_is_ancestor_of?(other)
+ self.left <= other.left && other.left < self.right && same_scope?(other)
+ end
+
+ # Check if other model is in the same scope
+ def same_scope?(other)
+ Array(acts_as_nested_set_options[:scope]).all? do |attr|
+ self.send(attr) == other.send(attr)
+ end
+ end
+
+ # Find the first sibling to the left
+ def left_sibling
+ siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} < ?", left],
+ :order => "#{self.class.table_name}.#{quoted_left_column_name} DESC")
+ end
+
+ # Find the first sibling to the right
+ def right_sibling
+ siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} > ?", left])
+ end
+
+ # Shorthand method for finding the left sibling and moving to the left of it.
+ def move_left
+ move_to_left_of left_sibling
+ end
+
+ # Shorthand method for finding the right sibling and moving to the right of it.
+ def move_right
+ move_to_right_of right_sibling
+ end
+
+ # Move the node to the left of another node (you can pass id only)
+ def move_to_left_of(node)
+ move_to node, :left
+ end
+
+ # Move the node to the left of another node (you can pass id only)
+ def move_to_right_of(node)
+ move_to node, :right
+ end
+
+ # Move the node to the child of another node (you can pass id only)
+ def move_to_child_of(node)
+ move_to node, :child
+ end
+
+ # Move the node to root nodes
+ def move_to_root
+ move_to nil, :root
+ end
+
+ def move_possible?(target)
+ self != target && # Can't target self
+ same_scope?(target) && # can't be in different scopes
+ # !(left..right).include?(target.left..target.right) # this needs tested more
+ # detect impossible move
+ !((left <= target.left && right >= target.left) or (left <= target.right && right >= target.right))
+ end
+
+ def to_text
+ self_and_descendants.map do |node|
+ "#{'*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
+ end.join("\n")
+ end
+
+ protected
+
+ def without_self(scope)
+ scope.scoped :conditions => ["#{self.class.table_name}.#{self.class.primary_key} != ?", self]
+ end
+
+ # All nested set queries should use this nested_set_scope, which performs finds on
+ # the base ActiveRecord class, using the :scope declared in the acts_as_nested_set
+ # declaration.
+ def nested_set_scope
+ options = {:order => quoted_left_column_name}
+ scopes = Array(acts_as_nested_set_options[:scope])
+ options[:conditions] = scopes.inject({}) do |conditions,attr|
+ conditions.merge attr => self[attr]
+ end unless scopes.empty?
+ self.class.base_class.scoped options
+ end
+
+ # on creation, set automatically lft and rgt to the end of the tree
+ def set_default_left_and_right
+ maxright = nested_set_scope.maximum(right_column_name) || 0
+ # adds the new node to the right of all existing nodes
+ self[left_column_name] = maxright + 1
+ self[right_column_name] = maxright + 2
+ end
+
+ # Prunes a branch off of the tree, shifting all of the elements on the right
+ # back to the left so the counts still work.
+ def prune_from_tree
+ return if right.nil? || left.nil?
+ diff = right - left + 1
+
+ delete_method = acts_as_nested_set_options[:dependent] == :destroy ?
+ :destroy_all : :delete_all
+
+ self.class.base_class.transaction do
+ nested_set_scope.send(delete_method,
+ ["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?",
+ left, right]
+ )
+ nested_set_scope.update_all(
+ ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff],
+ ["#{quoted_left_column_name} >= ?", right]
+ )
+ nested_set_scope.update_all(
+ ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff],
+ ["#{quoted_right_column_name} >= ?", right]
+ )
+ end
+ end
+
+ # reload left, right, and parent
+ def reload_nested_set
+ reload(:select => "#{quoted_left_column_name}, " +
+ "#{quoted_right_column_name}, #{quoted_parent_column_name}")
+ end
+
+ def move_to(target, position)
+ raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if self.new_record?
+ return if callback(:before_move) == false
+ transaction do
+ if target.is_a? self.class.base_class
+ target.reload_nested_set
+ elsif position != :root
+ # load object if node is not an object
+ target = nested_set_scope.find(target)
+ end
+ self.reload_nested_set
+
+ unless position == :root || move_possible?(target)
+ raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
+ end
+
+ bound = case position
+ when :child; target[right_column_name]
+ when :left; target[left_column_name]
+ when :right; target[right_column_name] + 1
+ when :root; 1
+ else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
+ end
+
+ if bound > self[right_column_name]
+ bound = bound - 1
+ other_bound = self[right_column_name] + 1
+ else
+ other_bound = self[left_column_name] - 1
+ end
+
+ # there would be no change
+ return if bound == self[right_column_name] || bound == self[left_column_name]
+
+ # we have defined the boundaries of two non-overlapping intervals,
+ # so sorting puts both the intervals and their boundaries in order
+ a, b, c, d = [self[left_column_name], self[right_column_name], bound, other_bound].sort
+
+ new_parent = case position
+ when :child; target.id
+ when :root; nil
+ else target[parent_column_name]
+ end
+
+ self.class.base_class.update_all([
+ "#{quoted_left_column_name} = CASE " +
+ "WHEN #{quoted_left_column_name} BETWEEN :a AND :b " +
+ "THEN #{quoted_left_column_name} + :d - :b " +
+ "WHEN #{quoted_left_column_name} BETWEEN :c AND :d " +
+ "THEN #{quoted_left_column_name} + :a - :c " +
+ "ELSE #{quoted_left_column_name} END, " +
+ "#{quoted_right_column_name} = CASE " +
+ "WHEN #{quoted_right_column_name} BETWEEN :a AND :b " +
+ "THEN #{quoted_right_column_name} + :d - :b " +
+ "WHEN #{quoted_right_column_name} BETWEEN :c AND :d " +
+ "THEN #{quoted_right_column_name} + :a - :c " +
+ "ELSE #{quoted_right_column_name} END, " +
+ "#{quoted_parent_column_name} = CASE " +
+ "WHEN #{self.class.base_class.primary_key} = :id THEN :new_parent " +
+ "ELSE #{quoted_parent_column_name} END",
+ {:a => a, :b => b, :c => c, :d => d, :id => self.id, :new_parent => new_parent}
+ ], nested_set_scope.proxy_options[:conditions])
+ end
+ target.reload_nested_set if target
+ self.reload_nested_set
+ callback(:after_move)
+ end
+
+ end
+
+ end
+ end
+end
diff --git a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/compatability.rb b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/compatability.rb
new file mode 100644
index 000000000..2d11da330
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/compatability.rb
@@ -0,0 +1,29 @@
+# Rails <2.x doesn't define #except
+class Hash #:nodoc:
+ # Returns a new hash without the given keys.
+ def except(*keys)
+ clone.except!(*keys)
+ end unless method_defined?(:except)
+
+ # Replaces the hash without the given keys.
+ def except!(*keys)
+ keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
+ keys.each { |key| delete(key) }
+ self
+ end unless method_defined?(:except!)
+end
+
+# NamedScope is new to Rails 2.1
+unless defined? ActiveRecord::NamedScope
+ require 'awesome_nested_set/named_scope'
+ ActiveRecord::Base.class_eval do
+ include CollectiveIdea::NamedScope
+ end
+end
+
+# Rails 1.2.x doesn't define #quoted_table_name
+class ActiveRecord::Base #:nodoc:
+ def self.quoted_table_name
+ self.connection.quote_column_name(self.table_name)
+ end unless methods.include?('quoted_table_name')
+end \ No newline at end of file
diff --git a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/helper.rb b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/helper.rb
new file mode 100644
index 000000000..09c803fd0
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/helper.rb
@@ -0,0 +1,40 @@
+module CollectiveIdea #:nodoc:
+ module Acts #:nodoc:
+ module NestedSet #:nodoc:
+ # This module provides some helpers for the model classes using acts_as_nested_set.
+ # It is included by default in all views.
+ #
+ module Helper
+ # Returns options for select.
+ # You can exclude some items from the tree.
+ # You can pass a block receiving an item and returning the string displayed in the select.
+ #
+ # == Params
+ # * +class_or_item+ - Class name or top level times
+ # * +mover+ - The item that is being move, used to exlude impossible moves
+ # * +&block+ - a block that will be used to display: { |item| ... item.name }
+ #
+ # == Usage
+ #
+ # <%= f.select :parent_id, nested_set_options(Category, @category) {|i|
+ # "#{'–' * i.level} #{i.name}"
+ # }) %>
+ #
+ def nested_set_options(class_or_item, mover = nil)
+ class_or_item = class_or_item.roots if class_or_item.is_a?(Class)
+ items = Array(class_or_item)
+ result = []
+ items.each do |root|
+ result += root.self_and_descendants.map do |i|
+ if mover.nil? || mover.new_record? || mover.move_possible?(i)
+ [yield(i), i.id]
+ end
+ end.compact
+ end
+ result
+ end
+
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/named_scope.rb b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/named_scope.rb
new file mode 100644
index 000000000..1836498bd
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/lib/awesome_nested_set/named_scope.rb
@@ -0,0 +1,140 @@
+# Taken from Rails 2.1
+module CollectiveIdea #:nodoc:
+ module NamedScope #:nodoc:
+ # All subclasses of ActiveRecord::Base have two named_scopes:
+ # * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
+ # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly:
+ #
+ # Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
+ #
+ # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
+ # intermediate values (scopes) around as first-class objects is convenient.
+ def self.included(base)
+ base.class_eval do
+ extend ClassMethods
+ named_scope :scoped, lambda { |scope| scope }
+ end
+ end
+
+ module ClassMethods #:nodoc:
+ def scopes
+ read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
+ end
+
+ # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
+ # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :red, :conditions => {:color => 'red'}
+ # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
+ # end
+ #
+ # The above calls to <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>,
+ # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
+ #
+ # Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
+ # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
+ # <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
+ # as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
+ # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
+ #
+ # These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
+ # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
+ # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
+ #
+ # All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to
+ # <tt>has_many</tt> associations. If,
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :shirts
+ # end
+ #
+ # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
+ # only shirts.
+ #
+ # Named scopes can also be procedural.
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :colored, lambda { |color|
+ # { :conditions => { :color => color } }
+ # }
+ # end
+ #
+ # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
+ #
+ # Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :red, :conditions => {:color => 'red'} do
+ # def dom_id
+ # 'red_shirts'
+ # end
+ # end
+ # end
+ #
+ #
+ # For testing complex named scopes, you can examine the scoping options using the
+ # <tt>proxy_options</tt> method on the proxy itself.
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :colored, lambda { |color|
+ # { :conditions => { :color => color } }
+ # }
+ # end
+ #
+ # expected_options = { :conditions => { :colored => 'red' } }
+ # assert_equal expected_options, Shirt.colored('red').proxy_options
+ def named_scope(name, options = {}, &block)
+ scopes[name] = lambda do |parent_scope, *args|
+ Scope.new(parent_scope, case options
+ when Hash
+ options
+ when Proc
+ options.call(*args)
+ end, &block)
+ end
+ (class << self; self end).instance_eval do
+ define_method name do |*args|
+ scopes[name].call(self, *args)
+ end
+ end
+ end
+ end
+
+ class Scope #:nodoc:
+ attr_reader :proxy_scope, :proxy_options
+ [].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
+ delegate :scopes, :with_scope, :to => :proxy_scope
+
+ def initialize(proxy_scope, options, &block)
+ [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
+ extend Module.new(&block) if block_given?
+ @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
+ end
+
+ def reload
+ load_found; self
+ end
+
+ protected
+ def proxy_found
+ @found || load_found
+ end
+
+ private
+ def method_missing(method, *args, &block)
+ if scopes.include?(method)
+ scopes[method].call(self, *args)
+ else
+ with_scope :find => proxy_options do
+ proxy_scope.send(method, *args, &block)
+ end
+ end
+ end
+
+ def load_found
+ @found = find(:all)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/plugins/awesome_nested_set/rails/init.rb b/vendor/plugins/awesome_nested_set/rails/init.rb
new file mode 100644
index 000000000..e0a4e8b06
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/rails/init.rb
@@ -0,0 +1,13 @@
+require 'awesome_nested_set/compatability'
+require 'awesome_nested_set'
+
+ActiveRecord::Base.class_eval do
+ include CollectiveIdea::Acts::NestedSet
+end
+
+if defined?(ActionView)
+ require 'awesome_nested_set/helper'
+ ActionView::Base.class_eval do
+ include CollectiveIdea::Acts::NestedSet::Helper
+ end
+end \ No newline at end of file
diff --git a/vendor/plugins/awesome_nested_set/test/awesome_nested_set/helper_test.rb b/vendor/plugins/awesome_nested_set/test/awesome_nested_set/helper_test.rb
new file mode 100644
index 000000000..6122a0e60
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/test/awesome_nested_set/helper_test.rb
@@ -0,0 +1,41 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module CollectiveIdea
+ module Acts #:nodoc:
+ module NestedSet #:nodoc:
+ class AwesomeNestedSetTest < Test::Unit::TestCase
+ include Helper
+ fixtures :categories
+
+ def test_nested_set_options
+ expected = [
+ [" Top Level", 1],
+ ["- Child 1", 2],
+ ['- Child 2', 3],
+ ['-- Child 2.1', 4],
+ ['- Child 3', 5],
+ [" Top Level 2", 6]
+ ]
+ actual = nested_set_options(Category) do |c|
+ "#{'-' * c.level} #{c.name}"
+ end
+ assert_equal expected, actual
+ end
+
+ def test_nested_set_options_with_mover
+ expected = [
+ [" Top Level", 1],
+ ["- Child 1", 2],
+ ['- Child 3', 5],
+ [" Top Level 2", 6]
+ ]
+ actual = nested_set_options(Category, categories(:child_2)) do |c|
+ "#{'-' * c.level} #{c.name}"
+ end
+ assert_equal expected, actual
+ end
+
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/plugins/awesome_nested_set/test/awesome_nested_set_test.rb b/vendor/plugins/awesome_nested_set/test/awesome_nested_set_test.rb
new file mode 100644
index 000000000..5252d804e
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/test/awesome_nested_set_test.rb
@@ -0,0 +1,603 @@
+require File.dirname(__FILE__) + '/test_helper'
+
+class Note < ActiveRecord::Base
+ acts_as_nested_set :scope => [:notable_id, :notable_type]
+end
+
+class AwesomeNestedSetTest < Test::Unit::TestCase
+
+ class Default < ActiveRecord::Base
+ acts_as_nested_set
+ set_table_name 'categories'
+ end
+ class Scoped < ActiveRecord::Base
+ acts_as_nested_set :scope => :organization
+ set_table_name 'categories'
+ end
+
+ def test_left_column_default
+ assert_equal 'lft', Default.acts_as_nested_set_options[:left_column]
+ end
+
+ def test_right_column_default
+ assert_equal 'rgt', Default.acts_as_nested_set_options[:right_column]
+ end
+
+ def test_parent_column_default
+ assert_equal 'parent_id', Default.acts_as_nested_set_options[:parent_column]
+ end
+
+ def test_scope_default
+ assert_nil Default.acts_as_nested_set_options[:scope]
+ end
+
+ def test_left_column_name
+ assert_equal 'lft', Default.left_column_name
+ assert_equal 'lft', Default.new.left_column_name
+ end
+
+ def test_right_column_name
+ assert_equal 'rgt', Default.right_column_name
+ assert_equal 'rgt', Default.new.right_column_name
+ end
+
+ def test_parent_column_name
+ assert_equal 'parent_id', Default.parent_column_name
+ assert_equal 'parent_id', Default.new.parent_column_name
+ end
+
+ def test_quoted_left_column_name
+ quoted = Default.connection.quote_column_name('lft')
+ assert_equal quoted, Default.quoted_left_column_name
+ assert_equal quoted, Default.new.quoted_left_column_name
+ end
+
+ def test_quoted_right_column_name
+ quoted = Default.connection.quote_column_name('rgt')
+ assert_equal quoted, Default.quoted_right_column_name
+ assert_equal quoted, Default.new.quoted_right_column_name
+ end
+
+ def test_left_column_protected_from_assignment
+ assert_raises(ActiveRecord::ActiveRecordError) { Category.new.lft = 1 }
+ end
+
+ def test_right_column_protected_from_assignment
+ assert_raises(ActiveRecord::ActiveRecordError) { Category.new.rgt = 1 }
+ end
+
+ def test_parent_column_protected_from_assignment
+ assert_raises(ActiveRecord::ActiveRecordError) { Category.new.parent_id = 1 }
+ end
+
+ def test_colums_protected_on_initialize
+ c = Category.new(:lft => 1, :rgt => 2, :parent_id => 3)
+ assert_nil c.lft
+ assert_nil c.rgt
+ assert_nil c.parent_id
+ end
+
+ def test_scoped_appends_id
+ assert_equal :organization_id, Scoped.acts_as_nested_set_options[:scope]
+ end
+
+ def test_roots_class_method
+ assert_equal Category.find_all_by_parent_id(nil), Category.roots
+ end
+
+ def test_root_class_method
+ assert_equal categories(:top_level), Category.root
+ end
+
+ def test_root
+ assert_equal categories(:top_level), categories(:child_3).root
+ end
+
+ def test_root?
+ assert categories(:top_level).root?
+ assert categories(:top_level_2).root?
+ end
+
+ def test_leaves_class_method
+ assert_equal Category.find(:all, :conditions => "#{Category.right_column_name} - #{Category.left_column_name} = 1"), Category.leaves
+ assert_equal Category.leaves.count, 4
+ assert (Category.leaves.include? categories(:child_1))
+ assert (Category.leaves.include? categories(:child_2_1))
+ assert (Category.leaves.include? categories(:child_3))
+ assert (Category.leaves.include? categories(:top_level_2))
+ end
+
+ def test_leaf
+ assert categories(:child_1).leaf?
+ assert categories(:child_2_1).leaf?
+ assert categories(:child_3).leaf?
+ assert categories(:top_level_2).leaf?
+
+ assert !categories(:top_level).leaf?
+ assert !categories(:child_2).leaf?
+ end
+
+ def test_parent
+ assert_equal categories(:child_2), categories(:child_2_1).parent
+ end
+
+ def test_self_and_ancestors
+ child = categories(:child_2_1)
+ self_and_ancestors = [categories(:top_level), categories(:child_2), child]
+ assert_equal self_and_ancestors, child.self_and_ancestors
+ end
+
+ def test_ancestors
+ child = categories(:child_2_1)
+ ancestors = [categories(:top_level), categories(:child_2)]
+ assert_equal ancestors, child.ancestors
+ end
+
+ def test_self_and_siblings
+ child = categories(:child_2)
+ self_and_siblings = [categories(:child_1), child, categories(:child_3)]
+ assert_equal self_and_siblings, child.self_and_siblings
+ assert_nothing_raised do
+ tops = [categories(:top_level), categories(:top_level_2)]
+ assert_equal tops, categories(:top_level).self_and_siblings
+ end
+ end
+
+ def test_siblings
+ child = categories(:child_2)
+ siblings = [categories(:child_1), categories(:child_3)]
+ assert_equal siblings, child.siblings
+ end
+
+ def test_leaves
+ leaves = [categories(:child_1), categories(:child_2_1), categories(:child_3), categories(:top_level_2)]
+ assert categories(:top_level).leaves, leaves
+ end
+
+ def test_level
+ assert_equal 0, categories(:top_level).level
+ assert_equal 1, categories(:child_1).level
+ assert_equal 2, categories(:child_2_1).level
+ end
+
+ def test_has_children?
+ assert categories(:child_2_1).children.empty?
+ assert !categories(:child_2).children.empty?
+ assert !categories(:top_level).children.empty?
+ end
+
+ def test_self_and_descendents
+ parent = categories(:top_level)
+ self_and_descendants = [parent, categories(:child_1), categories(:child_2),
+ categories(:child_2_1), categories(:child_3)]
+ assert_equal self_and_descendants, parent.self_and_descendants
+ assert_equal self_and_descendants, parent.self_and_descendants.count
+ end
+
+ def test_descendents
+ lawyers = Category.create!(:name => "lawyers")
+ us = Category.create!(:name => "United States")
+ us.move_to_child_of(lawyers)
+ patent = Category.create!(:name => "Patent Law")
+ patent.move_to_child_of(us)
+ lawyers.reload
+
+ assert_equal 1, lawyers.children.size
+ assert_equal 1, us.children.size
+ assert_equal 2, lawyers.descendants.size
+ end
+
+ def test_self_and_descendents
+ parent = categories(:top_level)
+ descendants = [categories(:child_1), categories(:child_2),
+ categories(:child_2_1), categories(:child_3)]
+ assert_equal descendants, parent.descendants
+ end
+
+ def test_children
+ category = categories(:top_level)
+ category.children.each {|c| assert_equal category.id, c.parent_id }
+ end
+
+ def test_is_or_is_ancestor_of?
+ assert categories(:top_level).is_or_is_ancestor_of?(categories(:child_1))
+ assert categories(:top_level).is_or_is_ancestor_of?(categories(:child_2_1))
+ assert categories(:child_2).is_or_is_ancestor_of?(categories(:child_2_1))
+ assert !categories(:child_2_1).is_or_is_ancestor_of?(categories(:child_2))
+ assert !categories(:child_1).is_or_is_ancestor_of?(categories(:child_2))
+ assert categories(:child_1).is_or_is_ancestor_of?(categories(:child_1))
+ end
+
+ def test_is_ancestor_of?
+ assert categories(:top_level).is_ancestor_of?(categories(:child_1))
+ assert categories(:top_level).is_ancestor_of?(categories(:child_2_1))
+ assert categories(:child_2).is_ancestor_of?(categories(:child_2_1))
+ assert !categories(:child_2_1).is_ancestor_of?(categories(:child_2))
+ assert !categories(:child_1).is_ancestor_of?(categories(:child_2))
+ assert !categories(:child_1).is_ancestor_of?(categories(:child_1))
+ end
+
+ def test_is_or_is_ancestor_of_with_scope
+ root = Scoped.root
+ child = root.children.first
+ assert root.is_or_is_ancestor_of?(child)
+ child.update_attribute :organization_id, 'different'
+ assert !root.is_or_is_ancestor_of?(child)
+ end
+
+ def test_is_or_is_descendant_of?
+ assert categories(:child_1).is_or_is_descendant_of?(categories(:top_level))
+ assert categories(:child_2_1).is_or_is_descendant_of?(categories(:top_level))
+ assert categories(:child_2_1).is_or_is_descendant_of?(categories(:child_2))
+ assert !categories(:child_2).is_or_is_descendant_of?(categories(:child_2_1))
+ assert !categories(:child_2).is_or_is_descendant_of?(categories(:child_1))
+ assert categories(:child_1).is_or_is_descendant_of?(categories(:child_1))
+ end
+
+ def test_is_descendant_of?
+ assert categories(:child_1).is_descendant_of?(categories(:top_level))
+ assert categories(:child_2_1).is_descendant_of?(categories(:top_level))
+ assert categories(:child_2_1).is_descendant_of?(categories(:child_2))
+ assert !categories(:child_2).is_descendant_of?(categories(:child_2_1))
+ assert !categories(:child_2).is_descendant_of?(categories(:child_1))
+ assert !categories(:child_1).is_descendant_of?(categories(:child_1))
+ end
+
+ def test_is_or_is_descendant_of_with_scope
+ root = Scoped.root
+ child = root.children.first
+ assert child.is_or_is_descendant_of?(root)
+ child.update_attribute :organization_id, 'different'
+ assert !child.is_or_is_descendant_of?(root)
+ end
+
+ def test_same_scope?
+ root = Scoped.root
+ child = root.children.first
+ assert child.same_scope?(root)
+ child.update_attribute :organization_id, 'different'
+ assert !child.same_scope?(root)
+ end
+
+ def test_left_sibling
+ assert_equal categories(:child_1), categories(:child_2).left_sibling
+ assert_equal categories(:child_2), categories(:child_3).left_sibling
+ end
+
+ def test_left_sibling_of_root
+ assert_nil categories(:top_level).left_sibling
+ end
+
+ def test_left_sibling_without_siblings
+ assert_nil categories(:child_2_1).left_sibling
+ end
+
+ def test_left_sibling_of_leftmost_node
+ assert_nil categories(:child_1).left_sibling
+ end
+
+ def test_right_sibling
+ assert_equal categories(:child_3), categories(:child_2).right_sibling
+ assert_equal categories(:child_2), categories(:child_1).right_sibling
+ end
+
+ def test_right_sibling_of_root
+ assert_equal categories(:top_level_2), categories(:top_level).right_sibling
+ assert_nil categories(:top_level_2).right_sibling
+ end
+
+ def test_right_sibling_without_siblings
+ assert_nil categories(:child_2_1).right_sibling
+ end
+
+ def test_right_sibling_of_rightmost_node
+ assert_nil categories(:child_3).right_sibling
+ end
+
+ def test_move_left
+ categories(:child_2).move_left
+ assert_nil categories(:child_2).left_sibling
+ assert_equal categories(:child_1), categories(:child_2).right_sibling
+ assert Category.valid?
+ end
+
+ def test_move_right
+ categories(:child_2).move_right
+ assert_nil categories(:child_2).right_sibling
+ assert_equal categories(:child_3), categories(:child_2).left_sibling
+ assert Category.valid?
+ end
+
+ def test_move_to_left_of
+ categories(:child_3).move_to_left_of(categories(:child_1))
+ assert_nil categories(:child_3).left_sibling
+ assert_equal categories(:child_1), categories(:child_3).right_sibling
+ assert Category.valid?
+ end
+
+ def test_move_to_right_of
+ categories(:child_1).move_to_right_of(categories(:child_3))
+ assert_nil categories(:child_1).right_sibling
+ assert_equal categories(:child_3), categories(:child_1).left_sibling
+ assert Category.valid?
+ end
+
+ def test_move_to_root
+ categories(:child_2).move_to_root
+ assert_nil categories(:child_2).parent
+ assert_equal 0, categories(:child_2).level
+ assert_equal 1, categories(:child_2_1).level
+ assert_equal 1, categories(:child_2).left
+ assert_equal 4, categories(:child_2).right
+ assert Category.valid?
+ end
+
+ def test_move_to_child_of
+ categories(:child_1).move_to_child_of(categories(:child_3))
+ assert_equal categories(:child_3).id, categories(:child_1).parent_id
+ assert Category.valid?
+ end
+
+ def test_move_to_child_of_appends_to_end
+ child = Category.create! :name => 'New Child'
+ child.move_to_child_of categories(:top_level)
+ assert_equal child, categories(:top_level).children.last
+ end
+
+ def test_subtree_move_to_child_of
+ assert_equal 4, categories(:child_2).left
+ assert_equal 7, categories(:child_2).right
+
+ assert_equal 2, categories(:child_1).left
+ assert_equal 3, categories(:child_1).right
+
+ categories(:child_2).move_to_child_of(categories(:child_1))
+ assert Category.valid?
+ assert_equal categories(:child_1).id, categories(:child_2).parent_id
+
+ assert_equal 3, categories(:child_2).left
+ assert_equal 6, categories(:child_2).right
+ assert_equal 2, categories(:child_1).left
+ assert_equal 7, categories(:child_1).right
+ end
+
+ def test_slightly_difficult_move_to_child_of
+ assert_equal 11, categories(:top_level_2).left
+ assert_equal 12, categories(:top_level_2).right
+
+ # create a new top-level node and move single-node top-level tree inside it.
+ new_top = Category.create(:name => 'New Top')
+ assert_equal 13, new_top.left
+ assert_equal 14, new_top.right
+
+ categories(:top_level_2).move_to_child_of(new_top)
+
+ assert Category.valid?
+ assert_equal new_top.id, categories(:top_level_2).parent_id
+
+ assert_equal 12, categories(:top_level_2).left
+ assert_equal 13, categories(:top_level_2).right
+ assert_equal 11, new_top.left
+ assert_equal 14, new_top.right
+ end
+
+ def test_difficult_move_to_child_of
+ assert_equal 1, categories(:top_level).left
+ assert_equal 10, categories(:top_level).right
+ assert_equal 5, categories(:child_2_1).left
+ assert_equal 6, categories(:child_2_1).right
+
+ # create a new top-level node and move an entire top-level tree inside it.
+ new_top = Category.create(:name => 'New Top')
+ categories(:top_level).move_to_child_of(new_top)
+ categories(:child_2_1).reload
+ assert Category.valid?
+ assert_equal new_top.id, categories(:top_level).parent_id
+
+ assert_equal 4, categories(:top_level).left
+ assert_equal 13, categories(:top_level).right
+ assert_equal 8, categories(:child_2_1).left
+ assert_equal 9, categories(:child_2_1).right
+ end
+
+ #rebuild swaps the position of the 2 children when added using move_to_child twice onto same parent
+ def test_move_to_child_more_than_once_per_parent_rebuild
+ root1 = Category.create(:name => 'Root1')
+ root2 = Category.create(:name => 'Root2')
+ root3 = Category.create(:name => 'Root3')
+
+ root2.move_to_child_of root1
+ root3.move_to_child_of root1
+
+ output = Category.roots.last.to_text
+ Category.update_all('lft = null, rgt = null')
+ Category.rebuild!
+
+ assert_equal Category.roots.last.to_text, output
+ end
+
+ # doing move_to_child twice onto same parent from the furthest right first
+ def test_move_to_child_more_than_once_per_parent_outside_in
+ node1 = Category.create(:name => 'Node-1')
+ node2 = Category.create(:name => 'Node-2')
+ node3 = Category.create(:name => 'Node-3')
+
+ node2.move_to_child_of node1
+ node3.move_to_child_of node1
+
+ output = Category.roots.last.to_text
+ Category.update_all('lft = null, rgt = null')
+ Category.rebuild!
+
+ assert_equal Category.roots.last.to_text, output
+ end
+
+
+ def test_valid_with_null_lefts
+ assert Category.valid?
+ Category.update_all('lft = null')
+ assert !Category.valid?
+ end
+
+ def test_valid_with_null_rights
+ assert Category.valid?
+ Category.update_all('rgt = null')
+ assert !Category.valid?
+ end
+
+ def test_valid_with_missing_intermediate_node
+ # Even though child_2_1 will still exist, it is a sign of a sloppy delete, not an invalid tree.
+ assert Category.valid?
+ Category.delete(categories(:child_2).id)
+ assert Category.valid?
+ end
+
+ def test_valid_with_overlapping_and_rights
+ assert Category.valid?
+ categories(:top_level_2)['lft'] = 0
+ categories(:top_level_2).save
+ assert !Category.valid?
+ end
+
+ def test_rebuild
+ assert Category.valid?
+ before_text = Category.root.to_text
+ Category.update_all('lft = null, rgt = null')
+ Category.rebuild!
+ assert Category.valid?
+ assert_equal before_text, Category.root.to_text
+ end
+
+ def test_move_possible_for_sibling
+ assert categories(:child_2).move_possible?(categories(:child_1))
+ end
+
+ def test_move_not_possible_to_self
+ assert !categories(:top_level).move_possible?(categories(:top_level))
+ end
+
+ def test_move_not_possible_to_parent
+ categories(:top_level).descendants.each do |descendant|
+ assert !categories(:top_level).move_possible?(descendant)
+ assert descendant.move_possible?(categories(:top_level))
+ end
+ end
+
+ def test_is_or_is_ancestor_of?
+ [:child_1, :child_2, :child_2_1, :child_3].each do |c|
+ assert categories(:top_level).is_or_is_ancestor_of?(categories(c))
+ end
+ assert !categories(:top_level).is_or_is_ancestor_of?(categories(:top_level_2))
+ end
+
+ def test_left_and_rights_valid_with_blank_left
+ assert Category.left_and_rights_valid?
+ categories(:child_2)[:lft] = nil
+ categories(:child_2).save(false)
+ assert !Category.left_and_rights_valid?
+ end
+
+ def test_left_and_rights_valid_with_blank_right
+ assert Category.left_and_rights_valid?
+ categories(:child_2)[:rgt] = nil
+ categories(:child_2).save(false)
+ assert !Category.left_and_rights_valid?
+ end
+
+ def test_left_and_rights_valid_with_equal
+ assert Category.left_and_rights_valid?
+ categories(:top_level_2)[:lft] = categories(:top_level_2)[:rgt]
+ categories(:top_level_2).save(false)
+ assert !Category.left_and_rights_valid?
+ end
+
+ def test_left_and_rights_valid_with_left_equal_to_parent
+ assert Category.left_and_rights_valid?
+ categories(:child_2)[:lft] = categories(:top_level)[:lft]
+ categories(:child_2).save(false)
+ assert !Category.left_and_rights_valid?
+ end
+
+ def test_left_and_rights_valid_with_right_equal_to_parent
+ assert Category.left_and_rights_valid?
+ categories(:child_2)[:rgt] = categories(:top_level)[:rgt]
+ categories(:child_2).save(false)
+ assert !Category.left_and_rights_valid?
+ end
+
+ def test_moving_dirty_objects_doesnt_invalidate_tree
+ r1 = Category.create
+ r2 = Category.create
+ r3 = Category.create
+ r4 = Category.create
+ nodes = [r1, r2, r3, r4]
+
+ r2.move_to_child_of(r1)
+ assert Category.valid?
+
+ r3.move_to_child_of(r1)
+ assert Category.valid?
+
+ r4.move_to_child_of(r2)
+ assert Category.valid?
+ end
+
+ def test_multi_scoped_no_duplicates_for_columns?
+ assert_nothing_raised do
+ Note.no_duplicates_for_columns?
+ end
+ end
+
+ def test_multi_scoped_all_roots_valid?
+ assert_nothing_raised do
+ Note.all_roots_valid?
+ end
+ end
+
+ def test_multi_scoped
+ note1 = Note.create!(:body => "A", :notable_id => 2, :notable_type => 'Category')
+ note2 = Note.create!(:body => "B", :notable_id => 2, :notable_type => 'Category')
+ note3 = Note.create!(:body => "C", :notable_id => 2, :notable_type => 'Default')
+
+ assert_equal [note1, note2], note1.self_and_siblings
+ assert_equal [note3], note3.self_and_siblings
+ end
+
+ def test_multi_scoped_rebuild
+ root = Note.create!(:body => "A", :notable_id => 3, :notable_type => 'Category')
+ child1 = Note.create!(:body => "B", :notable_id => 3, :notable_type => 'Category')
+ child2 = Note.create!(:body => "C", :notable_id => 3, :notable_type => 'Category')
+
+ child1.move_to_child_of root
+ child2.move_to_child_of root
+
+ Note.update_all('lft = null, rgt = null')
+ Note.rebuild!
+
+ assert_equal Note.roots.find_by_body('A'), root
+ assert_equal [child1, child2], Note.roots.find_by_body('A').children
+ end
+
+ def test_same_scope_with_multi_scopes
+ assert_nothing_raised do
+ notes(:scope1).same_scope?(notes(:child_1))
+ end
+ assert notes(:scope1).same_scope?(notes(:child_1))
+ assert notes(:child_1).same_scope?(notes(:scope1))
+ assert !notes(:scope1).same_scope?(notes(:scope2))
+ end
+
+ def test_quoting_of_multi_scope_column_names
+ assert_equal ["\"notable_id\"", "\"notable_type\""], Note.quoted_scope_column_names
+ end
+
+ def test_equal_in_same_scope
+ assert_equal notes(:scope1), notes(:scope1)
+ assert_not_equal notes(:scope1), notes(:child_1)
+ end
+
+ def test_equal_in_different_scopes
+ assert_not_equal notes(:scope1), notes(:scope2)
+ end
+
+end
diff --git a/vendor/plugins/awesome_nested_set/test/db/database.yml b/vendor/plugins/awesome_nested_set/test/db/database.yml
new file mode 100644
index 000000000..9281236d2
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/test/db/database.yml
@@ -0,0 +1,18 @@
+sqlite3:
+ adapter: sqlite3
+ dbfile: awesome_nested_set.sqlite3.db
+sqlite3mem:
+ :adapter: sqlite3
+ :dbfile: ":memory:"
+postgresql:
+ :adapter: postgresql
+ :username: postgres
+ :password: postgres
+ :database: awesome_nested_set_plugin_test
+ :min_messages: ERROR
+mysql:
+ :adapter: mysql
+ :host: localhost
+ :username: root
+ :password:
+ :database: awesome_nested_set_plugin_test \ No newline at end of file
diff --git a/vendor/plugins/awesome_nested_set/test/db/schema.rb b/vendor/plugins/awesome_nested_set/test/db/schema.rb
new file mode 100644
index 000000000..bbed1eb16
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/test/db/schema.rb
@@ -0,0 +1,23 @@
+ActiveRecord::Schema.define(:version => 0) do
+
+ create_table :categories, :force => true do |t|
+ t.column :name, :string
+ t.column :parent_id, :integer
+ t.column :lft, :integer
+ t.column :rgt, :integer
+ t.column :organization_id, :integer
+ end
+
+ create_table :departments, :force => true do |t|
+ t.column :name, :string
+ end
+
+ create_table :notes, :force => true do |t|
+ t.column :body, :text
+ t.column :parent_id, :integer
+ t.column :lft, :integer
+ t.column :rgt, :integer
+ t.column :notable_id, :integer
+ t.column :notable_type, :string
+ end
+end
diff --git a/vendor/plugins/awesome_nested_set/test/fixtures/categories.yml b/vendor/plugins/awesome_nested_set/test/fixtures/categories.yml
new file mode 100644
index 000000000..bc8e078e8
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/test/fixtures/categories.yml
@@ -0,0 +1,34 @@
+top_level:
+ id: 1
+ name: Top Level
+ lft: 1
+ rgt: 10
+child_1:
+ id: 2
+ name: Child 1
+ parent_id: 1
+ lft: 2
+ rgt: 3
+child_2:
+ id: 3
+ name: Child 2
+ parent_id: 1
+ lft: 4
+ rgt: 7
+child_2_1:
+ id: 4
+ name: Child 2.1
+ parent_id: 3
+ lft: 5
+ rgt: 6
+child_3:
+ id: 5
+ name: Child 3
+ parent_id: 1
+ lft: 8
+ rgt: 9
+top_level_2:
+ id: 6
+ name: Top Level 2
+ lft: 11
+ rgt: 12
diff --git a/vendor/plugins/awesome_nested_set/test/fixtures/category.rb b/vendor/plugins/awesome_nested_set/test/fixtures/category.rb
new file mode 100644
index 000000000..506b0dac5
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/test/fixtures/category.rb
@@ -0,0 +1,15 @@
+class Category < ActiveRecord::Base
+ acts_as_nested_set
+
+ def to_s
+ name
+ end
+
+ def recurse &block
+ block.call self, lambda{
+ self.children.each do |child|
+ child.recurse &block
+ end
+ }
+ end
+end \ No newline at end of file
diff --git a/vendor/plugins/awesome_nested_set/test/fixtures/departments.yml b/vendor/plugins/awesome_nested_set/test/fixtures/departments.yml
new file mode 100644
index 000000000..e50a944f1
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/test/fixtures/departments.yml
@@ -0,0 +1,3 @@
+top:
+ id: 1
+ name: Top \ No newline at end of file
diff --git a/vendor/plugins/awesome_nested_set/test/fixtures/notes.yml b/vendor/plugins/awesome_nested_set/test/fixtures/notes.yml
new file mode 100644
index 000000000..004a5335a
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/test/fixtures/notes.yml
@@ -0,0 +1,38 @@
+scope1:
+ id: 1
+ body: Top Level
+ lft: 1
+ rgt: 10
+ notable_id: 1
+ notable_type: Category
+child_1:
+ id: 2
+ body: Child 1
+ parent_id: 1
+ lft: 2
+ rgt: 3
+ notable_id: 1
+ notable_type: Category
+child_2:
+ id: 3
+ body: Child 2
+ parent_id: 1
+ lft: 4
+ rgt: 7
+ notable_id: 1
+ notable_type: Category
+child_3:
+ id: 4
+ body: Child 3
+ parent_id: 1
+ lft: 8
+ rgt: 9
+ notable_id: 1
+ notable_type: Category
+scope2:
+ id: 5
+ body: Top Level 2
+ lft: 1
+ rgt: 2
+ notable_id: 1
+ notable_type: Departments
diff --git a/vendor/plugins/awesome_nested_set/test/test_helper.rb b/vendor/plugins/awesome_nested_set/test/test_helper.rb
new file mode 100644
index 000000000..693982236
--- /dev/null
+++ b/vendor/plugins/awesome_nested_set/test/test_helper.rb
@@ -0,0 +1,31 @@
+$:.unshift(File.dirname(__FILE__) + '/../lib')
+plugin_test_dir = File.dirname(__FILE__)
+
+require 'rubygems'
+require 'test/unit'
+require 'multi_rails_init'
+# gem 'activerecord', '>= 2.0'
+require 'active_record'
+require 'action_controller'
+require 'action_view'
+require 'active_record/fixtures'
+
+require plugin_test_dir + '/../init.rb'
+
+ActiveRecord::Base.logger = Logger.new(plugin_test_dir + "/debug.log")
+
+ActiveRecord::Base.configurations = YAML::load(IO.read(plugin_test_dir + "/db/database.yml"))
+ActiveRecord::Base.establish_connection(ENV["DB"] || "sqlite3mem")
+ActiveRecord::Migration.verbose = false
+load(File.join(plugin_test_dir, "db", "schema.rb"))
+
+Dir["#{plugin_test_dir}/fixtures/*.rb"].each {|file| require file }
+
+
+class Test::Unit::TestCase #:nodoc:
+ self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
+ self.use_transactional_fixtures = true
+ self.use_instantiated_fixtures = false
+
+ fixtures :categories, :notes, :departments
+end \ No newline at end of file
diff --git a/vendor/plugins/classic_pagination/lib/pagination.rb b/vendor/plugins/classic_pagination/lib/pagination.rb
index b6e9cf4bc..6a3e1a97b 100644
--- a/vendor/plugins/classic_pagination/lib/pagination.rb
+++ b/vendor/plugins/classic_pagination/lib/pagination.rb
@@ -97,8 +97,8 @@ module ActionController
"Unknown options: #{unknown_option_keys.join(', ')}" unless
unknown_option_keys.empty?
- options[:singular_name] ||= Inflector.singularize(collection_id.to_s)
- options[:class_name] ||= Inflector.camelize(options[:singular_name])
+ options[:singular_name] ||= ActiveSupport::Inflector.singularize(collection_id.to_s)
+ options[:class_name] ||= ActiveSupport::Inflector.camelize(options[:singular_name])
end
# Returns a paginator and a collection of Active Record model instances
diff --git a/vendor/plugins/engines/lib/engines/rails_extensions/dependencies.rb b/vendor/plugins/engines/lib/engines/rails_extensions/dependencies.rb
index 82ecaa880..05ba0eb58 100644
--- a/vendor/plugins/engines/lib/engines/rails_extensions/dependencies.rb
+++ b/vendor/plugins/engines/lib/engines/rails_extensions/dependencies.rb
@@ -140,4 +140,4 @@ module Engines::RailsExtensions::Dependencies
end
end
-Dependencies.send :include, Engines::RailsExtensions::Dependencies
+ActiveSupport::Dependencies.send :include, Engines::RailsExtensions::Dependencies
diff --git a/vendor/plugins/gloc-1.1.0/lib/gloc.rb b/vendor/plugins/gloc-1.1.0/lib/gloc.rb
index bcad0ed9b..8123785b7 100644
--- a/vendor/plugins/gloc-1.1.0/lib/gloc.rb
+++ b/vendor/plugins/gloc-1.1.0/lib/gloc.rb
@@ -52,7 +52,7 @@ module GLoc
# Sets the current language for this instance/class.
# Setting the language of a class effects all instances unless the instance has its own language defined.
def set_language(language)
- @gloc_language= language.nil? ? nil : language.to_sym
+ GLoc.current_language = language
end
# Sets the current language if the language passed is a valid language.
@@ -75,7 +75,7 @@ module GLoc
include ::GLoc::InstanceMethods
# Returns the instance-level current language, or if not set, returns the class-level current language.
def current_language
- @gloc_language || self.class.current_language
+ GLoc.current_language
end
#---------------------------------------------------------------------------
@@ -87,7 +87,7 @@ module GLoc
include ::GLoc::InstanceMethods
# Returns the current language, or if not set, returns the GLoc current language.
def current_language
- @gloc_language || GLoc.current_language
+ GLoc.current_language
end
end
@@ -103,10 +103,16 @@ module GLoc
class << self
include ::GLoc::InstanceMethods
+
+ @@current_language = nil
- # Returns the default language
+ # Returns the current language
def current_language
- GLoc::CONFIG[:default_language]
+ @@current_language || GLoc::CONFIG[:default_language]
+ end
+
+ def current_language=(lang)
+ @@current_language = lang.blank? ? nil : lang.to_sym
end
# Adds a collection of localized strings to the in-memory string store.
diff --git a/vendor/plugins/gloc-1.1.0/tasks/gloc.rake b/vendor/plugins/gloc-1.1.0/tasks/gloc.rake
index 88f3472ec..fb5dfb8d3 100644
--- a/vendor/plugins/gloc-1.1.0/tasks/gloc.rake
+++ b/vendor/plugins/gloc-1.1.0/tasks/gloc.rake
@@ -28,7 +28,7 @@ namespace :gloc do
}
end
- desc 'Updates language files based on em.yml content'
+ desc 'Updates language files based on en.yml content'
task :update do
dir = ENV['DIR'] || './lang'
diff --git a/vendor/plugins/gravatar/MIT-LICENSE b/vendor/plugins/gravatar/MIT-LICENSE
new file mode 100644
index 000000000..6a222ac4d
--- /dev/null
+++ b/vendor/plugins/gravatar/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2007 West Arete Computing, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/vendor/plugins/gravatar/README b/vendor/plugins/gravatar/README
new file mode 100644
index 000000000..dc516d367
--- /dev/null
+++ b/vendor/plugins/gravatar/README
@@ -0,0 +1,52 @@
+== Gravatar Plugin
+
+This plugin provides a handful of view helpers for displaying gravatars
+(globally-recognized avatars).
+
+Gravatars allow users to configure an avatar to go with their email address at
+a central location: http://gravatar.com. Gravatar-aware websites (such
+as yours) can then look up and display each user's preferred avatar, without
+having to handle avatar management. The user gets the benefit of not having to
+set up an avatar for each site that they post on.
+
+== Installation
+
+ cd ~/myapp
+ ruby script/plugin install svn://rubyforge.org//var/svn/gravatarplugin/plugins/gravatar
+
+or, if you're using piston[http://piston.rubyforge.org] (worth it!):
+
+ cd ~/myapp/vendor/plugins
+ piston import svn://rubyforge.org//var/svn/gravatarplugin/plugins/gravatar
+
+== Example
+
+If you represent your users with a model that has an +email+ method (typical
+for most rails authentication setups), then you can simply use this method
+in your views:
+
+ <%= gravatar_for @user %>
+
+This will be replaced with the full HTML +img+ tag necessary for displaying
+that user's gravatar.
+
+Other helpers are documented under GravatarHelper::PublicMethods.
+
+== Acknowledgments
+
+The following people have also written gravatar-related Ruby libraries:
+* Seth Rasmussen created the gravatar gem[http://gravatar.rubyforge.org]
+* Matt McCray has also created a gravatar
+ plugin[http://mattmccray.com/svn/rails/plugins/gravatar_helper]
+
+== Author
+
+ Scott A. Woods
+ West Arete Computing, Inc.
+ http://westarete.com
+ scott at westarete dot com
+
+== TODO
+
+* Get full spec coverage
+* Finish rdoc documentation \ No newline at end of file
diff --git a/vendor/plugins/gravatar/Rakefile b/vendor/plugins/gravatar/Rakefile
new file mode 100644
index 000000000..5a8d92a8a
--- /dev/null
+++ b/vendor/plugins/gravatar/Rakefile
@@ -0,0 +1,33 @@
+require 'spec/rake/spectask'
+require 'rake/rdoctask'
+
+desc 'Default: run all specs'
+task :default => :spec
+
+desc 'Run all application-specific specs'
+Spec::Rake::SpecTask.new(:spec) do |t|
+ t.warning = true
+ t.rcov = true
+end
+
+desc "Report code statistics (KLOCs, etc) from the application"
+task :stats do
+ RAILS_ROOT = File.dirname(__FILE__)
+ STATS_DIRECTORIES = [
+ %w(Libraries lib/),
+ %w(Specs spec/),
+ ].collect { |name, dir| [ name, "#{RAILS_ROOT}/#{dir}" ] }.select { |name, dir| File.directory?(dir) }
+ require 'code_statistics'
+ CodeStatistics.new(*STATS_DIRECTORIES).to_s
+end
+
+namespace :doc do
+ desc 'Generate documentation for the assert_request plugin.'
+ Rake::RDocTask.new(:plugin) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'Gravatar Rails Plugin'
+ rdoc.options << '--line-numbers' << '--inline-source' << '--accessor' << 'cattr_accessor=rw'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+ end
+end
diff --git a/vendor/plugins/gravatar/about.yml b/vendor/plugins/gravatar/about.yml
new file mode 100644
index 000000000..a801d2582
--- /dev/null
+++ b/vendor/plugins/gravatar/about.yml
@@ -0,0 +1,7 @@
+author: Scott Woods, West Arete Computing
+summary: View helpers for displaying gravatars.
+homepage: http://gravatarplugin.rubyforge.org/
+plugin: svn://rubyforge.org//var/svn/gravatarplugin/plugins/gravatar
+license: MIT
+version: 0.1
+rails_version: 1.0+
diff --git a/vendor/plugins/gravatar/init.rb b/vendor/plugins/gravatar/init.rb
new file mode 100644
index 000000000..aab3f75f3
--- /dev/null
+++ b/vendor/plugins/gravatar/init.rb
@@ -0,0 +1,2 @@
+require 'gravatar'
+ActionView::Base.send :include, GravatarHelper::PublicMethods
diff --git a/vendor/plugins/gravatar/lib/gravatar.rb b/vendor/plugins/gravatar/lib/gravatar.rb
new file mode 100644
index 000000000..58cb4cabe
--- /dev/null
+++ b/vendor/plugins/gravatar/lib/gravatar.rb
@@ -0,0 +1,67 @@
+require 'digest/md5'
+require 'cgi'
+
+module GravatarHelper
+
+ # These are the options that control the default behavior of the public
+ # methods. They can be overridden during the actual call to the helper,
+ # or you can set them in your environment.rb as such:
+ #
+ # # Allow racier gravatars
+ # GravatarHelper::DEFAULT_OPTIONS[:rating] = 'R'
+ #
+ DEFAULT_OPTIONS = {
+ # The URL of a default image to display if the given email address does
+ # not have a gravatar.
+ :default => nil,
+
+ # The default size in pixels for the gravatar image (they're square).
+ :size => 50,
+
+ # The maximum allowed MPAA rating for gravatars. This allows you to
+ # exclude gravatars that may be out of character for your site.
+ :rating => 'PG',
+
+ # The alt text to use in the img tag for the gravatar.
+ :alt => 'avatar',
+
+ # The class to assign to the img tag for the gravatar.
+ :class => 'gravatar',
+ }
+
+ # The methods that will be made available to your views.
+ module PublicMethods
+
+ # Return the HTML img tag for the given user's gravatar. Presumes that
+ # the given user object will respond_to "email", and return the user's
+ # email address.
+ def gravatar_for(user, options={})
+ gravatar(user.email, options)
+ end
+
+ # Return the HTML img tag for the given email address's gravatar.
+ def gravatar(email, options={})
+ src = h(gravatar_url(email, options))
+ options = DEFAULT_OPTIONS.merge(options)
+ [:class, :alt, :size].each { |opt| options[opt] = h(options[opt]) }
+ "<img class=\"#{options[:class]}\" alt=\"#{options[:alt]}\" width=\"#{options[:size]}\" height=\"#{options[:size]}\" src=\"#{src}\" />"
+ end
+
+ # Return the gravatar URL for the given email address.
+ def gravatar_url(email, options={})
+ email_hash = Digest::MD5.hexdigest(email)
+ options = DEFAULT_OPTIONS.merge(options)
+ options[:default] = CGI::escape(options[:default]) unless options[:default].nil?
+ returning "http://www.gravatar.com/avatar.php?gravatar_id=#{email_hash}" do |url|
+ [:rating, :size, :default].each do |opt|
+ unless options[opt].nil?
+ value = h(options[opt])
+ url << "&#{opt}=#{value}"
+ end
+ end
+ end
+ end
+
+ end
+
+end \ No newline at end of file
diff --git a/vendor/plugins/gravatar/spec/gravatar_spec.rb b/vendor/plugins/gravatar/spec/gravatar_spec.rb
new file mode 100644
index 000000000..a11d2683a
--- /dev/null
+++ b/vendor/plugins/gravatar/spec/gravatar_spec.rb
@@ -0,0 +1,37 @@
+require 'rubygems'
+require 'erb' # to get "h"
+require 'active_support' # to get "returning"
+require File.dirname(__FILE__) + '/../lib/gravatar'
+include GravatarHelper, GravatarHelper::PublicMethods, ERB::Util
+
+context "gravatar_url with a custom default URL" do
+ setup do
+ @original_options = DEFAULT_OPTIONS.dup
+ DEFAULT_OPTIONS[:default] = "no_avatar.png"
+ @url = gravatar_url("somewhere")
+ end
+
+ specify "should include the \"default\" argument in the result" do
+ @url.should match(/&default=no_avatar.png/)
+ end
+
+ teardown do
+ DEFAULT_OPTIONS.merge!(@original_options)
+ end
+
+end
+
+context "gravatar_url with default settings" do
+ setup do
+ @url = gravatar_url("somewhere")
+ end
+
+ specify "should have a nil default URL" do
+ DEFAULT_OPTIONS[:default].should be_nil
+ end
+
+ specify "should not include the \"default\" argument in the result" do
+ @url.should_not match(/&default=/)
+ end
+
+end \ No newline at end of file
diff --git a/vendor/plugins/open_id_authentication/CHANGELOG b/vendor/plugins/open_id_authentication/CHANGELOG
new file mode 100644
index 000000000..7349bd3c0
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/CHANGELOG
@@ -0,0 +1,35 @@
+* Fake HTTP method from OpenID server since they only support a GET. Eliminates the need to set an extra route to match the server's reply. [Josh Peek]
+
+* OpenID 2.0 recommends that forms should use the field name "openid_identifier" rather than "openid_url" [Josh Peek]
+
+* Return open_id_response.display_identifier to the application instead of .endpoints.claimed_id. [nbibler]
+
+* Add Timeout protection [Rick]
+
+* An invalid identity url passed through authenticate_with_open_id will no longer raise an InvalidOpenId exception. Instead it will return Result[:missing] to the completion block.
+
+* Allow a return_to option to be used instead of the requested url [Josh Peek]
+
+* Updated plugin to use Ruby OpenID 2.x.x [Josh Peek]
+
+* Tied plugin to ruby-openid 1.1.4 gem until we can make it compatible with 2.x [DHH]
+
+* Use URI instead of regexps to normalize the URL and gain free, better matching #8136 [dkubb]
+
+* Allow -'s in #normalize_url [Rick]
+
+* remove instance of mattr_accessor, it was breaking tests since they don't load ActiveSupport. Fix Timeout test [Rick]
+
+* Throw a InvalidOpenId exception instead of just a RuntimeError when the URL can't be normalized [DHH]
+
+* Just use the path for the return URL, so extra query parameters don't interfere [DHH]
+
+* Added a new default database-backed store after experiencing trouble with the filestore on NFS. The file store is still available as an option [DHH]
+
+* Added normalize_url and applied it to all operations going through the plugin [DHH]
+
+* Removed open_id? as the idea of using the same input box for both OpenID and username has died -- use using_open_id? instead (which checks for the presence of params[:openid_url] by default) [DHH]
+
+* Added OpenIdAuthentication::Result to make it easier to deal with default situations where you don't care to do something particular for each error state [DHH]
+
+* Stop relying on root_url being defined, we can just grab the current url instead [DHH] \ No newline at end of file
diff --git a/vendor/plugins/open_id_authentication/README b/vendor/plugins/open_id_authentication/README
new file mode 100644
index 000000000..807cdc756
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/README
@@ -0,0 +1,231 @@
+OpenIdAuthentication
+====================
+
+Provides a thin wrapper around the excellent ruby-openid gem from JanRan. Be sure to install that first:
+
+ gem install ruby-openid
+
+To understand what OpenID is about and how it works, it helps to read the documentation for lib/openid/consumer.rb
+from that gem.
+
+The specification used is http://openid.net/specs/openid-authentication-2_0.html.
+
+
+Prerequisites
+=============
+
+OpenID authentication uses the session, so be sure that you haven't turned that off. It also relies on a number of
+database tables to store the authentication keys. So you'll have to run the migration to create these before you get started:
+
+ rake open_id_authentication:db:create
+
+Or, use the included generators to install or upgrade:
+
+ ./script/generate open_id_authentication_tables MigrationName
+ ./script/generate upgrade_open_id_authentication_tables MigrationName
+
+Alternatively, you can use the file-based store, which just relies on on tmp/openids being present in RAILS_ROOT. But be aware that this store only works if you have a single application server. And it's not safe to use across NFS. It's recommended that you use the database store if at all possible. To use the file-based store, you'll also have to add this line to your config/environment.rb:
+
+ OpenIdAuthentication.store = :file
+
+This particular plugin also relies on the fact that the authentication action allows for both POST and GET operations.
+If you're using RESTful authentication, you'll need to explicitly allow for this in your routes.rb.
+
+The plugin also expects to find a root_url method that points to the home page of your site. You can accomplish this by using a root route in config/routes.rb:
+
+ map.root :controller => 'articles'
+
+This plugin relies on Rails Edge revision 6317 or newer.
+
+
+Example
+=======
+
+This example is just to meant to demonstrate how you could use OpenID authentication. You might well want to add
+salted hash logins instead of plain text passwords and other requirements on top of this. Treat it as a starting point,
+not a destination.
+
+Note that the User model referenced in the simple example below has an 'identity_url' attribute. You will want to add the same or similar field to whatever
+model you are using for authentication.
+
+Also of note is the following code block used in the example below:
+
+ authenticate_with_open_id do |result, identity_url|
+ ...
+ end
+
+In the above code block, 'identity_url' will need to match user.identity_url exactly. 'identity_url' will be a string in the form of 'http://example.com' -
+If you are storing just 'example.com' with your user, the lookup will fail.
+
+There is a handy method in this plugin called 'normalize_url' that will help with validating OpenID URLs.
+
+ OpenIdAuthentication.normalize_url(user.identity_url)
+
+The above will return a standardized version of the OpenID URL - the above called with 'example.com' will return 'http://example.com/'
+It will also raise an InvalidOpenId exception if the URL is determined to not be valid.
+Use the above code in your User model and validate OpenID URLs before saving them.
+
+config/routes.rb
+
+ map.root :controller => 'articles'
+ map.resource :session
+
+
+app/views/sessions/new.erb
+
+ <% form_tag(session_url) do %>
+ <p>
+ <label for="name">Username:</label>
+ <%= text_field_tag "name" %>
+ </p>
+
+ <p>
+ <label for="password">Password:</label>
+ <%= password_field_tag %>
+ </p>
+
+ <p>
+ ...or use:
+ </p>
+
+ <p>
+ <label for="openid_identifier">OpenID:</label>
+ <%= text_field_tag "openid_identifier" %>
+ </p>
+
+ <p>
+ <%= submit_tag 'Sign in', :disable_with => "Signing in&hellip;" %>
+ </p>
+ <% end %>
+
+app/controllers/sessions_controller.rb
+ class SessionsController < ApplicationController
+ def create
+ if using_open_id?
+ open_id_authentication
+ else
+ password_authentication(params[:name], params[:password])
+ end
+ end
+
+
+ protected
+ def password_authentication(name, password)
+ if @current_user = @account.users.authenticate(params[:name], params[:password])
+ successful_login
+ else
+ failed_login "Sorry, that username/password doesn't work"
+ end
+ end
+
+ def open_id_authentication
+ authenticate_with_open_id do |result, identity_url|
+ if result.successful?
+ if @current_user = @account.users.find_by_identity_url(identity_url)
+ successful_login
+ else
+ failed_login "Sorry, no user by that identity URL exists (#{identity_url})"
+ end
+ else
+ failed_login result.message
+ end
+ end
+ end
+
+
+ private
+ def successful_login
+ session[:user_id] = @current_user.id
+ redirect_to(root_url)
+ end
+
+ def failed_login(message)
+ flash[:error] = message
+ redirect_to(new_session_url)
+ end
+ end
+
+
+
+If you're fine with the result messages above and don't need individual logic on a per-failure basis,
+you can collapse the case into a mere boolean:
+
+ def open_id_authentication
+ authenticate_with_open_id do |result, identity_url|
+ if result.successful? && @current_user = @account.users.find_by_identity_url(identity_url)
+ successful_login
+ else
+ failed_login(result.message || "Sorry, no user by that identity URL exists (#{identity_url})")
+ end
+ end
+ end
+
+
+Simple Registration OpenID Extension
+====================================
+
+Some OpenID Providers support this lightweight profile exchange protocol. See more: http://www.openidenabled.com/openid/simple-registration-extension
+
+You can support it in your app by changing #open_id_authentication
+
+ def open_id_authentication(identity_url)
+ # Pass optional :required and :optional keys to specify what sreg fields you want.
+ # Be sure to yield registration, a third argument in the #authenticate_with_open_id block.
+ authenticate_with_open_id(identity_url,
+ :required => [ :nickname, :email ],
+ :optional => :fullname) do |result, identity_url, registration|
+ case result.status
+ when :missing
+ failed_login "Sorry, the OpenID server couldn't be found"
+ when :invalid
+ failed_login "Sorry, but this does not appear to be a valid OpenID"
+ when :canceled
+ failed_login "OpenID verification was canceled"
+ when :failed
+ failed_login "Sorry, the OpenID verification failed"
+ when :successful
+ if @current_user = @account.users.find_by_identity_url(identity_url)
+ assign_registration_attributes!(registration)
+
+ if current_user.save
+ successful_login
+ else
+ failed_login "Your OpenID profile registration failed: " +
+ @current_user.errors.full_messages.to_sentence
+ end
+ else
+ failed_login "Sorry, no user by that identity URL exists"
+ end
+ end
+ end
+ end
+
+ # registration is a hash containing the valid sreg keys given above
+ # use this to map them to fields of your user model
+ def assign_registration_attributes!(registration)
+ model_to_registration_mapping.each do |model_attribute, registration_attribute|
+ unless registration[registration_attribute].blank?
+ @current_user.send("#{model_attribute}=", registration[registration_attribute])
+ end
+ end
+ end
+
+ def model_to_registration_mapping
+ { :login => 'nickname', :email => 'email', :display_name => 'fullname' }
+ end
+
+Attribute Exchange OpenID Extension
+===================================
+
+Some OpenID providers also support the OpenID AX (attribute exchange) protocol for exchanging identity information between endpoints. See more: http://openid.net/specs/openid-attribute-exchange-1_0.html
+
+Accessing AX data is very similar to the Simple Registration process, described above -- just add the URI identifier for the AX field to your :optional or :required parameters. For example:
+
+ authenticate_with_open_id(identity_url,
+ :required => [ :email, 'http://schema.openid.net/birthDate' ]) do |result, identity_url, registration|
+
+This would provide the sreg data for :email, and the AX data for 'http://schema.openid.net/birthDate'
+
+
+
+Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license \ No newline at end of file
diff --git a/vendor/plugins/open_id_authentication/Rakefile b/vendor/plugins/open_id_authentication/Rakefile
new file mode 100644
index 000000000..31074b856
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/Rakefile
@@ -0,0 +1,22 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the open_id_authentication plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the open_id_authentication plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'OpenIdAuthentication'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
diff --git a/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb b/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb
new file mode 100644
index 000000000..6f78afc71
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb
@@ -0,0 +1,11 @@
+class OpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase
+ def initialize(runtime_args, runtime_options = {})
+ super
+ end
+
+ def manifest
+ record do |m|
+ m.migration_template 'migration.rb', 'db/migrate'
+ end
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb b/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb
new file mode 100644
index 000000000..ef2a0cfb4
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb
@@ -0,0 +1,20 @@
+class <%= class_name %> < ActiveRecord::Migration
+ def self.up
+ create_table :open_id_authentication_associations, :force => true do |t|
+ t.integer :issued, :lifetime
+ t.string :handle, :assoc_type
+ t.binary :server_url, :secret
+ end
+
+ create_table :open_id_authentication_nonces, :force => true do |t|
+ t.integer :timestamp, :null => false
+ t.string :server_url, :null => true
+ t.string :salt, :null => false
+ end
+ end
+
+ def self.down
+ drop_table :open_id_authentication_associations
+ drop_table :open_id_authentication_nonces
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb b/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb
new file mode 100644
index 000000000..d13bbab23
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb
@@ -0,0 +1,26 @@
+class <%= class_name %> < ActiveRecord::Migration
+ def self.up
+ drop_table :open_id_authentication_settings
+ drop_table :open_id_authentication_nonces
+
+ create_table :open_id_authentication_nonces, :force => true do |t|
+ t.integer :timestamp, :null => false
+ t.string :server_url, :null => true
+ t.string :salt, :null => false
+ end
+ end
+
+ def self.down
+ drop_table :open_id_authentication_nonces
+
+ create_table :open_id_authentication_nonces, :force => true do |t|
+ t.integer :created
+ t.string :nonce
+ end
+
+ create_table :open_id_authentication_settings, :force => true do |t|
+ t.string :setting
+ t.binary :value
+ end
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb b/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb
new file mode 100644
index 000000000..02fddd7fd
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb
@@ -0,0 +1,11 @@
+class UpgradeOpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase
+ def initialize(runtime_args, runtime_options = {})
+ super
+ end
+
+ def manifest
+ record do |m|
+ m.migration_template 'migration.rb', 'db/migrate'
+ end
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/init.rb b/vendor/plugins/open_id_authentication/init.rb
new file mode 100644
index 000000000..2055ef700
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/init.rb
@@ -0,0 +1,16 @@
+begin
+ require 'openid'
+rescue LoadError
+ begin
+ gem 'ruby-openid', '>=2.1.4'
+ rescue Gem::LoadError
+ # no openid support
+ end
+end
+
+if Object.const_defined?(:OpenID)
+ config.to_prepare do
+ OpenID::Util.logger = Rails.logger
+ ActionController::Base.send :include, OpenIdAuthentication
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication.rb
new file mode 100644
index 000000000..54a38acc1
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication.rb
@@ -0,0 +1,241 @@
+require 'uri'
+require 'openid/extensions/sreg'
+require 'openid/extensions/ax'
+require 'openid/store/filesystem'
+
+require File.dirname(__FILE__) + '/open_id_authentication/db_store'
+require File.dirname(__FILE__) + '/open_id_authentication/mem_cache_store'
+require File.dirname(__FILE__) + '/open_id_authentication/request'
+require File.dirname(__FILE__) + '/open_id_authentication/timeout_fixes' if OpenID::VERSION == "2.0.4"
+
+module OpenIdAuthentication
+ OPEN_ID_AUTHENTICATION_DIR = RAILS_ROOT + "/tmp/openids"
+
+ def self.store
+ @@store
+ end
+
+ def self.store=(*store_option)
+ store, *parameters = *([ store_option ].flatten)
+
+ @@store = case store
+ when :db
+ OpenIdAuthentication::DbStore.new
+ when :mem_cache
+ OpenIdAuthentication::MemCacheStore.new(*parameters)
+ when :file
+ OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR)
+ else
+ raise "Unknown store: #{store}"
+ end
+ end
+
+ self.store = :db
+
+ class InvalidOpenId < StandardError
+ end
+
+ class Result
+ ERROR_MESSAGES = {
+ :missing => "Sorry, the OpenID server couldn't be found",
+ :invalid => "Sorry, but this does not appear to be a valid OpenID",
+ :canceled => "OpenID verification was canceled",
+ :failed => "OpenID verification failed",
+ :setup_needed => "OpenID verification needs setup"
+ }
+
+ def self.[](code)
+ new(code)
+ end
+
+ def initialize(code)
+ @code = code
+ end
+
+ def status
+ @code
+ end
+
+ ERROR_MESSAGES.keys.each { |state| define_method("#{state}?") { @code == state } }
+
+ def successful?
+ @code == :successful
+ end
+
+ def unsuccessful?
+ ERROR_MESSAGES.keys.include?(@code)
+ end
+
+ def message
+ ERROR_MESSAGES[@code]
+ end
+ end
+
+ # normalizes an OpenID according to http://openid.net/specs/openid-authentication-2_0.html#normalization
+ def self.normalize_identifier(identifier)
+ # clean up whitespace
+ identifier = identifier.to_s.strip
+
+ # if an XRI has a prefix, strip it.
+ identifier.gsub!(/xri:\/\//i, '')
+
+ # dodge XRIs -- TODO: validate, don't just skip.
+ unless ['=', '@', '+', '$', '!', '('].include?(identifier.at(0))
+ # does it begin with http? if not, add it.
+ identifier = "http://#{identifier}" unless identifier =~ /^http/i
+
+ # strip any fragments
+ identifier.gsub!(/\#(.*)$/, '')
+
+ begin
+ uri = URI.parse(identifier)
+ uri.scheme = uri.scheme.downcase # URI should do this
+ identifier = uri.normalize.to_s
+ rescue URI::InvalidURIError
+ raise InvalidOpenId.new("#{identifier} is not an OpenID identifier")
+ end
+ end
+
+ return identifier
+ end
+
+ # deprecated for OpenID 2.0, where not all OpenIDs are URLs
+ def self.normalize_url(url)
+ ActiveSupport::Deprecation.warn "normalize_url has been deprecated, use normalize_identifier instead"
+ self.normalize_identifier(url)
+ end
+
+ protected
+ def normalize_url(url)
+ OpenIdAuthentication.normalize_url(url)
+ end
+
+ def normalize_identifier(url)
+ OpenIdAuthentication.normalize_identifier(url)
+ end
+
+ # The parameter name of "openid_identifier" is used rather than the Rails convention "open_id_identifier"
+ # because that's what the specification dictates in order to get browser auto-complete working across sites
+ def using_open_id?(identity_url = nil) #:doc:
+ identity_url ||= params[:openid_identifier] || params[:openid_url]
+ !identity_url.blank? || params[:open_id_complete]
+ end
+
+ def authenticate_with_open_id(identity_url = nil, options = {}, &block) #:doc:
+ identity_url ||= params[:openid_identifier] || params[:openid_url]
+
+ if params[:open_id_complete].nil?
+ begin_open_id_authentication(identity_url, options, &block)
+ else
+ complete_open_id_authentication(&block)
+ end
+ end
+
+ private
+ def begin_open_id_authentication(identity_url, options = {})
+ identity_url = normalize_identifier(identity_url)
+ return_to = options.delete(:return_to)
+ method = options.delete(:method)
+
+ options[:required] ||= [] # reduces validation later
+ options[:optional] ||= []
+
+ open_id_request = open_id_consumer.begin(identity_url)
+ add_simple_registration_fields(open_id_request, options)
+ add_ax_fields(open_id_request, options)
+ redirect_to(open_id_redirect_url(open_id_request, return_to, method))
+ rescue OpenIdAuthentication::InvalidOpenId => e
+ yield Result[:invalid], identity_url, nil
+ rescue OpenID::OpenIDError, Timeout::Error => e
+ logger.error("[OPENID] #{e}")
+ yield Result[:missing], identity_url, nil
+ end
+
+ def complete_open_id_authentication
+ params_with_path = params.reject { |key, value| request.path_parameters[key] }
+ params_with_path.delete(:format)
+ open_id_response = timeout_protection_from_identity_server { open_id_consumer.complete(params_with_path, requested_url) }
+ identity_url = normalize_identifier(open_id_response.display_identifier) if open_id_response.display_identifier
+
+ case open_id_response.status
+ when OpenID::Consumer::SUCCESS
+ profile_data = {}
+
+ # merge the SReg data and the AX data into a single hash of profile data
+ [ OpenID::SReg::Response, OpenID::AX::FetchResponse ].each do |data_response|
+ if data_response.from_success_response( open_id_response )
+ profile_data.merge! data_response.from_success_response( open_id_response ).data
+ end
+ end
+
+ yield Result[:successful], identity_url, profile_data
+ when OpenID::Consumer::CANCEL
+ yield Result[:canceled], identity_url, nil
+ when OpenID::Consumer::FAILURE
+ yield Result[:failed], identity_url, nil
+ when OpenID::Consumer::SETUP_NEEDED
+ yield Result[:setup_needed], open_id_response.setup_url, nil
+ end
+ end
+
+ def open_id_consumer
+ OpenID::Consumer.new(session, OpenIdAuthentication.store)
+ end
+
+ def add_simple_registration_fields(open_id_request, fields)
+ sreg_request = OpenID::SReg::Request.new
+
+ # filter out AX identifiers (URIs)
+ required_fields = fields[:required].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
+ optional_fields = fields[:optional].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
+
+ sreg_request.request_fields(required_fields, true) unless required_fields.blank?
+ sreg_request.request_fields(optional_fields, false) unless optional_fields.blank?
+ sreg_request.policy_url = fields[:policy_url] if fields[:policy_url]
+ open_id_request.add_extension(sreg_request)
+ end
+
+ def add_ax_fields( open_id_request, fields )
+ ax_request = OpenID::AX::FetchRequest.new
+
+ # look through the :required and :optional fields for URIs (AX identifiers)
+ fields[:required].each do |f|
+ next unless f =~ /^https?:\/\//
+ ax_request.add( OpenID::AX::AttrInfo.new( f, nil, true ) )
+ end
+
+ fields[:optional].each do |f|
+ next unless f =~ /^https?:\/\//
+ ax_request.add( OpenID::AX::AttrInfo.new( f, nil, false ) )
+ end
+
+ open_id_request.add_extension( ax_request )
+ end
+
+ def open_id_redirect_url(open_id_request, return_to = nil, method = nil)
+ open_id_request.return_to_args['_method'] = (method || request.method).to_s
+ open_id_request.return_to_args['open_id_complete'] = '1'
+ open_id_request.redirect_url(root_url, return_to || requested_url)
+ end
+
+ def requested_url
+ relative_url_root = self.class.respond_to?(:relative_url_root) ?
+ self.class.relative_url_root.to_s :
+ request.relative_url_root
+ "#{request.protocol}#{request.host_with_port}#{ActionController::AbstractRequest.relative_url_root}#{request.path}"
+ end
+
+ def timeout_protection_from_identity_server
+ yield
+ rescue Timeout::Error
+ Class.new do
+ def status
+ OpenID::FAILURE
+ end
+
+ def msg
+ "Identity server timed out"
+ end
+ end.new
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/association.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/association.rb
new file mode 100644
index 000000000..9654eaeb2
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication/association.rb
@@ -0,0 +1,9 @@
+module OpenIdAuthentication
+ class Association < ActiveRecord::Base
+ set_table_name :open_id_authentication_associations
+
+ def from_record
+ OpenID::Association.new(handle, secret, issued, lifetime, assoc_type)
+ end
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/db_store.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/db_store.rb
new file mode 100644
index 000000000..780fb6ad2
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication/db_store.rb
@@ -0,0 +1,55 @@
+require 'openid/store/interface'
+
+module OpenIdAuthentication
+ class DbStore < OpenID::Store::Interface
+ def self.cleanup_nonces
+ now = Time.now.to_i
+ Nonce.delete_all(["timestamp > ? OR timestamp < ?", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew])
+ end
+
+ def self.cleanup_associations
+ now = Time.now.to_i
+ Association.delete_all(['issued + lifetime > ?',now])
+ end
+
+ def store_association(server_url, assoc)
+ remove_association(server_url, assoc.handle)
+ Association.create(:server_url => server_url,
+ :handle => assoc.handle,
+ :secret => assoc.secret,
+ :issued => assoc.issued,
+ :lifetime => assoc.lifetime,
+ :assoc_type => assoc.assoc_type)
+ end
+
+ def get_association(server_url, handle = nil)
+ assocs = if handle.blank?
+ Association.find_all_by_server_url(server_url)
+ else
+ Association.find_all_by_server_url_and_handle(server_url, handle)
+ end
+
+ assocs.reverse.each do |assoc|
+ a = assoc.from_record
+ if a.expires_in == 0
+ assoc.destroy
+ else
+ return a
+ end
+ end if assocs.any?
+
+ return nil
+ end
+
+ def remove_association(server_url, handle)
+ Association.delete_all(['server_url = ? AND handle = ?', server_url, handle]) > 0
+ end
+
+ def use_nonce(server_url, timestamp, salt)
+ return false if Nonce.find_by_server_url_and_timestamp_and_salt(server_url, timestamp, salt)
+ return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
+ Nonce.create(:server_url => server_url, :timestamp => timestamp, :salt => salt)
+ return true
+ end
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/mem_cache_store.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/mem_cache_store.rb
new file mode 100644
index 000000000..b520e4a8b
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication/mem_cache_store.rb
@@ -0,0 +1,73 @@
+require 'digest/sha1'
+require 'openid/store/interface'
+
+module OpenIdAuthentication
+ class MemCacheStore < OpenID::Store::Interface
+ def initialize(*addresses)
+ @connection = ActiveSupport::Cache::MemCacheStore.new(addresses)
+ end
+
+ def store_association(server_url, assoc)
+ server_key = association_server_key(server_url)
+ assoc_key = association_key(server_url, assoc.handle)
+
+ assocs = @connection.read(server_key) || {}
+ assocs[assoc.issued] = assoc_key
+
+ @connection.write(server_key, assocs)
+ @connection.write(assoc_key, assoc, :expires_in => assoc.lifetime)
+ end
+
+ def get_association(server_url, handle = nil)
+ if handle
+ @connection.read(association_key(server_url, handle))
+ else
+ server_key = association_server_key(server_url)
+ assocs = @connection.read(server_key)
+ return if assocs.nil?
+
+ last_key = assocs[assocs.keys.sort.last]
+ @connection.read(last_key)
+ end
+ end
+
+ def remove_association(server_url, handle)
+ server_key = association_server_key(server_url)
+ assoc_key = association_key(server_url, handle)
+ assocs = @connection.read(server_key)
+
+ return false unless assocs && assocs.has_value?(assoc_key)
+
+ assocs = assocs.delete_if { |key, value| value == assoc_key }
+
+ @connection.write(server_key, assocs)
+ @connection.delete(assoc_key)
+
+ return true
+ end
+
+ def use_nonce(server_url, timestamp, salt)
+ return false if @connection.read(nonce_key(server_url, salt))
+ return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
+ @connection.write(nonce_key(server_url, salt), timestamp, :expires_in => OpenID::Nonce.skew)
+ return true
+ end
+
+ private
+ def association_key(server_url, handle = nil)
+ "openid_association_#{digest(server_url)}_#{digest(handle)}"
+ end
+
+ def association_server_key(server_url)
+ "openid_association_server_#{digest(server_url)}"
+ end
+
+ def nonce_key(server_url, salt)
+ "openid_nonce_#{digest(server_url)}_#{digest(salt)}"
+ end
+
+ def digest(text)
+ Digest::SHA1.hexdigest(text)
+ end
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/nonce.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/nonce.rb
new file mode 100644
index 000000000..c52f6c50d
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication/nonce.rb
@@ -0,0 +1,5 @@
+module OpenIdAuthentication
+ class Nonce < ActiveRecord::Base
+ set_table_name :open_id_authentication_nonces
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb
new file mode 100644
index 000000000..e0cc8e3fc
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb
@@ -0,0 +1,23 @@
+module OpenIdAuthentication
+ module Request
+ def self.included(base)
+ base.alias_method_chain :request_method, :openid
+ end
+
+ def request_method_with_openid
+ if !parameters[:_method].blank? && parameters[:open_id_complete] == '1'
+ parameters[:_method].to_sym
+ else
+ request_method_without_openid
+ end
+ end
+ end
+end
+
+# In Rails 2.3, the request object has been renamed
+# from AbstractRequest to Request
+if defined? ActionController::Request
+ ActionController::Request.send :include, OpenIdAuthentication::Request
+else
+ ActionController::AbstractRequest.send :include, OpenIdAuthentication::Request
+end
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb
new file mode 100644
index 000000000..cc711c9ac
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb
@@ -0,0 +1,20 @@
+# http://trac.openidenabled.com/trac/ticket/156
+module OpenID
+ @@timeout_threshold = 20
+
+ def self.timeout_threshold
+ @@timeout_threshold
+ end
+
+ def self.timeout_threshold=(value)
+ @@timeout_threshold = value
+ end
+
+ class StandardFetcher
+ def make_http(uri)
+ http = @proxy.new(uri.host, uri.port)
+ http.read_timeout = http.open_timeout = OpenID.timeout_threshold
+ http
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/plugins/open_id_authentication/tasks/open_id_authentication_tasks.rake b/vendor/plugins/open_id_authentication/tasks/open_id_authentication_tasks.rake
new file mode 100644
index 000000000..c71434a50
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/tasks/open_id_authentication_tasks.rake
@@ -0,0 +1,30 @@
+namespace :open_id_authentication do
+ namespace :db do
+ desc "Creates authentication tables for use with OpenIdAuthentication"
+ task :create => :environment do
+ generate_migration(["open_id_authentication_tables", "add_open_id_authentication_tables"])
+ end
+
+ desc "Upgrade authentication tables from ruby-openid 1.x.x to 2.x.x"
+ task :upgrade => :environment do
+ generate_migration(["upgrade_open_id_authentication_tables", "upgrade_open_id_authentication_tables"])
+ end
+
+ def generate_migration(args)
+ require 'rails_generator'
+ require 'rails_generator/scripts/generate'
+
+ if ActiveRecord::Base.connection.supports_migrations?
+ Rails::Generator::Scripts::Generate.new.run(args)
+ else
+ raise "Task unavailable to this database (no migration support)"
+ end
+ end
+
+ desc "Clear the authentication tables"
+ task :clear => :environment do
+ OpenIdAuthentication::DbStore.cleanup_nonces
+ OpenIdAuthentication::DbStore.cleanup_associations
+ end
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/test/mem_cache_store_test.rb b/vendor/plugins/open_id_authentication/test/mem_cache_store_test.rb
new file mode 100644
index 000000000..18a943979
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/test/mem_cache_store_test.rb
@@ -0,0 +1,151 @@
+require File.dirname(__FILE__) + '/test_helper'
+require File.dirname(__FILE__) + '/../lib/open_id_authentication/mem_cache_store'
+
+# Mock MemCacheStore with MemoryStore for testing
+class OpenIdAuthentication::MemCacheStore < OpenID::Store::Interface
+ def initialize(*addresses)
+ @connection = ActiveSupport::Cache::MemoryStore.new
+ end
+end
+
+class MemCacheStoreTest < Test::Unit::TestCase
+ ALLOWED_HANDLE = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
+
+ def setup
+ @store = OpenIdAuthentication::MemCacheStore.new
+ end
+
+ def test_store
+ server_url = "http://www.myopenid.com/openid"
+ assoc = gen_assoc(0)
+
+ # Make sure that a missing association returns no result
+ assert_retrieve(server_url)
+
+ # Check that after storage, getting returns the same result
+ @store.store_association(server_url, assoc)
+ assert_retrieve(server_url, nil, assoc)
+
+ # more than once
+ assert_retrieve(server_url, nil, assoc)
+
+ # Storing more than once has no ill effect
+ @store.store_association(server_url, assoc)
+ assert_retrieve(server_url, nil, assoc)
+
+ # Removing an association that does not exist returns not present
+ assert_remove(server_url, assoc.handle + 'x', false)
+
+ # Removing an association that does not exist returns not present
+ assert_remove(server_url + 'x', assoc.handle, false)
+
+ # Removing an association that is present returns present
+ assert_remove(server_url, assoc.handle, true)
+
+ # but not present on subsequent calls
+ assert_remove(server_url, assoc.handle, false)
+
+ # Put assoc back in the store
+ @store.store_association(server_url, assoc)
+
+ # More recent and expires after assoc
+ assoc2 = gen_assoc(1)
+ @store.store_association(server_url, assoc2)
+
+ # After storing an association with a different handle, but the
+ # same server_url, the handle with the later expiration is returned.
+ assert_retrieve(server_url, nil, assoc2)
+
+ # We can still retrieve the older association
+ assert_retrieve(server_url, assoc.handle, assoc)
+
+ # Plus we can retrieve the association with the later expiration
+ # explicitly
+ assert_retrieve(server_url, assoc2.handle, assoc2)
+
+ # More recent, and expires earlier than assoc2 or assoc. Make sure
+ # that we're picking the one with the latest issued date and not
+ # taking into account the expiration.
+ assoc3 = gen_assoc(2, 100)
+ @store.store_association(server_url, assoc3)
+
+ assert_retrieve(server_url, nil, assoc3)
+ assert_retrieve(server_url, assoc.handle, assoc)
+ assert_retrieve(server_url, assoc2.handle, assoc2)
+ assert_retrieve(server_url, assoc3.handle, assoc3)
+
+ assert_remove(server_url, assoc2.handle, true)
+
+ assert_retrieve(server_url, nil, assoc3)
+ assert_retrieve(server_url, assoc.handle, assoc)
+ assert_retrieve(server_url, assoc2.handle, nil)
+ assert_retrieve(server_url, assoc3.handle, assoc3)
+
+ assert_remove(server_url, assoc2.handle, false)
+ assert_remove(server_url, assoc3.handle, true)
+
+ assert_retrieve(server_url, nil, assoc)
+ assert_retrieve(server_url, assoc.handle, assoc)
+ assert_retrieve(server_url, assoc2.handle, nil)
+ assert_retrieve(server_url, assoc3.handle, nil)
+
+ assert_remove(server_url, assoc2.handle, false)
+ assert_remove(server_url, assoc.handle, true)
+ assert_remove(server_url, assoc3.handle, false)
+
+ assert_retrieve(server_url, nil, nil)
+ assert_retrieve(server_url, assoc.handle, nil)
+ assert_retrieve(server_url, assoc2.handle, nil)
+ assert_retrieve(server_url, assoc3.handle, nil)
+
+ assert_remove(server_url, assoc2.handle, false)
+ assert_remove(server_url, assoc.handle, false)
+ assert_remove(server_url, assoc3.handle, false)
+ end
+
+ def test_nonce
+ server_url = "http://www.myopenid.com/openid"
+
+ [server_url, ''].each do |url|
+ nonce1 = OpenID::Nonce::mk_nonce
+
+ assert_nonce(nonce1, true, url, "#{url}: nonce allowed by default")
+ assert_nonce(nonce1, false, url, "#{url}: nonce not allowed twice")
+ assert_nonce(nonce1, false, url, "#{url}: nonce not allowed third time")
+
+ # old nonces shouldn't pass
+ old_nonce = OpenID::Nonce::mk_nonce(3600)
+ assert_nonce(old_nonce, false, url, "Old nonce #{old_nonce.inspect} passed")
+ end
+ end
+
+ private
+ def gen_assoc(issued, lifetime = 600)
+ secret = OpenID::CryptUtil.random_string(20, nil)
+ handle = OpenID::CryptUtil.random_string(128, ALLOWED_HANDLE)
+ OpenID::Association.new(handle, secret, Time.now + issued, lifetime, 'HMAC-SHA1')
+ end
+
+ def assert_retrieve(url, handle = nil, expected = nil)
+ assoc = @store.get_association(url, handle)
+
+ if expected.nil?
+ assert_nil(assoc)
+ else
+ assert_equal(expected, assoc)
+ assert_equal(expected.handle, assoc.handle)
+ assert_equal(expected.secret, assoc.secret)
+ end
+ end
+
+ def assert_remove(url, handle, expected)
+ present = @store.remove_association(url, handle)
+ assert_equal(expected, present)
+ end
+
+ def assert_nonce(nonce, expected, server_url, msg = "")
+ stamp, salt = OpenID::Nonce::split_nonce(nonce)
+ actual = @store.use_nonce(server_url, stamp, salt)
+ assert_equal(expected, actual, msg)
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/test/normalize_test.rb b/vendor/plugins/open_id_authentication/test/normalize_test.rb
new file mode 100644
index 000000000..635d3abc9
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/test/normalize_test.rb
@@ -0,0 +1,32 @@
+require File.dirname(__FILE__) + '/test_helper'
+
+class NormalizeTest < Test::Unit::TestCase
+ include OpenIdAuthentication
+
+ NORMALIZATIONS = {
+ "openid.aol.com/nextangler" => "http://openid.aol.com/nextangler",
+ "http://openid.aol.com/nextangler" => "http://openid.aol.com/nextangler",
+ "https://openid.aol.com/nextangler" => "https://openid.aol.com/nextangler",
+ "HTTP://OPENID.AOL.COM/NEXTANGLER" => "http://openid.aol.com/NEXTANGLER",
+ "HTTPS://OPENID.AOL.COM/NEXTANGLER" => "https://openid.aol.com/NEXTANGLER",
+ "loudthinking.com" => "http://loudthinking.com/",
+ "http://loudthinking.com" => "http://loudthinking.com/",
+ "http://loudthinking.com:80" => "http://loudthinking.com/",
+ "https://loudthinking.com:443" => "https://loudthinking.com/",
+ "http://loudthinking.com:8080" => "http://loudthinking.com:8080/",
+ "techno-weenie.net" => "http://techno-weenie.net/",
+ "http://techno-weenie.net" => "http://techno-weenie.net/",
+ "http://techno-weenie.net " => "http://techno-weenie.net/",
+ "=name" => "=name"
+ }
+
+ def test_normalizations
+ NORMALIZATIONS.each do |from, to|
+ assert_equal to, normalize_identifier(from)
+ end
+ end
+
+ def test_broken_open_id
+ assert_raises(InvalidOpenId) { normalize_identifier(nil) }
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/test/open_id_authentication_test.rb b/vendor/plugins/open_id_authentication/test/open_id_authentication_test.rb
new file mode 100644
index 000000000..ddcc17b96
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/test/open_id_authentication_test.rb
@@ -0,0 +1,46 @@
+require File.dirname(__FILE__) + '/test_helper'
+
+class OpenIdAuthenticationTest < Test::Unit::TestCase
+ def setup
+ @controller = Class.new do
+ include OpenIdAuthentication
+ def params() {} end
+ end.new
+ end
+
+ def test_authentication_should_fail_when_the_identity_server_is_missing
+ open_id_consumer = mock()
+ open_id_consumer.expects(:begin).raises(OpenID::OpenIDError)
+ @controller.expects(:open_id_consumer).returns(open_id_consumer)
+ @controller.expects(:logger).returns(mock(:error => true))
+
+ @controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url|
+ assert result.missing?
+ assert_equal "Sorry, the OpenID server couldn't be found", result.message
+ end
+ end
+
+ def test_authentication_should_be_invalid_when_the_identity_url_is_invalid
+ @controller.send(:authenticate_with_open_id, "!") do |result, identity_url|
+ assert result.invalid?, "Result expected to be invalid but was not"
+ assert_equal "Sorry, but this does not appear to be a valid OpenID", result.message
+ end
+ end
+
+ def test_authentication_should_fail_when_the_identity_server_times_out
+ open_id_consumer = mock()
+ open_id_consumer.expects(:begin).raises(Timeout::Error, "Identity Server took too long.")
+ @controller.expects(:open_id_consumer).returns(open_id_consumer)
+ @controller.expects(:logger).returns(mock(:error => true))
+
+ @controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url|
+ assert result.missing?
+ assert_equal "Sorry, the OpenID server couldn't be found", result.message
+ end
+ end
+
+ def test_authentication_should_begin_when_the_identity_server_is_present
+ @controller.expects(:begin_open_id_authentication)
+ @controller.send(:authenticate_with_open_id, "http://someone.example.com")
+ end
+end
diff --git a/vendor/plugins/open_id_authentication/test/status_test.rb b/vendor/plugins/open_id_authentication/test/status_test.rb
new file mode 100644
index 000000000..b1d5e0933
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/test/status_test.rb
@@ -0,0 +1,14 @@
+require File.dirname(__FILE__) + '/test_helper'
+
+class StatusTest < Test::Unit::TestCase
+ include OpenIdAuthentication
+
+ def test_state_conditional
+ assert Result[:missing].missing?
+ assert Result[:missing].unsuccessful?
+ assert !Result[:missing].successful?
+
+ assert Result[:successful].successful?
+ assert !Result[:successful].unsuccessful?
+ end
+end \ No newline at end of file
diff --git a/vendor/plugins/open_id_authentication/test/test_helper.rb b/vendor/plugins/open_id_authentication/test/test_helper.rb
new file mode 100644
index 000000000..43216e1ef
--- /dev/null
+++ b/vendor/plugins/open_id_authentication/test/test_helper.rb
@@ -0,0 +1,17 @@
+require 'test/unit'
+require 'rubygems'
+
+gem 'activesupport'
+require 'active_support'
+
+gem 'actionpack'
+require 'action_controller'
+
+gem 'mocha'
+require 'mocha'
+
+gem 'ruby-openid'
+require 'openid'
+
+RAILS_ROOT = File.dirname(__FILE__) unless defined? RAILS_ROOT
+require File.dirname(__FILE__) + "/../lib/open_id_authentication"